Customizing Dispatch Process in Web API 2

 30-Nov-2018   nityaprakash     WebAPI  life-cycle  Controller    Comments  0

In previous article we tried to understand the dispatch process of Web API. we extend it to customize the same project. How are we going to add new message handler in th dispatch process. We are also going to learn, how can we replace deafault message handlers with our custom ones.

Adding Custom Message Handler

As we learn in last post that, there are three basic message handlers are involve here HttpServer, HttpRoutingDispatcher and HttpControllerDispatcher. But if we want to add more than we can add. For example, I have created below CustomMessageHandler class which will not allow user to submit GET request. At very first stage it will determine request method from HttpRequestMessage and generate ErrorResponseMessage.


public class CustomMessageHandler : DelegatingHandler
    {
        protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            if (request.Method == HttpMethod.Get)
                return request.CreateErrorResponse(HttpStatusCode.MethodNotAllowed, "GET method is not allowed.");

            return await base.SendAsync(request, cancellationToken);
        }
    }

We have to add this configuration in WebApiConfig's Register method.


        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            config.MessageHandlers.Add(new CustomMessageHandler());

        }

Note: New Custom Handler will always be added between HttpServer and HttpRoutingDispatcher

Changing Behavior of Default Message Handler

In most of the cases, Default implementation of Dispatch Message handler are good enough. We don't need to customize them for large cases. But there can be the case when we want change behavior than this is useful technic. We are trying to change behavior of HttpControllerSelector. In previous post, we learnt that controller must be end with "Controller" suffix and should be implementing IHttpController interface. Here we change behavior slightly. We want Controllers to end with "Service" suffix.

In this case we have to implement two intercaces IHttpControllerTypeResolver and IHttpControllerSelector.

Custom Controller Type Resolver


using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;

 public class CustomControllerTypeResolver : IHttpControllerTypeResolver
    {
        public string Suffix { get; set; }

        public ICollection GetControllerTypes(IAssembliesResolver assembliesResolver)
        {
            return assembliesResolver.GetAssemblies()
                .Select(assembly => assembly.GetTypes())
                .SelectMany(t => t)
                .Where(t => t != null
                       && t.IsClass
                       && t.IsVisible
                       && !t.IsAbstract
                       && typeof(IHttpController).IsAssignableFrom(t)
                       && HasValidControllerName(t)).ToList();
        }

        private bool HasValidControllerName(Type t)
        {
            return t.Name.Length > Suffix.Length &&
                t.Name.EndsWith(Suffix, StringComparison.OrdinalIgnoreCase);
        }
    }

As you saw code above, that we haven't yet specify the controller suffix. We are making it bit more customizable, that Suffix will be provide from WebApiConfig.Register method. It will injected in the class during configuration. Above code will search for all assemblies and classes which are public, not abstract, implements IHttpController interface and has valid suffix.

Custom Controller Selector


using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;

   public class CustomControllerSelector : IHttpControllerSelector
    {
        private IDictionary dictionary;
        private ILookup mapping;
        private string Suffix { get; set; }
        public CustomControllerSelector(string suffix)
        {
            this.Suffix = suffix;
            HttpConfiguration config = GlobalConfiguration.Configuration;

            IHttpControllerTypeResolver typeResolver = config.Services.GetHttpControllerTypeResolver();

            IAssembliesResolver assembliesResolver = config.Services.GetAssembliesResolver();

            var descriptor = typeResolver.GetControllerTypes(assembliesResolver)
                                          .Select(t => new HttpControllerDescriptor
                                          {
                                              Configuration = config,
                                              ControllerName = t.Name.Substring(0, t.Name.Length - Suffix.Length),
                                              ControllerType = t
                                          });

            mapping = descriptor.ToLookup(d => d.ControllerName, StringComparer.OrdinalIgnoreCase);

            dictionary = descriptor.ToDictionary(d => d.ControllerName, d => d);
        }
        public IDictionary GetControllerMapping()
        {
            return dictionary;
        }

        public HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            string key = request.GetRequestContext().RouteData.Values["controller"] as string;

            IEnumerable matches = mapping[key];

            switch(matches.Count())
            {
                case 1:
                    return matches.First();
                case 0:
                    throw new HttpResponseException(System.Net.HttpStatusCode.NotFound);
                default:
                    throw new HttpResponseException(System.Net.HttpStatusCode.InternalServerError);
            }
        }
    }

Construcotr is playing main role here. we have injected/specified suffix in contructor and getting instance of HttpControllerTypeResolver reference from cofiguration and creating dictionary of HttpControllerDescriptor which will be return by GetControllerMapping() method. We also implemented SelectController method which will get the specific ControllerDescriptor and instantiate it.

Custom Message Handler configuration

Last thing left to hook these handler in in message handler chain. We are going Default Handlers with new one. Will use the Replace method of ServiceContainer.


using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        config.Services.Replace(typeof(IHttpControllerTypeResolver),
            new CustomControllerTypeResolver { Suffix = "Service" });

        config.Services.Replace(typeof(IHttpControllerSelector),
            new CustomControllerSelector("Service"));
    }
}

Replace method will search types IHttpControllerTypeResolver and `IHttpControllerSelector' and replace with our custom classes. Here we are passing "Service" as suffix to search controllers with this suffix.

There is another way of achiveing this, if we just want to replace suffix Controller with Service


using System.Reflection;

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        //config.Services.Replace(typeof(IHttpControllerTypeResolver),
            new CustomControllerTypeResolver { Suffix = "Service" });

        //config.Services.Replace(typeof(IHttpControllerSelector),
            new CustomControllerSelector("Service"));
            
        FieldInfo field = typeof(DefaultHttpControllerSelector)
        						.GetField("ControllerSuffix", BindingFlags.Static | BindingFlags.Public);
        if(field != null) {
        	field.SetValue(null, "Service");
        }
    }
}

Note: Just to change Suffix we shouldn't override the default behaviour.

Summary

In this post we learnt how to change the default behaviour of the mesage handlers and adding new handler in the chain. Adding new custom message handler in the message handling chain can be good use case, where we don't want perform some check or task before it start executing the controller. However, we may not need to create Custom Controller Type Resolver and Selector in most of the scenario, but for some special case, we saw how can we customize it.


Nitya Prakash Sharma has over 10 years of experience in .NET technology. He is currently working as Senior Consultant in industry. He is always keen to learn new things in Technology and eager to apply wherever is possible. He is also has interest in Photography, sketching and painting.

My Blog
Post Comment

COMMENTS