Chitika

March 26, 2012

ASP.NET MVC 4 WebAPI. Support Areas in HttpControllerFactory

This article was written for ASP.NET MVC 4 Beta. If you are using Release Candidate version of ASP.NET MVC 4 then you have to read the next article.

Unfortunately DefaultHttpControllerFactory doesn't support Areas by default.
To support it you have to write your HttpControllerFactory from scratch.

In this post i will show you how you can do it.

AreaHttpControllerFactory


First of all, you have to implement IHttpControllerFactory interface:
    public class AreaHttpControllerFactory : IHttpControllerFactory
    {
        public IHttpController CreateController(HttpControllerContext controllerContext, string controllerName)
        {
            throw new NotImplementedException();
        }

        public void ReleaseController(IHttpController controller)
        {
            throw new NotImplementedException();
        }
    }
We will implement two obligatory methods CreateController and ReleaseController later.

Let's start with fields and constructor:
        private const string ControllerSuffix = "Controller";
        private const string AreaRouteVariableName = "area";

        private IHttpControllerFactory _defaultFactory;
        private HttpConfiguration _configuration;
        private Dictionary<string, Type> _apiControllerTypes;

        public AreaHttpControllerFactory(HttpConfiguration configuration)
        {
            _configuration = configuration;
            _defaultFactory = new DefaultHttpControllerFactory(configuration);
        }
Our class is wrapper around DefaultHttpControllerFactory.
I hope i don't need to explain meaning of ControllerSuffix constant. But I would like to explain the AreaRouteVariableName constant - it contains the name of the variable which we will use to specify area name  in Routes collection.

In the _apiControllerTypes we will store all the API controllers types, so let's create private property:
        private Dictionary<string, Type> ApiControllerTypes
        {
            get
            {
                if (_apiControllerTypes != null)
                {
                    return _apiControllerTypes;
                }

                var assemblies = AppDomain.CurrentDomain.GetAssemblies();

                _apiControllerTypes = assemblies.SelectMany(a => a.GetTypes().Where(t => !t.IsAbstract && t.Name.EndsWith(ControllerSuffix) && typeof(IHttpController).IsAssignableFrom(t))).ToDictionary(t => t.FullName, t => t);

                return _apiControllerTypes;
            }
        }

This code, just takes all the API controllers types from all of your assemblies, and store it inside the dictionary, where the key is FullName of the type and value is the type itself.
Of course we will set this dictionary only once. And then just use it.

Now we are ready to implement the one of our main methods. I will start with ReleaseController method:
        public void ReleaseController(IHttpController controller)
        {
            _defaultFactory.ReleaseController(controller); 
        }
Easy.

The last method will do all the "magic" for us:
        public IHttpController CreateController(HttpControllerContext controllerContext, string controllerName)
        {
            var controller = GetApiController(controllerContext, controllerName);
            return controller ?? _defaultFactory.CreateController(controllerContext, controllerName);
        }
Easy as well. :)
And the method which will find the controller for us:
        private IHttpController GetApiController(HttpControllerContext controllerContext, string controllerName)
        {
            if (!controllerContext.RouteData.Values.ContainsKey(AreaRouteVariableName))
            {
                return null;
            }

            var areaName = controllerContext.RouteData.Values[AreaRouteVariableName].ToString().ToLower();
            if (string.IsNullOrEmpty(areaName))
            {
                return null;
            }

            var type = ApiControllerTypes.Where(t => t.Key.ToLower().Contains(string.Format(".{0}.", areaName)) && t.Key.EndsWith(string.Format(".{0}{1}", controllerName, ControllerSuffix), StringComparison.OrdinalIgnoreCase)).Select(t => t.Value).FirstOrDefault();
            if (type == null)
            {
                return null;
            }

            return CreateControllerInstance(controllerContext, controllerName, type);
        }
So, if an area variable is specifying in RouteData.Values and this variable is not null or not empty, then it tries to find the controller in the ApiControllerTypes by full name of the controller where the full name contains area's name surrounded by "." (e.g. ".Admin.") and ends with controller name + controller suffix (e.g. UsersController).
If the controller type is found, then it will calls CreateControllerInstance method. Otherwise, the method will return null.

And the code for CreateControllerInstance:
        private IHttpController CreateControllerInstance(HttpControllerContext controllerContext, string controllerName, Type controllerType)
        {
            var descriptor = new HttpControllerDescriptor(_configuration, controllerName, controllerType);
            controllerContext.ControllerDescriptor = descriptor;
            var controller = descriptor.HttpControllerActivator.Create(controllerContext, controllerType);
            controllerContext.Controller = controller;
            return controller;
        }

Registering AreaHttpControllerFactory


The next thing you have to do is to say to your application to use this controller factory instead of DefaultHttpControllerFactory. And fortunately it is really easy - just add one additional line to the end of Application_Start method in Glogal.asax file:
        protected void Application_Start()
        {
            // your default code

            GlobalConfiguration.Configuration.ServiceResolver.SetService(typeof(IHttpControllerFactory), new AreaHttpControllerFactory(GlobalConfiguration.Configuration));
        }
That's all.

Using AreaHttpControllerFactory

