在MVC 3中区分Guid和字符串参数

8

在ASP.NET MVC(3或4DP)中使用开箱即用的方法定位器,是否有一种方法可以使MVC框架区分字符串和Guid,而无需在控制器操作中解析参数?

使用示例将用于URL

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

来执行

public ActionResult Details(Guid guid)

方法和

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

执行

public ActionResult Details(string id)

方法。

显然,如果没有变化,这些方法是模糊的,如下所示:

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);
}

导致错误的结果:
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 

我尝试使用自定义约束(基于如何创建类型为System.Guid的路由约束?)通过路由来传递它:
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 }
);

并将操作签名切换为:

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);
}

约束执行并通过,因此参数被发送到操作,但似乎仍然是一个字符串,并且对于两个方法签名来说含义不明确。我希望有一些方法可以解决这种歧义,可能是在如何定位操作方法方面存在问题,因此可以通过插入自定义模块来覆盖它。通过解析字符串参数可以实现相同的结果,但为了简洁起见,避免在操作中使用该逻辑(更不用说希望将来重用)会非常好。

你说得对,动作方法定位器对路由约束一无所知。如果你删除了字符串动作方法,会选择GUID动作方法吗? - bzlm
1
MVC不支持仅基于签名的方法重载 - 对您来说最简单的解决方案可能是只需拥有两个唯一命名的操作方法,一个用于按GUID获取详细信息(Details),另一个用于按名称获取详细信息(Search或Information也许?)。 - Tommy
@bzlm - 没错,移除字符串操作将选择Guid(假设它通过约束条件或可以解析为Guid)。 - falquan
3个回答

12

首先,您必须通过给方法命名两个不同的名称来消除歧义:

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);
} 

接下来,您需要一个自定义路由处理程序来检查请求,并相应地更改方法名称:

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; 
    } 
} 

最后,在你的路由顶部添加一个名为Details的路由,如下所示:

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

当请求详情时,Details路由将使用您的自定义路由处理程序来检查id标记。路由处理程序根据id标记的形式添加操作名称,以便将请求定向到适当的操作。


9

我认为使用动作方法选择器更易用且编码量更少。

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;
    }
}

这个选择器检查请求中是否带有ID参数。如果它是GUID,它会返回true。因此,要使用它:
public class HomeController : Controller
{
    [GuidMethodSelector]
    public ActionResult Index(Guid id)
    {
        return View();
    }
    public ActionResult Index(string id)
    {
        return View();
    }
}

1
您可以使用内置的GuidRouteConstraint类,而无需添加自定义代码。我知道在编写时可能不是这种情况。 - Laurence C

8
如果你仍然按照这种方式注册路由,那么在较新版本的MVC中添加了GuidRouteConstraint()类,应该使用它来代替自定义实现。
public override void RegisterArea(AreaRegistrationContext context)
{
    context.MapRoute(
        "Guid",
        "{controller}/{action}/{guid}",
        new { controller = "Home", action = "Index" }, 
        new { guid = new GuidRouteConstraint() }
    );
}

那么你可以简单地创建你的操作结果,如下:

public class HomeController : Controller {
    public ActionResult Index(Guid guid) {
    }
}

4
效果很好,只需确保使用正确的“using”语句 - 您可能需要使用using System.Web.Mvc.Routing.Constraints;而不是System.Web.Http.Routing.Constraints - Zhaph - Ben Duguid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接