Click here to Skip to main content
12,074,308 members (65,442 online)
Click here to Skip to main content
Add your own
alternative version

Stats

26.6K views
218 downloads
22 bookmarked
Posted

Using ASP.NET MVC Without Controllers or Actions for Each View

, 23 May 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
Using ASP.NET MVC without controllers or actions for each view

Introduction

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.

Background

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 and action.

ASP.NET MVC Default Routing Behaviour

Looking at the CustomerController that you would create for this example, you'd have something like this:

public class CustomerController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Detail()
    {
        return View();
    }
}

In many cases, these controllers have one or more actions that simply returns the corresponding view. This seems inefficient and adds to the maintenance cost of the application.

This raises some fundamental questions:

  • Isn't one of the principles of objected oriented programming the re-use of code?
  • Does the code above reflect that principle?
  • What if we were to add more views handled by that controller?
  • Would the situation be improved if we were able to omit those actions which only return a view and focus on the code specific to your 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!

ASP.NET MVC Modified Routing Behaviour

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 a bit later on).

using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Anterec.ControllerLess.Configuration;

/// <summary>
/// The ControllerLessHttpHandler class.
/// </summary>
public class ControllerLessHttpHandler : IHttpHandler
{
    /// <summary>
    /// The request context.
    /// </summary>
    private readonly RequestContext _requestContext;

    /// <summary>
    /// The configuration settings.
    /// </summary>
    private readonly RouteConfiguration _configuration;

    /// <summary>
    /// Initializes a new instance of the <see cref="ControllerLessHttpHandler"/> class.
    /// </summary>
    /// <param name="requestContext">The requestContext to be used in this instance.</param>
    public ControllerLessHttpHandler(RequestContext requestContext)
    {
        _requestContext = requestContext;
        _configuration = RouteConfiguration.GetConfigurationSettings();

        var target = typeof(ControllerLessHttpHandler).Namespace;
        if (!ControllerBuilder.Current.DefaultNamespaces.Contains(target))
        {
            ControllerBuilder.Current.DefaultNamespaces.Add(target);
        }
    }

    /// <summary>
    /// Gets a value indicating whether this class is reusable.
    /// </summary>
    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 controllerFactory = null;

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

                try
                {
                    viewController = controllerFactory.CreateController(_requestContext, controller);
                    viewController.Execute(_requestContext);
                }
                catch
                {
                    DispatchRequest(controllerFactory, controller, action);
                }
            }
            finally
            {
                if (controllerFactory != null)
                {
                    controllerFactory.ReleaseController(viewController);
                }
            }
        }
    }

    /// <summary>
    /// Dispatches the request.
    /// </summary>
    /// <param name="controllerFactory">The controller factory.</param>
    /// <param name="controller">The controller.</param>
    /// <param name="action">The action.</param>
    private void DispatchRequest(IControllerFactory controllerFactory, string controller, string action)
    {
        var route = GetRoute(controller, action);
        _requestContext.RouteData.Values["x-action"] = action;
        _requestContext.RouteData.Values["x-controller"] = controller;

        if (route != null)
        {
            _requestContext.RouteData.Values["controller"] = route.Controller;
            _requestContext.RouteData.Values["action"] = route.Action;

            if (route.Area != string.Empty)
            {
                _requestContext.RouteData.DataTokens["area"] = route.Area;
            }

            controller = route.Controller;
        }
        else
        {
            _requestContext.RouteData.Values["action"] = _configuration.DefaultAction;
            controller = _configuration.DefaultController;
        }

        var viewController = controllerFactory.CreateController(_requestContext, controller);
        if (viewController != null)
        {
            viewController.Execute(_requestContext);
        }
    }

    /// <summary>
    /// Gets the configured route.
    /// </summary>
    /// <param name="controller">The controller.</param>
    /// <param name="action">The action.</param>
    /// <returns>The configured route (or null if the route is not configured).</returns>
    private RouteElement GetRoute(string controller, string action)
    {
        RouteElement route;

        if (_requestContext.RouteData.Values["area"] != null)
        {
            var area = _requestContext.RouteData.Values["area"].ToString();
            route = _configuration.Get(string.Format("/{0}/{1}/{2}", area, controller, action));
        }
        else
        {
            route = _configuration.Get(string.Format("/{0}/{1}", controller, action));
        }

        return route;
    }
}

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 sealed class ControllerLessRouteHandler : IRouteHandler
{
    /// <summary>
    /// Gets the IRouteHandler for the current request.
    /// </summary>
    /// <param name="requestContext">The RequestContext for the current request.</param>
    /// <returns>The IRouteHandler for the current request.</returns>
    IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
    {
        return new ControllerLessHttpHandler(requestContext);
    }
}

Create the Custom Controller

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

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 virtual ActionResult Index()
    {
        var action = RouteData.Values["x-action"].ToString();
        var controller = RouteData.Values["x-controller"].ToString();
        RouteData.Values["action"] = action;
        RouteData.Values["controller"] = controller;
        if (RouteData.Values["area"] != null)
        {
            RouteData.DataTokens["area"] = RouteData.Values["area"].ToString();
        }

        return View(action);
    }
}

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);
    }
}

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/or actions. And if you need an action or controller for a specific view, just add it in the normal way and it will work as expected.

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 full source code for the NuGet package can be found at https://github.com/brentj73/ControllerLess.

