Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Differentiate Guid and string Parameters in MVC 3

Using the out-of-the-box method locators in ASP.NET MVC (3 or 4DP), is there a way to have the MVC framework differentiate between a string and Guid without needing to parse the parameter in the controller action?

Usage examples would be for the URL

http://[domain]/customer/details/F325A917-04F4-4562-B104-AF193C41FA78

to execute the

public ActionResult Details(Guid guid)

method, and

http://[domain]/customer/details/bill-gates

to execute the

public ActionResult Details(string id)

method.

With no changes, obviously the methods are ambiguous, as follows:

public ActionResult Details(Guid id)
{
    var model = Context.GetData(id);
    return View(model);
}

public ActionResult Details(string id)
{
    var model = Context.GetData(id);
    return View(model);
}

resulting in the error:

The current request for action 'Details' on controller type 'DataController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult Details(System.Guid) on type Example.Web.Controllers.DataController
System.Web.Mvc.ActionResult Details(System.String) on type Example.Web.Controllers.DataController 

I attempted to use a custom constraint (based on How can I create a route constraint of type System.Guid?) to try and push it through via routing:

routes.MapRoute(
    "Guid",
    "{controller}/{action}/{guid}",
    new { controller = "Home", action = "Index" }, 
    new { guid = new GuidConstraint() }
);

routes.MapRoute(
    "Default", // Route name
    "{controller}/{action}/{id}",
    new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

And switched the action signatures to:

public ActionResult Details(Guid guid)
{
    var model = Context.GetData(guid);
    return View(model);
}

public ActionResult Details(string id)
{
    var model = Context.GetData(id);
    return View(model);
}

The constraint executes and passes, thus the argument is sent to an action, but seemingly still as a string, and thus ambiguous to the two method signatures. I expect that there's something in how the action methods are located that causes the ambiguity, and thus could be overridden by plugging in a custom module to locate methods.

The same result could be achieved by parsing out the string parameter, but would be really nice for brevity to avoid that logic in the action (not to mention to hopefully reuse someday later).

like image 737
falquan Avatar asked Sep 27 '11 02:09

falquan


2 Answers

First, you must disambigute your methods by giving them two different names:

public ActionResult DetailsGuid(Guid guid)
{
    var model = Context.GetData(guid);
    return View(model); 
}

public ActionResult DetailsString(string id)
{
    var model = Context.GetData(id);
    return View(model);
} 

Next, you need a custom route handler to inspect the request, and change the method name accordingly:

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

public class MyRouteHandler : IRouteHandler 
{ 
    public IHttpHandler GetHttpHandler(RequestContext requestContext) 
    { 
        var routeData = requestContext.RouteData; 
        var stringValue = routeData.Values["id"].ToString();
        Guid guidValue; 
        var action = routeData.Values["action"]; 
        if (Guid.TryParse(stringValue, out guidValue) && (guidValue != Guid.Empty);
            routeData.Values["action"] = action + "Guid"; 

        else
            routeData.Values["action"] = action + "String"; 

        var handler = new MvcHandler(requestContext); 
        return handler; 
    } 
} 

Finally, add a Details route at the top of your routes, as follows:

routes.Add("Details", 
    new Route("{controller}/Details/{id}", 
        new RouteValueDictionary( 
            new { controller = "Home", action = "Details" }), 
            new MyRouteHandler()
        )
    ); 
);

When a request comes in for details, the Details route will use your custom route handler to inspect the id token. The route handler adds to the action name based on the form of the id token, so that the request will be directed to the appropriate action.

like image 124
counsellorben Avatar answered Oct 06 '22 11:10

counsellorben


My opinion is that using action method selector is more usable and less coding.

public class GuidMethodSelectorAttribute : ActionMethodSelectorAttribute
{
    public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
    {
        var idStr = controllerContext.RouteData.Values["id"];
        if (idStr == null)
            return false;
        Guid a;
        var result = Guid.TryParse(idStr.ToString(), out a);
        return result;
    }
}

This selector inspects request for ID parameter. If it's guid, it returns true. So, to use it:

public class HomeController : Controller
{
    [GuidMethodSelector]
    public ActionResult Index(Guid id)
    {
        return View();
    }
    public ActionResult Index(string id)
    {
        return View();
    }
}
like image 20
Nickmaovich Avatar answered Oct 06 '22 12:10

Nickmaovich