I blogged many times before on use of CSHTML MVC views in Angular application. They make it easier to implement things like validation and localization, as well as generate uniform look and feel for views. However, I am still against using those views to serve the data. The data should always come from data services, Web Api in all my recent projects. Hence, most of my MVC controller methods are super simple and look similarly to the following:
public ActionResult Edit() { return PartialView("_Edit"); }
One of my co-workers at Tyler gave me an idea to use the same controller for all such methods. They also had a specific implementation using custom MVC handler registered with a route. I found this approach a bit too cumbersome. So instead I decided to implement the idea in my own way, using controller factories. My idea is also to use this generic controller when no specific controller exists when serving HTML views to my Angular application. I also do not want to change my approach when defining Angular routes and I want my URLs to correspond to the data I am fetching. For example, to get my Contact List view I want to use http://myserver.com/Contacts/Index. Hence, I want to use the usual MVC routes:
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.IgnoreRoute("{resource}.aspx/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); }
So, inside my context I would have controller and action already defined as Contacts and Index in my example above. My controller factory just need to check to see if the controller type exists, and if not redirect to the generic view controller:
using System; using System.Web.Mvc; using System.Web.Routing; using AngularAspNetMvc.Web.Controllers; namespace AngularAspNetMvc.Web.App_Start { public class CustomControllerFactory : DefaultControllerFactory { protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (controllerType == null) { var controllerName = requestContext.RouteData.Values["controller"].ToString() .Replace("Controller", ""); var viewName = requestContext.RouteData.Values["action"].ToString(); requestContext.RouteData.Values["controller"] = "GenericView"; requestContext.RouteData.Values["action"] = "GetView"; requestContext.RouteData.Values["controllerName"] = controllerName; requestContext.RouteData.Values["viewName"] = viewName; return base.GetControllerInstance(requestContext, typeof(GenericViewController)); } return base.GetControllerInstance(requestContext, controllerType); } } }
And then my controller has single method – GetView:
using System.Web.Mvc; namespace AngularAspNetMvc.Web.Controllers { public class GenericViewController : Controller { public ActionResult GetView(string controllerName, string viewName) { return PartialView("~/Views/" + controllerName + "/_" + viewName + ".cshtml"); } } }
As you can see I use controller from the original request to use as folder name under Views folder and original action name as CSHML view name. The only small change is to add underscore to be consistent with naming convention to use underscore for partial views.
You can download entire sample application here.
Enjoy.