Brent_1 14 Newbie Poster

Microsoft ASP.NET MVC is arguably one of the most flexible frameworks for building modern web applications. One of the things that I've noticed over the last couple of years is that with many AJAX applications using Restful web service, many MVC controllers are now almost redundant.

Using the default MVC HTTP handler means that regardless of whether you actually need server-side code for rendering each view, you must have a corresponding controller.

In many cases, these controllers have a single action (e.g. Index) which simply returns the view. This seems inefficient and adds to the maintenance cost of the application.

So how can we get around this? Wouldn't it be great if we could just have a single re-usable controller with a single action instead of creating multiple copies of the same thing? The good news is that it's easy with MVC!

Create a Custom HTTP Handler

The first thing that we need to create is a custom HTTP handler [ControllerLessHttpHandler.cs]. This will look for a controller relevant to the view being requested; if it finds one, then it will work in the usual way. This means that if you need a controller for specific pages, you can still add them.

If it doesn't find a controller for the view, it will re-route the request to a re-usable controller-less view controller (we'll get to that bit later on).

using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

/// <summary>
/// The ControllerLessHttpHandler class.
/// </summary>
public class ControllerLessHttpHandler : IHttpHandler, System.Web.SessionState.IRequiresSessionState, IRouteHandler
{
    /// <summary>
    /// The requestContext field.
    /// </summary>
    private readonly RequestContext _requestContext;

    /// <summary>
    /// Initializes a new instance of the <see cref="ControllerLessHttpHandler"/> class.
    /// </summary>
    /// <param name="requestContext">The request context.</param>
    public ControllerLessHttpHandler(RequestContext requestContext)
    {
        _requestContext = requestContext;
    }

    /// <summary>
    /// Gets a value indicating whether another request can use the <see cref="T:System.Web.IHttpHandler" /> instance.
    /// </summary>
    /// <returns>true if the <see cref="T:System.Web.IHttpHandler" /> instance is reusable; otherwise, false.</returns>
    public bool IsReusable
    {
        get
        {
            return true;
        }
    }

    /// <summary>
    /// Process the current HTTP request.
    /// </summary>
    /// <param name="httpContext">The HttpContext containing the request.</param>
    public void ProcessRequest(HttpContext httpContext)
    {
        var controller = _requestContext.RouteData.GetRequiredString("controller");
        var action = string.Empty;

        if (_requestContext.RouteData.Values["action"] != null)
        {
            action = _requestContext.RouteData.Values["action"].ToString();
        }

        if (action != string.Empty)
        {
            IController viewController = null;
            IControllerFactory viewControllerFactory = null;

            try
            {
                viewControllerFactory = ControllerBuilder.Current.GetControllerFactory();

                try
                {
                    viewController = viewControllerFactory.CreateController(_requestContext, controller);
                }
                catch
                {
                    _requestContext.RouteData.Values["ctrl"] = controller;
                    _requestContext.RouteData.Values["x-ctrl"] = controller;
                    _requestContext.RouteData.Values["action"] = "Index";
                    _requestContext.RouteData.Values["x-action"] = action;

                    controller = "ControllerLess";
                    viewController = viewControllerFactory.CreateController(_requestContext, controller);
                }

                if (viewController != null)
                {
                    viewController.Execute(_requestContext);
                }
            }
            finally
            {
                if (viewControllerFactory != null)
                {
                    viewControllerFactory.ReleaseController(viewController);
                }
            }
        }
    }

    /// <summary>
    /// Provides the object that processes the request.
    /// </summary>
    /// <param name="requestContext">An object that encapsulates information about the request.</param>
    /// <returns>An object that processes the request.</returns>
    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        throw new NotImplementedException();
    }
}
Create the route handler

Next, we need to create a custom route handler [ControllerLessRouteHandler.cs] to let MVC know which HTTP handler to use to process each request.

using System.Web;
using System.Web.Routing;

/// <summary>
/// The ControllerLessRouteHandler class.
/// </summary>
public class ControllerLessRouteHandler : IRouteHandler
{
    /// <summary>
    /// Provides the object that processes the request.
    /// </summary>
    /// <param name="requestContext">An object that encapsulates information about the request.</param>
    /// <returns>An object that processes the request.</returns>
    IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
    {
        return new ControllerLessHttpHandler(requestContext);
    }
}
Update the MVC Route Configuration

Once all this is in place, we can configure the MVC routes [RouteConfig.cs] to use the custom route handler.

using System.Web.Mvc;
using System.Web.Routing;

/// <summary>
/// The RouteConfig class.
/// </summary>
public class RouteConfig
{
    /// <summary>
    /// Registers the routes.
    /// </summary>
    /// <param name="routes">The routes.</param>
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        var route = new Route(
                    "{controller}/{action}/{id}",
                    new RouteValueDictionary(new { controller = "Home", action = "Index", id = UrlParameter.Optional }),
                    new ControllerLessRouteHandler());

        routes.Add(route);
    }
}
Create the Custom Controller

Finally, we can add the custom view controller that the requests will be routed through [ControllerLessController.cs].

Now give it a try. Simply add a view without a corresponding controller, start up your application and navigate to the view in your browser.

You can now add as many views as you like, without needing to add any more controllers. And if you need a controller for a specific view, just add it in the normal way and it will work as expected.

using System.Web.Mvc;

/// <summary>
/// The ControllerLessController class.
/// </summary>
public class ControllerLessController : Controller
{
    /// <summary>
    /// Provides the default view controller.
    /// </summary>
    /// <returns>The requested view.</returns>
    public ActionResult Index()
    {
        var controller = RouteData.Values["x-ctrl"].ToString();
        var action = RouteData.Values["x-action"].ToString();
        var path = string.Format("~/Views/{0}/{1}.cshtml", controller, action);
        return View(path);
    }
}
NuGet

A NuGet package supporting both C# and VB.NET views and with more configuration options is available at https://www.nuget.org/packages/ControllerLess

GitHub

The source code for the NuGet package can be found at https://github.com/brentj73/ControllerLess

History

Originally posted by Brent Jenkins at http://www.anterec.co.uk/articles/using-aspnet-mvc-5-without-creating-controllers-for-each-view/

__avd commented: worth a try. +14