tags:

views:

361

answers:

3

I would like to learn how setup prefix routing like there is in cakephp for admin routing. Unfortunately I have not figured out how to do this in ASP.NET MVC.

For example,

If the user types in /Admin/Management, I would like controller Management.admin_index to be called, without changing the url. I tried what I thought was correct but it ended up changing all my urls.

So the question is: If it is possible, how do I do prefix routing in ASP.NET MVC?

Update: This is exactly what I was looking for. http://devlicio.us/blogs/billy%5Fmccafferty/archive/2009/01/22/mvc-quot-areas-quot-as-hierarchical-subfolders-under-views.aspx This code allows you to have a default website, and then an admin area.

A: 

I think maybe setup a route for it?

With this example you would need to create a route per (admin) controller.

routes.MapRoute(null,
    "Admin/{controller}/{action}/",
    new { controller = "Management", action = "Index" });
jeef3
I'm still pretty new also, but I think this could be done tidier.
jeef3
Thanks for the answer. What I think I want is if the user types in Admin\Management\Index my request gets routed to Management:admin_index() which is what prefix routing is...but I don't know how it is possible in ASP.NET MVC.
Jon Price
For that, you can create an Action Method (public method that returns an ActionResult) called `admin_index` and preppend the attribute `[ActionName("Index")]`It's not quite prefix routing but should give the desired result?
jeef3
Ok, I'll try that and let you know the results.
Jon Price
A: 

Maybe you should try writing a custom route. Look at System.Web.Routing.RouteBase.

* edit *

Ok, I had another look and I was wrong. What was initially my first thought turned out to be the easier solution: you should implement a custom IRouteHandler like this:

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

namespace PrefixRoutingTest.Util
{
    public class PrefixRouteHandler : IRouteHandler
    {
        public IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            //only apply this behavior to a part of the site
            if (requestContext.HttpContext.Request.Path.ToLower().StartsWith("/prefixed/") &&
                requestContext.HttpContext.User.Identity.IsAuthenticated)
            {
                //prefix the action name with Admin
                var action = requestContext.RouteData.Values["action"].ToString();
                requestContext.RouteData.Values["action"] = "Admin" + action;
            }

            return new MvcHandler(requestContext);
        }
    }
}

Then, make a controller with two actions, prefix one of them with 'Admin', and mark it with the AuthorizeAttribute. It should look something like this:

using System.Web.Mvc;

namespace PrefixRoutingTest.Controllers
{
    public class PrefixedController : Controller
    {
        public ActionResult Test()
        {
            return View();
        }

        [Authorize]
        public ActionResult AdminTest()
        {
            return View();
        }

    }
}

Make a 'Prefixed' folder in the Views folder and add two Views in it, a Test.aspx and a AdminTest.aspx. Then you just need to replace the default route (in Global.asax) with this code:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.Add("Default", new Route("{controller}/{action}/{id}", new PrefixRouteHandler())
    {
        Defaults = new RouteValueDictionary(new { controller = "Home", action = "Index", id = "" })
    });
}

And that's it, when you now navigate to site/Prefixed/Test as an anonymous user you should get the standard Test view, but if you are an admin you will get the AdminTest view. If an anonymous user, however, tries to access AdminTest url directly he will be sent to the login page.

PS: I can confirm this works, sou if you have problems implementing it just give me a ring ;)

Adis H
There's not enough info to know if this is a good answer. I specifically ask for how to do prefix routing in ASP.NET MVC. How does looking at base class help me?
Jon Price
Ahh ok, this looks like what I'm after. I'll try it later today and get back to you.
Jon Price
A: 

I also have been searching how to do this and just figured it out.

Here is the code in VB (it shouldn't be hard to translate to c#)


Create a class implementing IRouteHandler:

Imports System.Web
Imports System.Web.Mvc
Imports System.Web.Routing

Namespace Util.Routing
    ''' <summary>
    ''' This Route Handler looks for a 'prefix' part in the url. If found, it appends the prefix to the action name.
    ''' Example: /prefix/controller/action
    '''   Turns into: /controller/prefix_action
    ''' </summary>
    ''' <remarks></remarks>
    Public Class PrefixRouteHandler
        Implements IRouteHandler

        Public Function GetHttpHandler(ByVal requestContext As System.Web.Routing.RequestContext) As System.Web.IHttpHandler Implements System.Web.Routing.IRouteHandler.GetHttpHandler
            Dim prefix = requestContext.RouteData.Values("prefix")
            If (prefix IsNot Nothing) Then
                ' If prefix actually exists in the beginning of the URL
                If (requestContext.HttpContext.Request.Path.ToLower().StartsWith("/" & prefix.ToString.ToLower() & "/")) Then
                    Dim action = requestContext.RouteData.Values("action")
                    If action Is Nothing Then
                        action = "index"
                    End If
                    requestContext.RouteData.Values("action") = prefix & "_" & action
                End If
            End If
            Return New MvcHandler(requestContext)
        End Function
    End Class
End Namespace


Next, add this to your global.asax.vb:

Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}")
    routes.IgnoreRoute("{*favicon}", New With {.favicon = "(.*/)?favicon.ico(/.*)?"})
    routes.IgnoreRoute("{resource}.aspx/{*pathInfo}")
    ' MapRoute takes the following parameters, in order:
    ' (1) Route name
    ' (2) URL with parameters
    ' (3) Parameter defaults
    routes.Add(New Route("admin/{controller}/{action}/{id}", _
                    New RouteValueDictionary(New With {.prefix = "Admin", .controller = "Home", .action = "Index", .id = ""}), _
                    New PrefixRouteHandler() _
               ) _
    )
    routes.Add(New Route("manager/{controller}/{action}/{id}", _
                    New RouteValueDictionary(New With {.prefix = "Manager", .controller = "Home", .action = "Index", .id = ""}), _
                    New PrefixRouteHandler() _
               ) _
    )
    routes.MapRoute("Default", _
        "{controller}/{action}/{id}", _
        New With {.controller = "Home", .action = "Index", .id = ""} _
    )

End Sub


You will notice that there are two prefixed routes: admin/controller/action, and manager/controller/action. You can add as many as you want.

--Edit-- Initially that seemed to work until I tried using Html.Actionlink to create links. This causes ActionLink to prepend admin/ to every generated url. So, I corrected it. This should cause the prefix routing to work vary similarly to CakePHP's admin routing.

here is the edit:

Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}")
    routes.IgnoreRoute("{*favicon}", New With {.favicon = "(.*/)?favicon.ico(/.*)?"})
    routes.IgnoreRoute("{resource}.aspx/{*pathInfo}")

    routes.Add("AdminPrefix", _
                    New Route("{prefix}/{controller}/{action}/{id}", _
                    New RouteValueDictionary(New With {.controller = "Home", .action = "Index", .id = "", .prefix = ""}), _
                    New RouteValueDictionary(New With {.prefix = "admin"}), _
                    New PrefixRouteHandler()))
    routes.Add("ManagerPrefix", _
                    New Route("{prefix}/{controller}/{action}/{id}", _
                    New RouteValueDictionary(New With {.controller = "Home", .action = "Index", .id = "", .prefix = ""}), _
                    New RouteValueDictionary(New With {.prefix = "manager"}), _
                    New PrefixRouteHandler()))
    routes.MapRoute("Default", _
        "{controller}/{action}/{id}", _
        New With {.controller = "Home", .action = "Index", .id = ""} _
    )

End Sub
demersus