@Shazwazza

Shannon Deminick's blog all about web development

Isolated WebApi attribute routing

January 17, 2016 12:39

Attribute routing in ASP.Net WebApi is great and makes routing your controllers quite a bit more elegant than writing routes manually. However one problem I have with it is that it is either “on” or “off” at an application level.  There is no way for a library developer to tell ASP.Net to create routes based on attributes for specific controllers or assemblies without forcing the consumer of that library to enable Attribute Routing for the whole application. In many cases this might not matter, but if you are creating a package or library of that contains it’s own API routes, you probably don’t want to interfere with a developers’ normal application setup. There should be no reason why they need to be forced to turn on attribute routing in order for your product to work, and similarly they might not want your routes automatically enabled.

The good news is that this is possible. With a bit of code, you can route your own controllers with attribute routing and be able to turn them on or off without affecting the default application attribute routes. A full implementation of this has been created for the Umbraco RestApi project so I’ll reference that source in this post for the following code examples.

Show me the code

They key to getting this to work is: IDirectRouteProvider, IDirectRouteFactory

The first thing we need is a custom IDirectRouteFactory which is actually a custom attribute. I’ve called this CustomRouteAttribute  but you could call it whatever you want.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class CustomRouteAttribute : Attribute, IDirectRouteFactory

This custom attribute just wraps the default WebApi RouteAttribute’s IDirectRouteFactory implementation so we don’t have to re-write any code for that.

(see full implementation here)

Next we’ll create a custom IDirectRouteProvider:

/// <summary>
/// This is used to lookup our CustomRouteAttribute instead of the normal RouteAttribute so that 
/// we can use the CustomRouteAttribute instead of the RouteAttribute on our controlles so the normal
/// MapHttpAttributeRoutes method doesn't try to route our controllers - since the point of this is
/// to be able to map our controller routes with attribute routing explicitly without interfering
/// with default application routes.
/// </summary>
public class CustomRouteAttributeDirectRouteProvider : DefaultDirectRouteProvider
{
    private readonly bool _inherit;

    public CustomRouteAttributeDirectRouteProvider(bool inherit = false)
    {
        _inherit = inherit;
    }

    protected override IReadOnlyList<IDirectRouteFactory> GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
    {
        return actionDescriptor.GetCustomAttributes<CustomRouteAttribute>(inherit: _inherit);
    }
}

So far this is all pretty straight forward so far but here’s where things start to get interesting. Because we only want to create routes for specific controllers, we need to use a custom IHttpControllerTypeResolver. However, since the HttpConfiguration instance only contains a single reference to the IHttpControllerTypeResolver we need to do some hacking. The route creation process for attribute routing happens during the HttpConfiguration initialization so we need to create an isolated instance of HttpConfiguration, set it up with the services we want to use, initialize it to create our custom routes and assign those custom routes back to the main application’s HttpConfiguration.

Up first, we create a custom IHttpControllerTypeResolver to only resolve the controller we’re looking for:

public class SpecificControllerTypeResolver : IHttpControllerTypeResolver
{
    private readonly IEnumerable<Type> _controllerTypes;

    public SpecificControllerTypeResolver(IEnumerable<Type> controllerTypes)
    {
        if (controllerTypes == null) throw new ArgumentNullException("controllerTypes");
        _controllerTypes = controllerTypes;
    }

    public ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver)
    {
        return _controllerTypes.ToList();
    }
}

Before we look at initializing a separate instance of HttpConfiguration, lets look at the code you’d use to enable all of this in your startup code:

//config = the main application HttpConfiguration instance
config.MapControllerAttributeRoutes(
    routeNamePrefix: "MyRoutes-",
    //Map these explicit controllers in the order they appear
    controllerTypes: new[]
    {                    
        typeof (MyProductController),
        typeof (MyStoreController)
    });

The above code will enable custom attribute routing for the 2 specific controllers. These controllers will be routed with attribute routing but instead of using the standard [Route] attribute, you’d use our custom [CustomRoute] attribute. The MapControllerAttributeRoutes extension method is where all of the magic happens, here’s what it does:

  • Iterates over each controller type
  • Creates an instance of HttpConfiguration
  • Sets it’s IHttpControllerTypeResolver instance to SpecificControllerTypeResolver for the current controller iteration (The reason an instance of HttpConfiguration is created for each controller is to ensure that the routes are created in the order of which they are specified in the above code snippet)
  • Initialize the HttpConfiguration instance to create the custom attribute routes
  • Copy these routes back to the main application’s HttpConfguration route table

You can see the full implementation of this extension method here which includes code comments and more details on what it’s doing.  The actual implementation of this method also allows for some additional parameters and callbacks so that each of these routes could be customized if required when they are created.

 

There is obviously a bit of code involved to achieve this and there could very well be a simpler way, however this implementation does work rather well and offers quite a lot of flexibility. I’d certainly be interested to hear if other developers have figured this out and what their solutions were.

ASP.Net 5 Re-learning a few things (part 2)

November 21, 2014 01:34

This is part 2 of a series of posts about some fundamental changes in ASP.Net 5 that we’ll need to re-learn (or un-learn!)

Part 1: http://shazwazza.com/post/aspnet-5-re-learning-a-few-things-part-1/

System.Web

This probably isn’t new news to most people since it’s really one of the fundamental shifts for ASP.Net 5 – There won’t be a System.Web DLL. Everything that you’ll install in your website will come as different, smaller, separate libraries. For example, if you want to serve static files, you’d reference the Microsoft.AspNet.StaticFiles package, if you want to use MVC, you’d include the Microsoft.AspNet.Mvc package.

ASP.Net 5: “consists of modular components with minimal overhead, so you retain flexibility while constructing your solutions

Web Forms

Gone! YAY! :-)

Web Forms will still be a part of .Net as part of the main framework in System.Web, just not part of ASP.Net 5.

HttpModules

An HttpModule simply doesn’t exist in Asp.Net 5, but of course there is a new/better way to achieve this functionality, it’s called Middleware. In an HttpModule, you had to execute code based on the various stages of a request, things such as AuthenticateRequest, AuthorizeRequest, PostResolveRequestCache, and other slightly confusingly named events. This is no longer the case with Middleware, things just make sense now … everything is simply a linear execution of your code. You can have multiple middleware’s defined to execute in your application and each one is registered explicitly in your Startup.cs file. As a developer, you are in full control of what get’s executed and in what order instead of not knowing which HttpModules are executing and not really in control of their order of execution. Middleware can simply modify a request and continue calling the next one in the chain, or it can just terminate the pipeline and return a result.

There’s loads of examples in the source for middleware, ranging from the static file middleware to cookie authentication middleware,  etc…

And here’s a good article that explains middeware registration and the flow of control.

HttpHandlers

HttpHandlers are also a thing of the past. All they really were was a request handler that was based on a specific request path. MVC (which now also includes WebApi) has got this covered. If you really wanted, you could create middleware for this type of functionality as well but unless you require something extraordinarily complex that MVC cannot do (and it can do a lot!), I’d recommend just sticking with MVC.

Multiple WebApi controllers with the same name but different namespaces

June 27, 2014 05:56

Warren recently reported this issue on Umbraco which prohibits WebApi from routing to two different paths that specify the same controller name but different namespaces. This type of thing is fully supported in MVC but not in WebApi for some reason.

Here’s a quick example, suppose we have two controllers:

namespace Test1
{
    [PluginController("Test1")]
    [IsBackOffice]
    public class ConfigController : UmbracoApiController
    {
        public int GetStuff()
        {
            return 9876;
        }
    }
}
namespace Test2
{
    [PluginController("Test2")]
    [IsBackOffice]    
    public class ConfigController : UmbracoApiController
    {
        public int GetStuff()
        {
            return 1234;
        }
    }
}

These controller definitions will create routes to the following paths respectively:

  • /umbraco/backoffice/test1/config/getstuff
  • /umbraco/backoffice/test2/config/getstuff

When these routes are created, the “Namespaces” data token is specified on the route, just like what is done in MVC, however in WebApi that needs to be done manually. Example:

var r = routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);
r.DataTokens["Namespaces"] = new string[] {"Foo"};

but if you navigate to either of these paths you’ll end up with a message like:

Multiple types were found that match the controller named 'Config'. This can happen if the route that services this request ('umbraco/backoffice/Test2/Config/{action}/{id}') found multiple controllers defined with the same name but differing namespaces, which is not supported. The request for 'Config' has found the following matching controllers: Test1.ConfigController Test2.ConfigController

Custom IHttpControllerSelector

To achieve what we want, we need to create a custom IHttpControllerSelector. I’ve created this in the Umbraco core to solve the issue and the source can be found HERE. The implementation is pretty straight forward – it relies on the default WebApi controller selector for everything unless a “Namespaces” data token is detected in the route and more than one controller type was found for the current controller name in the app domain.

There’s some posts out there that elude to the possibility of this being supported in WebApi in the future but as of the latest source code for the DefaultHttpControllerSelector, it appears that the functionality is not yet there.

If you need this functionality though, this implementation is working and pretty simple. To register this selector just use this code on startup:

GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector),
    new NamespaceHttpControllerSelector(GlobalConfiguration.Configuration));

WebApi per controller configuration

March 21, 2014 00:56

This is more of a blog post about what not to do :)

At first glance, it would seem relatively painless to change your WebApi controller’s configuration, I’d assume most people would do what I initially did. Say for example you wanted to have your controller only support JSON, here’s what I initially tried (DO NOT DO THIS):

protected override void Initialize(HttpControllerContext controllerContext)
{
    base.Initialize(controllerContext);
    var toRemove = controllerContext.Configuration.Formatters
        .Where(t => (t is JsonMediaTypeFormatter) == false).ToList();
    foreach (var r in toRemove)
    {
        controllerContext.Configuration.Formatters.Remove(r);
    }
}

