ASP.NET MVC Custom View Routing

12,908

If I understood your question correctly, you can create your own view engine which resolves view location at runtime and plug into your application.

Create your own custom view engine.

    public class MyViewEngine : RazorViewEngine
{
    public MyViewEngine()
        : base()
    {
        ViewLocationFormats = new[] {
        "~/Views/{1}/%1/{0}.cshtml",
        "~/Views/{1}/%1/{0}.vbhtml",
        "~/Views/Shared/{0}.cshtml",
        "~/Views/Shared/{0}.vbhtml"
    };

    PartialViewLocationFormats = new[] {
        "~/Views/%1/{1}/{0}.cshtml",
        "~/Views/%1/{1}/{0}.vbhtml",
        "~/Views/Shared/{0}.cshtml",
        "~/Views/Shared/{0}.vbhtml"
    };
    }

    protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
    {
        var catagoryName = controllerContext.RouteData.Values["category"].ToString();
        return base.CreatePartialView(controllerContext, partialPath.Replace("%1", catagoryName));
    }

    protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
    {
        var catagoryName = controllerContext.RouteData.Values["category"].ToString();
        return base.CreateView(controllerContext, viewPath.Replace("%1", catagoryName),masterPath);
    }

    protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
    {
        var catagoryName = controllerContext.RouteData.Values["category"].ToString();
        return base.FileExists(controllerContext, virtualPath.Replace("%1", catagoryName));
    }

}

And register it here

protected void Application_Start()
{   
   AreaRegistration.RegisterAllAreas();

   RegisterGlobalFilters(GlobalFilters.Filters);
   RegisterRoutes(RouteTable.Routes);


   //Register your View Engine Here.
   ViewEngines.Engines.Add(new MyViewEngine());
}

Update route config, default should be

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{category}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", category = "DefaultCategoryName", id = UrlParameter.Optional }
        );
Share:
12,908
TtT23
Author by

TtT23

Updated on June 15, 2022

Comments

  • TtT23
    TtT23 almost 2 years

    Our solution hierarchy is as follows:

    Controller\Category\View

    Ex: Controllers\DataAnalysis\DataRetrieve

    Now I'd like to map the routing so that when the user just types the name of the view in the url, it automatically maps the url to the corresponding controller

    I.E: localhost:1234\DataAnalysis\DataRetrieve

    Should map to

    View\DataAnalysis\DataRetrieve\Index.cshtml

    Similarly, any url requests including the action should retrieve the corresponding view

    I.E: localhost:1234\DataAnalysis\DataRetrieve\TestAction

    Should map to

    View\DataAnalysis\DataRetrieve\TestAction.cshtml

    Currently, we're using the default routing

        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Login", action = "Index", id = UrlParameter.Optional }
            );
        }
    

    Which means if I type the URL shown above, it ignores the category and fails to return the view.

    Is there a way to customize routing to get the behavior above?

    --Edit

    To clarify my question based on the comments, here's a screenshot of my solution explorer

    enter image description here

    Now if I call localhost:12346/DataAnalysis/DataRetrieve, this should take me to the index. Routing this isn't a problem as I can do something like this:

            routes.MapRoute(
                name: "ExampleRouting",
                url: "{category}/{controller}/{action}"
            );
    

    But here's the issue. I'd also like to organize my file structure like this:

    enter image description here

    By default, when I try to retrieve the index of dataretrieve, it looks under Views\DataRetrieve\Index not Views\DataAnalysis\DataRetrieve\Index.

    How can I change this behavior?

    Edit2------------------------

    Based on the answer, I've added a custom view engine, registered it in Application_Start, updated my routing. Still having an identical issue.

    Global.asax

    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
    
            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            ViewEngines.Engines.Add(new SPCViewEngine());
    
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            AuthConfig.RegisterAuth();
        }
    }
    

    RouteConfig.cs

        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
    
            routes.MapRoute(
               name: "Default",
               url: "{controller}/{category}/{action}/{id}",
               defaults: new { controller = "Login", action = "Index", category = "Login", id = UrlParameter.Optional }
            );
        }
    

    SPCViewEngine (Custom View Engine)

    public class SPCViewEngine : RazorViewEngine
    {
        public SPCViewEngine()
            : base()
        {
            ViewLocationFormats = new[] {
                "~/Views/{1}/%1/{0}.cshtml",
                "~/Views/{1}/%1/{0}.vbhtml",
                "~/Views/Shared/{0}.cshtml",
                "~/Views/Shared/{0}.vbhtml"
            };
    
             PartialViewLocationFormats = new[] {
                "~/Views/%1/{1}/{0}.cshtml",
                "~/Views/%1/{1}/{0}.vbhtml",
                "~/Views/Shared/{0}.cshtml",
                "~/Views/Shared/{0}.vbhtml"
            };
        }
    
        protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
        {
            var categoryName = controllerContext.RouteData.Values["category"].ToString();
            return base.CreatePartialView(controllerContext, partialPath.Replace("%1", categoryName));
        }
    
        protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
        {
            var categoryName = controllerContext.RouteData.Values["category"].ToString();
            return base.CreateView(controllerContext, viewPath.Replace("%1", categoryName), masterPath);
        }
    
        protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
        {
            var categoryName = controllerContext.RouteData.Values["category"].ToString();
            return base.FileExists(controllerContext, virtualPath.Replace("%1", categoryName));
        }
    }
    
  • TtT23
    TtT23 over 10 years
    Your answer seems like the right way to do it, but for some reason, it's still not working. Requesting localhost/DataAnalysis/DataRetrieve results in 404 and none of the override methods in the customized viewengine are being invoked when I put debug points on them. However, When I try localhost:12346/DataRetrieve/TestAction without the category in front, it works. Edit: Never mind, the latter part doesn't work either.
  • ssilas777
    ssilas777 over 10 years
    Did you register the view engine in application start also update the route config? I tried this in my localhost it works for me also debug point should hit on every request.
  • TtT23
    TtT23 over 10 years
    Yes. I'll edit my question with the updated content momentarily.
  • TtT23
    TtT23 over 10 years
    Wow.. just calling ViewEngines.Engines.Clear() solved the issue.. I guess it was still using the default viewengine. Many thanks!