If you did everything right, now you can forget about that "nightmare" code. And just start to use it!

You have to add new HttpRoute to your AreaRegistration.cs file:
        public override void RegisterArea(AreaRegistrationContext context)
        {
            context.Routes.MapHttpRoute(
                name: "Admin_Api",
                routeTemplate: "api/admin/{controller}/{id}",
                defaults: new { area = AreaName, id = RouteParameter.Optional }
            );

            // other mappings
        }

That's all. Good luck, and have a nice day.

March 14, 2012

ASP.NET MVC 4 WebAPI authorization

In the examples of ASP.NET MVC 4 WebAPI you can find that authorization is really easy.
You just have to add [Authorize] attribute to your controller or for some actions which need it.

And if you do this you will expect that your WebAPI will return error code 401 (Not authorized) to your client.

But unfortunately it's not happened.
And you can ask: what will happen in the real world?
I can answer: your browser just get the 302 (Found) status code and will redirect you to the login page.

I think it's not expected behavior for you. Because you want to get just 401 status code in the client.

And I can show you how you can achieve it.

1. Please, double sure that you use AuthorizeAttribute from the System.Web.Http library instead of one from the System.Web.Mvc.

2. Add a directory and two classes to your code which should "FIX" it:

- Add App_Start directory to your solution.
       We will create two classes in this directory in a minute.

Add first class into that folder:  
       This class is a HTTP module which do all the 'magic'.
       The 'magic' is simple - just set the marker in PosrReleaseRequestState event if this is an ajax request and if our action returns status code equals to 401 (Unauthorized) or 403 (Forbidden). Then in OnEndRequest we have to check is this marker exists, and if yes, then set the returned status code from an action back.
public class AjaxFormsAuthenticationModule : IHttpModule

    {

        private const string FixupKey = "__WEBAPI:Authentication-Fixup";

        public void Dispose()

        {

        }

        public void Init(HttpApplication context)

        {

            context.PostReleaseRequestState += OnPostReleaseRequestState;

            context.EndRequest += OnEndRequest;

        }

        private void OnPostReleaseRequestState(object source, EventArgs args)

        {

            var context = (HttpApplication)source;

            var response = context.Response;

            var request = context.Request;

            bool isAjax = request.Headers["X-Requested-With"] == "XMLHttpRequest";

            if ((response.StatusCode == 401 || response.StatusCode == 403) && isAjax)

            {

                context.Context.Items[FixupKey] = response.StatusCode;

            }

        }

        private void OnEndRequest(object source, EventArgs args)

        {

            var context = (HttpApplication)source;

            var response = context.Response;

            if (context.Context.Items.Contains("__WEBAPI:Authentication-Fixup"))

            {

                response.StatusCode = (int)context.Context.Items[FixupKey];

                response.RedirectLocation = null;

            }

        }

    }

Add second class into that folder:
       This class will just register our HTTP module for our application..
using Microsoft.Web.Infrastructure.DynamicModuleHelper;

[assembly: PreApplicationStartMethod(typeof(FormsAuthenticationFixer), "Start")]

namespace LoginVS11.App_Start

{

    public static class FormsAuthenticationFixer

    {

        public static void Start()

        {

            DynamicModuleUtility.RegisterModule(typeof(AjaxFormsAuthenticationModule));

        }

    }

}

That's all.

After that all of your ajax requests will get the 401 status code if your client is not authorized yet.

March 5, 2012

ASP.NET MVC 4. Real application. Design, develop, testing.

In the next several posts i will show you how to create a real application in ASP.NET MVC 4.

I will try to cover all the aspects of creating an application.

I will start from design, where i will explain some general patterns.

Then I will show you how you can work with data through Entity Framework Code First, how you can migrate your database via Fluent Migrator.

Then the general principles of jQuery and jQuery UI. How you can create a version of your UI for mobile devices.

The last but not least - unit and integration testing of your application using Moq library and using database migration.


Table of contents:

  1. Design your application in ASP.NET MVC 4. (coming soon)
  2. General patterns - Repository, UnitOfWork. (coming soon)
  3. Entity Framework Code First in action. (coming soon)
  4. Database migrations with Fluent Migrator. (coming soon)
  5. Testing your repositories. (coming soon)
  6. Services via Web API. (coming soon)
  7. Business logic of the application. (coming soon)
  8. Unit testing using Moq library. (coming soon)
  9. Authentication and authorization with jQuery (coming soon)
  10. UI with jQuery. (coming soon)
  11. UI for mobile devices. (coming soon)
  12. Testing your UI. (coming soon)
  13. Create installation with Wix. (coming soon)

March 4, 2012

ASP.NET MVC 4 Beta has been relesed


Top Features

  • ASP.NET Web API
  • Refreshed and modernized default project templates
  • New mobile project template
  • Many new features to support mobile apps
  • Recipes to customize code generation
  • Enhanced support for asynchronous methods

You can read a detailed information at:
ASP.NET MVC official site

Download

You can download ASP.NET MVC 4 Beta for Visual Studio 2010:

Or you can download Visual Studio 11 Beta (includes ASP.NET MVC 4 Beta):