Simple right, just override initialize in your controller and change the current controllerContext’s configuration…. WRONG :(

What this is actually doing is modifying the global WebApi configuration though it’s not clear that this is the case. Unfortunately the actual Configuration property on the controllerContext instance is assigned to the global one. I’m assuming the WebApi team has done this for a reason but I’m not sure what that is; as seen above it’s very easy to change the global WebApi configuration at runtime. Seems to me like it might have been a better idea to clone the global configuration instance and assign that to each HttpControllerContext object.

The correct way to specify per controller custom configuration in WebApi is to use the IControllerConfiguration interface. You can read all about here and it is fairly simple but it does seem like you have to jump through a few hoops for something that initially seems very straight forward.

Uploading files and JSON data in the same request with Angular JS

May 25, 2013 04:01

I decided to write a quick blog post about this because much of the documentation and examples about this seems to be a bit scattered. What this achieves is the ability to upload any number of files with any other type of data in one request. For this example we’ll send up JSON data along with some files.

File upload directive

First we’ll create a simple custom file upload angular directive

app.directive('fileUpload', function () {
return {
scope: true, //create a new scope
link: function (scope, el, attrs) {
el.bind('change', function (event) {
var files = event.target.files;
//iterate files since 'multiple' may be specified on the element
for (var i = 0;i<files.length;i++) {
//emit event upward
scope.$emit("fileSelected", { file: files[i] });
}
});
}
};
});

The usage of this is simple:

<input type="file" file-upload multiple/>

The ‘multiple’ parameter indicates that the user can select multiple files to upload which this example fully supports.

In the directive we ensure a new scope is created and then listen for changes made to the file input element. When changes are detected with emit an event to all ancestor scopes (upward) with the file object as a parameter.

Mark-up & the controller

Next we’ll create a controller to:

  • Create a model to bind to
  • Create a collection of files
  • Consume this event so we can assign the files to  the collection
  • Create a method to post it all to the server

NOTE: I’ve put all this functionality in this controller for brevity, in most cases you’d have a separate factory to handle posting the data

With the controller in place, the mark-up might look like this (and will display the file names of all of the files selected):

<div ng-controller="Ctrl">
<input type="file" file-upload multiple/>
<ul>
<li ng-repeat="file in files">{{file.name}}</li>
</ul>
</div>

The controller code below contains some important comments relating to how the data gets posted up to the server, namely the ‘Content-Type’ header as the value that needs to be set is a bit quirky.

function Ctrl($scope, $http) {

//a simple model to bind to and send to the server
$scope.model = {
name: "",
comments: ""
};

//an array of files selected
$scope.files = [];

//listen for the file selected event
$scope.$on("fileSelected", function (event, args) {
$scope.$apply(function () {
//add the file object to the scope's files collection
$scope.files.push(args.file);
});
});

//the save method
$scope.save = function() {
$http({
method: 'POST',
url: "/Api/PostStuff",
//IMPORTANT!!! You might think this should be set to 'multipart/form-data'
// but this is not true because when we are sending up files the request
// needs to include a 'boundary' parameter which identifies the boundary
// name between parts in this multi-part request and setting the Content-type
// manually will not set this boundary parameter. For whatever reason,
// setting the Content-type to 'false' will force the request to automatically
// populate the headers properly including the boundary parameter.
headers: { 'Content-Type': false },
//This method will allow us to change how the data is sent up to the server
// for which we'll need to encapsulate the model data in 'FormData'
transformRequest: function (data) {
var formData = new FormData();
//need to convert our json object to a string version of json otherwise
// the browser will do a 'toString()' on the object which will result
// in the value '[Object object]' on the server.
formData.append("model", angular.toJson(data.model));
//now add all of the assigned files
for (var i = 0; i < data.files; i++) {
//add each file to the form data and iteratively name them
formData.append("file" + i, data.files[i]);
}
return formData;
},
//Create an object that contains the model and files which will be transformed
// in the above transformRequest method
data: { model: $scope.model, files: $scope.files }
}).
success(function (data, status, headers, config) {
alert("success!");
}).
error(function (data, status, headers, config) {
alert("failed!");
});
};
};

Handling the data server-side

This example shows how to handle the data on the server side using ASP.Net WebAPI, I’m sure it’s reasonably easy to do on other server-side platforms too.

public async Task<HttpResponseMessage> PostStuff()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}

var root = HttpContext.Current.Server.MapPath("~/App_Data/Temp/FileUploads");
Directory.CreateDirectory(root);
var provider = new MultipartFormDataStreamProvider(root);
var result = await Request.Content.ReadAsMultipartAsync(provider);
if (result.FormData["model"] == null)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}

var model = result.FormData["model"];
//TODO: Do something with the json model which is currently a string



//get the files
foreach (var file in result.FileData)
{
//TODO: Do something with each uploaded file
}

return Request.CreateResponse(HttpStatusCode.OK, "success!");
}