4

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
  • 6,876
  • 34
  • 103
  • 174
  • So DataAnalysis is your Controller, or is that the name of your project, and DataRetrieve is your Controller? – krillgar Jan 27 '14 at 01:07
  • I'm confused, or maybe you are. Routing has nothing to do with views. Routing only routes to controller actions. – Erik Funkenbusch Jan 27 '14 at 03:17
  • 1
    I would suggest organizing your application using areas. See http://msdn.microsoft.com/en-us/library/ee671793(v=vs.100).aspx for this. – Onots Jan 27 '14 at 05:18
  • @Onots Nearly all of the pages will be using a same shared layout file and frankly, we thought reconfiguring their shared layout settings (Including all child renderaction properties) under areas would be pretty hectic, unless if we are wrong and there is an easy way around this. – TtT23 Jan 27 '14 at 06:24
  • @l46kok I hope you could use the Area _viewStart to reference the same (shared) root layout settings. Haven't had to deal with this personally (yet) though. That's why I'm commenting and not answering :D – Onots Jan 27 '14 at 07:23

1 Answers1

14

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 }
        );
ssilas777
  • 9,672
  • 4
  • 45
  • 68
  • 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. – TtT23 Jan 27 '14 at 08:33
  • 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. – ssilas777 Jan 27 '14 at 08:36
  • Yes. I'll edit my question with the updated content momentarily. – TtT23 Jan 27 '14 at 08:40
  • Wow.. just calling ViewEngines.Engines.Clear() solved the issue.. I guess it was still using the default viewengine. Many thanks! – TtT23 Jan 27 '14 at 08:54