History

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Brent Jenkins
Software Developer Anterec
United Kingdom United Kingdom
No Biography provided

You may also be interested in...

Comments and Discussions

 
QuestionPowerful concept Pin
Member 87689757-Sep-15 18:01
memberMember 87689757-Sep-15 18:01 
SuggestionMore powerful Pin
Wladis5-Jun-14 0:25
memberWladis5-Jun-14 0:25 
GeneralRe: More powerful Pin
Brent Jenkins5-Jun-14 0:39
memberBrent Jenkins5-Jun-14 0:39 
GeneralRe: More powerful Pin
Wladis5-Jun-14 0:52
memberWladis5-Jun-14 0:52 
GeneralRe: More powerful Pin
Brent Jenkins5-Jun-14 0:56
memberBrent Jenkins5-Jun-14 0:56 
GeneralRe: More powerful Pin
Wladis5-Jun-14 1:34
memberWladis5-Jun-14 1:34 
GeneralRe: More powerful Pin
Brent Jenkins5-Jun-14 1:48
memberBrent Jenkins5-Jun-14 1:48 
GeneralRe: More powerful Pin
Wladis5-Jun-14 1:56
memberWladis5-Jun-14 1:56 
GeneralRe: More powerful Pin
Brent Jenkins5-Jun-14 1:58
memberBrent Jenkins5-Jun-14 1:58 
GeneralRe: More powerful Pin
Wladis5-Jun-14 2:02
memberWladis5-Jun-14 2:02 
GeneralRe: More powerful Pin
Brent Jenkins5-Jun-14 2:05
memberBrent Jenkins5-Jun-14 2:05 
GeneralRe: More powerful Pin
Wladis5-Jun-14 2:11
memberWladis5-Jun-14 2:11 
GeneralRe: More powerful Pin
Brent Jenkins5-Jun-14 2:18
memberBrent Jenkins5-Jun-14 2:18 
GeneralRe: More powerful Pin
Wladis5-Jun-14 3:00
memberWladis5-Jun-14 3:00 
GeneralRe: More powerful Pin
Brent Jenkins5-Jun-14 3:09
memberBrent Jenkins5-Jun-14 3:09 
GeneralRe: More powerful Pin
Wladis5-Jun-14 3:29
memberWladis5-Jun-14 3:29 
GeneralRe: More powerful Pin
Brent Jenkins5-Jun-14 3:39
memberBrent Jenkins5-Jun-14 3:39 
AnswerRe: More powerful Pin
Wladis5-Jun-14 4:02
memberWladis5-Jun-14 4:02 
GeneralRe: More powerful Pin
Brent Jenkins5-Jun-14 4:17
memberBrent Jenkins5-Jun-14 4:17 
AnswerRe: More powerful Pin
Wladis5-Jun-14 4:24
memberWladis5-Jun-14 4:24 
GeneralRe: More powerful Pin
Brent Jenkins5-Jun-14 4:28
memberBrent Jenkins5-Jun-14 4:28 
GeneralRe: More powerful Pin
Wladis5-Jun-14 4:34
memberWladis5-Jun-14 4:34 
GeneralRe: More powerful Pin
Brent Jenkins5-Jun-14 4:44
memberBrent Jenkins5-Jun-14 4:44 
GeneralRe: More powerful Pin
Wladis5-Jun-14 5:19
memberWladis5-Jun-14 5:19 
GeneralRe: More powerful Pin
Brent Jenkins5-Jun-14 5:21
memberBrent Jenkins5-Jun-14 5:21 
GeneralRe: More powerful Pin
Wladis5-Jun-14 5:29
memberWladis5-Jun-14 5:29 
GeneralRe: More powerful Pin
Brent Jenkins5-Jun-14 5:39
memberBrent Jenkins5-Jun-14 5:39 
GeneralRe: More powerful Pin
Wladis5-Jun-14 5:53
memberWladis5-Jun-14 5:53 
GeneralRe: More powerful Pin
Brent Jenkins5-Jun-14 5:57
memberBrent Jenkins5-Jun-14 5:57 
GeneralRe: More powerful Pin
Wladis5-Jun-14 6:45
memberWladis5-Jun-14 6:45 
GeneralRe: More powerful Pin
Wladis5-Jun-14 2:00
memberWladis5-Jun-14 2:00 
GeneralRe: More powerful Pin
Brent Jenkins5-Jun-14 2:01
memberBrent Jenkins5-Jun-14 2:01 
Questioni guess this can be done in any asp.net mvc version Pin
Tridip Bhattacharjee25-May-14 22:21
memberTridip Bhattacharjee25-May-14 22:21 
AnswerRe: i guess this can be done in any asp.net mvc version Pin
Brent Jenkins26-May-14 1:54
memberBrent Jenkins26-May-14 1:54 
QuestionExcellent Idea!! Pin
jlopez78823-May-14 7:03
memberjlopez78823-May-14 7:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.160208.1 | Last Updated 23 May 2014
Article Copyright 2014 by Brent Jenkins
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid