I'm setting up a new web application. We have services that perform continous background operations (CQRS projections), which is why we host them in windows services. We'd like to use these services to host the corresponding web APIs as well (otherwise we couldn't serve in-memory projections).
In addition, we'd like to have SignalR support to inform the clients whenever a projection was updated. We have a separate ASP.NET MVC application because we use Razor views for templating.
We'd like to split the web API into several areas - similar to how it's possible to do in an ASP.NET (MVC) application - with one area per bounded context. Such as http://localhost:8080/Orders/api/{Controller}/{id} or http://localhost:8080/Foo/api/{Controller}/{id}
At a later point we'd also like to have the controllers, projections, models etc. in separate assemblies. Again, one per context
Is it possible to define areas in a self-hosted Web API project? Would it perhaps be possible to route them to controllers from specific assemblies?
Thanks to the article in https://blogs.msdn.microsoft.com/webdev/2013/03/07/asp-net-web-api-using-namespaces-to-version-web-apis/ I've solved it.
I have to implement my own IHttpControllerSelector and replace the default one in Startup.cs like so:
/// <summary>
/// OWIN startup class
/// </summary>
public class Startup
{
/// <summary>
/// Owin configuration
/// </summary>
/// <param name="app">App builder</param>
public void Configuration(IAppBuilder app)
{
// We might have to resolve the referenced assemblies here or else we won't find them. This is a quick and dirty way to make sure that the assembly containing the controller has been loaded
var x = typeof(CarRental.Reservations.Application.Read.Web.FooController);
// Configure Web API for self-host.
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{boundedcontext}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// Enable routing via bounded context
config.Services.Replace(typeof(IHttpControllerSelector), new BoundedContextControllerSelector(config));
app.UseWebApi(config);
// Configure SignalR
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR();
}
}
With BoundedContextControllerSelector being an implementation of IHttpControllerSelector very close to the code in the example: https://aspnet.codeplex.com/SourceControl/changeset/view/dd207952fa86#Samples/WebApi/NamespaceControllerSelector/NamespaceHttpControllerSelector.cs
I use the namespace to determine the bounded context and now have a clear separation of web api endpoints for each context :)
Related
I know that when migrating from asp.net 2.2 to 3 app.UseMvc() line in startup.cs file's configure method is replaced by app.UseRouting(); app.UseAuthorization(); app.UseEndpoints();.
Can anyone explain how app.UseEndpoints() and app.UseMvc() works internally ?
Under the asp.net core mvc 2.2 framework, to use traditional routing, you must configure the IRouteBuilder interface in the UseMVC middleware.
In the Configure method of the application Startup, the default routing settings are as follows:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
{
In the call to UseMvc, MapRoute is used to create a single route, also known as the default route. Most MVC applications use routing with templates. A convenient method for the default route can be used:
app.UseMvcWithDefaultRoute();
UseMvc and UseMvcWithDefaultRoute can add an instance of RouterMiddleware to the middleware pipeline. MVC does not directly interact with middleware, but uses routing to process requests. MVC connects to the route through an instance of MvcRouteHandler.
UseMvc does not directly define any routes. It adds placeholders {controller=Home}/{action=Index}/{id?} to the route collection of attribute routing. By overloading UseMvc(Action), users are allowed to add their own routes, and attribute routing is also supported.
UseEndpoints: Perform matching endpoints.
It separates routing matching and resolution functions from endpoint execution functions, and until now, these functions are bundled with MVC middleware.
First of all,you could have a look at their source code:
public static IApplicationBuilder UseEndpoints(this IApplicationBuilder builder, Action<IEndpointRouteBuilder> configure)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
VerifyRoutingServicesAreRegistered(builder);
VerifyEndpointRoutingMiddlewareIsRegistered(builder, out var endpointRouteBuilder);
configure(endpointRouteBuilder);
// Yes, this mutates an IOptions. We're registering data sources in a global collection which
// can be used for discovery of endpoints or URL generation.
//
// Each middleware gets its own collection of data sources, and all of those data sources also
// get added to a global collection.
var routeOptions = builder.ApplicationServices.GetRequiredService<IOptions<RouteOptions>>();
foreach (var dataSource in endpointRouteBuilder.DataSources)
{
routeOptions.Value.EndpointDataSources.Add(dataSource);
}
return builder.UseMiddleware<EndpointMiddleware>();
}
ASP.NET Core 3 uses a refined endpoint routing which will generally give more control about routing within the application. Endpoint routing works in two separate steps:
In a first step, the requested route is matched agains the configured routes to figure out what route is being accessed.
In a final step, the determined route is being evaluated and the respective middleware, e.g. MVC, is called.
The two steps are set up by app.UseRouting() and app.UseEndpoints(). The former will register the middleware that runs the logic to determine the route. The latter will then execute that route.
Also, refer to:
https://asp.net-hacker.rocks/2019/08/12/aspnetcore30-look-into-startup.html https://aregcode.com/blog/2019/dotnetcore-understanding-aspnet-endpoint-routing/
I have created a 'WCFService' application under 'WCFSolution' solution and generated the DBContext using Entity Framework from a Database 'DemoDB' in 'WCFService' application. and also created some CRUD methods in WCFService (Which is working great).
Then I created an empty 'WCFMVCApp' MVC application under the same solution ('WCFSolution') and also added the service reference to this app. Now i needed to create a controller ('HomeController') with the DBContext that is generated in that WCFService, so that i can generate the views based on the WCF models while creating the controller.
I could create a new EF in WCFMVCApp but it would defeat the purpose of WCF. Any way to do this. or is it possible? Any help would be appreciated. Thank you.
If you are using a WCF Service, in your MVCProject you don't have a DbContext to deal with and you should not add a reference to your WCF Service. You have some options.
Solution 1: Use a client
In your MVC Project create a data service client. Your service should be running and you need the data service tools installed. Then you can add a service reference and some proxy classes are generated for you.
WCF Data Services 5.6.0 RTM Tools Installer
Solution 2: Add a DbContext dll
You can have your DbContext living in a seperate class library that you reference in your service and your MVC project.
In both cases you are using DataServiceContext to perform CRUD operations. For the second one you may have to add an an implementation for ResolveType. To get an idea how to do this, this is how the automatic generated DataServiceContext would resolve types:
ODataSamples
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "2.1.0")]
public Container(global::System.Uri serviceRoot) :
base(serviceRoot, global::Microsoft.OData.Client.ODataProtocolVersion.V4)
{
....
this.ResolveType = new global::System.Func<string, global::System.Type>(this.ResolveTypeFromName);
....
}
/// <summary>
/// Since the namespace configured for this service reference
/// in Visual Studio is different from the one indicated in the
/// server schema, use type-mappers to map between the two.
/// </summary>
protected string ResolveNameFromType(global::System.Type clientType)
{
global::Microsoft.OData.Client.OriginalNameAttribute originalNameAttribute = (global::Microsoft.OData.Client.OriginalNameAttribute)global::System.Linq.Enumerable.SingleOrDefault(global::Microsoft.OData.Client.Utility.GetCustomAttributes(clientType, typeof(global::Microsoft.OData.Client.OriginalNameAttribute), true));
if (clientType.Namespace.Equals("ODataSamples.CustomFormatService", global::System.StringComparison.Ordinal))
{
if (originalNameAttribute != null)
{
return string.Concat("ODataSamples.CustomFormatService.", originalNameAttribute.OriginalName);
}
return string.Concat("ODataSamples.CustomFormatService.", clientType.Name);
}
return null;
}
I have several .asmx web services that I want to upgrade to WebAPI. These web services look somewhat like this:
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.Web.Script.Services.ScriptService]
public class TheWebService : System.Web.Services.WebService {
[WebMethod(EnableSession = true)]
public string SomeMethod(string SomeInput)
{
MySessionModel TheSession = HttpContext.Current.Session["UserSession"] as MySessionModel;
return SomeClass.SomeMethod(SomeInput, TheSession);
}
}
Basically, I have a single-page application. I'm using Forms Auth to login and redirect users to their "profile" and then, from this page, the app uses web services to communicate with the server. The web services only return raw strings so I don't need serialization at teh web service level. For the moment, the app is hosted in IIS and soon I'll be deploying it into azure.
I've looked around on the web, and several posts suggest that using session state and HttpContext is bad design. Why is using HttpCurrent and session state a bad idea in this case?
There is nothing innately wrong with using ASP.NET Session, as long as you don't use it as a catch-all basket for any old data. Shopping carts, for example, do not belong in Session: they belong in a Shopping Cart persistence component.
Also, and I suspect the reason for the Azure tag on this question, if you are running in a load-balanced environment such as an Azure Cloud Service, you need to use an external session provider such as a SQL Database or a shared cache. Using the in-process session provider (the default) will cause very odd, often unreproducable bugs as users are switched between different servers with different copies of the session.
As for HttpContext.Current, well, for Web API, things like Inversion of Control, Dependency Injection, and simple testability are important. A clean, testable Web API version of that service might look something like this:
public class TheWebService : ApiController {
private readonly IUserSession _userSession;
public TheWebService(IUserSession userSession)
{
_userSession = userSession;
}
public string SomeMethod(string SomeInput)
{
MySessionModel TheSession = _userSession.Get();
return SomeClass.SomeMethod(SomeInput, TheSession);
}
}
public interface IUserSession
{
MySessionModel Get();
}
You could still use HttpContext.Current.Session["UserSession"] in a class like this:
public class CurrentContextUserSession : IUserSession
{
public MySessionModel Get()
{
return HttpContext.Current.Session["UserSession"] as MySessionModel;
}
}
You would then use an IoC container such as Unity or Ninject to set CurrentContextUserSession as the implementation of IUserSession for Web API to use when constructing instances of TheWebService. But when you were writing your tests, you could use a mock or stub implementation of IUserSession that had no dependency on HttpContext.Current.
In your specific example, you are using the Session only inside the WebMethod, which is fine as it is already coupled to ASP.NET but many people tend to use this at other layers of their application which is a really bad design.
Common problems of using HttpContext.Current in those layers are:
the code cannot be easily unit tested in isolation
the code is tightly coupled to an ASP.NET context
This being said, having stateful services that depend on the session is bad design. In the example you have shown, that's an ASMX WebService which is depending on the ASP.NET Session state meaning that the client should be passing cookies around in order to invoke this service. Also stateful services are harder to scale.
I have an ASP.NET Web API project with two controllers, one of which I want to be publicly addressable over the internet and the other which I only want to be called internally over the network.
The best solution that I can come up with so far is to have a route template for public controllers and a template for internal: -
routeTemplate: "api/{controller}/{id}"
routeTemplate: "privateapi/{controller}/{id}"
That way I can configure IIS to block requests to the ‘privateapi’ route.
Is that the best way to handle this scenario?
Thanks.
The problem with controlling access MVC and WebAPI in IIS is that routing can sometimes make it difficult to see exactly which routes are ending up at your controller. It is perfectly valid (and in many cases preferred) to restrict access in the code as well.
To do this in code, you can do something like the following which uses a custom AuthorizeAttribute to filter out unauthorized users.
public class InternalAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (actionContext.Request.Properties.ContainsKey("MS_HttpContext"))
{
var ipAddress =
((HttpContextWrapper) actionContext.Request.Properties["MS_HttpContext"]).Request.UserHostAddress;
if (IsPrivateAddress(ipAddress))
{
return;
}
}
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Forbidden, "Forbidden");
}
private bool IsPrivateAddress(string ipAddress)
{
// todo: verify ip address is in internal or otherwise whitelisted
}
}
You can then annotate your controller and have the filter applied on all actions in your controller.
[InternalAuthorize]
public class PrivateController : ApiController
{
}
Note: if the information/actions from this controller is particularly sensitive, you may want to deploy a version of your application that exposes this private api and blocks all traffic non from your whitelist rather than relying on application logic to keep bad guys out.
Use the Authorize Attribute:
[Authorize(Roles = "Admin")]
public class MyPrivateDataController :ApiController
You can't do this!
What you are doing is just creating another route for your controllers.
If they are deployed online they are accessible.
Now what you need is to deploy 2 different API's one at an external machine and another at an internal machine.
Today I am digging into WCF Data Service and I have a question regarding this. Can I create WCF Data service as Library and just only create WCF data Service in our existing web app and take reference that library using Factory property so service will deploy with existing web application.
As I know We can create WCF Service Library and only need to take reference that library in Web application like :
Create a WCF Library and implement service contract
Create a Web application and add new item as Wcf service file then take reference WCF library
<%# ServiceHost Service="MyServiceLibrary.MyService" Factory="System.ServiceModel.Activation.WebServiceHostFactory" />
Instead of a service library, I want to create OData service library.
Thanks
Yes, you can host a WCF Data Service in your own assembly - with a few little tricks. I researched this a while ago and came up with these steps / instructions.
Here's how:
put your data model (EF Data Model) into its own assembly, let's call it DataModel
create a new class library project (call it MyDataServiceHost)
add a few references:
your DataModel assembly with the data layer
System.ServiceModel
System.ServiceModel.Web
System.Data.Services.Client
System.Data.Services - you cannot pick this from the usual Add Reference dialog under the .NET category - you need to browse for the assembly file. Find the directory C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0 (or C:\Program Files (x86)\... on a 64-bit machine) and pick the System.Data.Services.dll inside it
add a new class to that class library and call it e.g. YourDataService.cs - it will look something like this:
using System.Data.Services;
using System.Data.Services.Common;
using DataModel;
namespace MyDataServiceHost
{
public class YourDataService : DataService<YourModelEntities>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
// TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc.
// Examples:
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}
}
}
You can name the class anything you like, and it has to derive from DataService<T> where T is the name of your data model; if you're using Entity Framework, it's the name of your object context class - typically something like (database)Entities or whatever you picked when you created the EDM
add another class to your new project, call it MyDataServiceHost.cs and it will look something like this:
using System;
using System.Data.Services;
using DataModel;
namespace MyDataServiceHost
{
public class MyDataServiceHost
{
public static void LaunchDataService(string baseAddress)
{
Uri[] baseAddresses = new Uri[1];
baseAddresses[0] = new Uri(baseAddress);
using(DataServiceHost host = new DataServiceHost(typeof(YourDataService), baseAddresses))
{
host.Open();
Console.WriteLine("DataService up and running.....");
Console.ReadLine();
host.Close();
}
}
}
}
It instantiates a DataServiceHost, which is derived from WebServiceHost (which in turn is derived from ServiceHost) and it will spin up the WCF Data Service runtime for you.
now you can start up your WCF Data Service from any app using:
MyDataServiceHost.LaunchDataService("http://localhost:4444/YourService");
last thing to remember: the app that you use to launch the WCF Data Service must have the connection string (the EDM connection string, if you're using Entity Framework) in its app.config (or web.config) in order for this to work!