动态注册MVC操作的路由

时间:2018-10-12 17:27:38

标签: c# asp.net-mvc reflection routing

我了解为什么存在路由以及如何使用一组非常硬编码的路由会很有用。

但是,我一直处于这种情况下:

 public PartialViewResult GetAssetHistory(string assetCode, string mode)

 public PartialViewResult GetAssetData(string assetCode)

 public PartialViewResult GetAssetData2(string assetCode, string mode, int assetParam)

我不想硬编码一条路线

Asset/{action}/{assetCode}/{mode}/{assetParam}

如果我可以拥有所有路由,或者将所有控制器的属性设置为动态创建路由,使第一个路由值映射到第一个参数(例如assetCode),第二个路由值映射到第一个参数,那就更好了。动作的第二个参数(例如模式),依此类推?

我知道这可能需要反思,但是是否有任何原因导致这样做很糟糕,或者不应该这样做,或者不能完成?

在RouteConfig.cs中这样做的一个奖励点

2 个答案:

答案 0 :(得分:0)

更新

事实证明这是可能的。以下是一个示例,应进行修改以添加站点支持的其余路由。

  • 使用自定义属性来检测应使用哪些操作来创建动态路由

[AttributeUsage(AttributeTargets.Method)]
public class DynamicUrlAttribute : Attribute
{
}

  • 使用自定义属性装饰您的操作

public class SampleController : Controller
{
    [DynamicUrl]
    public ActionResult Index(int param2)
    {
        return View(param2);
    }

    [DynamicUrl]
    public ActionResult MultipleParams(int param1, int param2)
    {
        return View(new { param1, param2 });
    }
}

  • 通过反射生成路线

public static class Extensions
{
    /// <summary>
    /// Detect all the actions whose route should be generated dynamically
    /// </summary>
    /// <returns>List of actions</returns>
    private static IEnumerable<MethodInfo> GetTypesWithHelpAttribute()
    {
        foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            foreach (Type type in assembly.GetTypes())
            {
                foreach (var method in type.GetMethods())
                {
                    if (method.GetCustomAttributes(typeof(DynamicUrlAttribute), 
                                            true).Length > 0)
                    {
                        yield return method;
                    }
                }
            }
        }
    }

    /// <summary>
    /// Get the list of routes to add to the Route Table
    /// </summary>
    /// <returns>List of routes</returns>
    public static List<Route> GetRoutes()
    {
        List<Route> routes = new List<Route>();

        foreach (var action in GetTypesWithHelpAttribute())
        {
            string controllerName = action.DeclaringType.Name.Replace("Controller","");
            string actionName = action.Name;
            List<string> parameters = new List<string>();
            int index = 0;
            foreach (var parameterInfo in action.GetParameters())
            {
                parameters.Add(GetParamName(action, index++));
            }
            string parameterSection = action.GetParameters().Length > 0 ?
                    parameters.Aggregate("", (a, b) => $"{a}/{{{b}}}") 
                    : "";
            string finalRoute = $"dynamic/{controllerName}/{actionName}{parameterSection}";
            routes.Add(new Route(
                url: finalRoute,
                defaults: new RouteValueDictionary( 
                    new { 
                        controller = controllerName, 
                        action = actionName }),
                routeHandler: new MvcRouteHandler()
            ));
        }
        return routes;
    }

    /// <summary>
    /// Return the name of the parameter by using reflection
    /// </summary>
    /// <param name="method">Method information</param>
    /// <param name="index">Parameter index</param>
    /// <returns>Parameter name</returns>
    public static string GetParamName(System.Reflection.MethodInfo method, int index)
    {
        string retVal = string.Empty;

        if (method != null && method.GetParameters().Length > index)
            retVal = method.GetParameters()[index].Name;

        return retVal;
    }
}

  • 将检测到的路线添加到路线集合中

    public static void RegisterRoutes(RouteCollection routes)
    {
        foreach (var route in Extensions.GetRoutes())
        {
            routes.Add(route);
        }
        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }

对于此代码,您将获得以下两个有效的url路由

  1. / dynamic / Sample / Index / 2
  2. / dynamic / Sample / MultipleParams / 1/2

原始(无效)

想象另一种方式。假设我们对此内容有看法

@Url.Action("Action","Controller", 
    new { 
        assetCode = "code", 
        mode = "mode", 
        assetParam = "assetParam", 
        someOtherProperty = "someOtherProperty" 
    })

现在,您要创建哪个URL?

  • /控制器/操作/代码
  • /控制器/操作/代码/模式
  • / Controller / Action / code / mode / assetParam
  • / Controller / Action / code / mode / assetParam / someOtherProperty(甚至更糟,因为甚至不存在)
  • / Controller / Action / someOtherProperty / code / assetParam / mode(更糟糕的是,这可能是可能的网址,因为没有关于该网址生成顺序的任何信息)

定义3条路由时,很明显要创建的框架是URL。如果这是动态的,则框架将无法正确生成有效的对应URL。也就是说,除非您开始使用自定义属性填充控制器和操作,然后再开始执行此操作,否则在实际使用RouteAttribute和/或在路由表中定义实际路由更有意义。

答案 1 :(得分:0)

您可以通过实现IRouteHandler来实现。

  1. 创建自定义IRouteHandler
  2. 自定义IRouteHandler将找出控制器,动作,参数等。
  3. 然后,自定义IRouteHandler将简单地将请求传递给常规MvcHandler。
  4. MvcHandler现在知道哪个控制器和操作将处理请求。

自定义IRouteHandler就像这样:

public sealed class UrlRouteHandler : IRouteHandler
{
    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        var routeData = requestContext.RouteData.Values;
        var url = routeData["urlRouteHandler"] as string;

        // The class UrlHandler will have all the code for figuring things out
        var route = UrlHandler.GetRoute(url);

        routeData["url"] = route.Url;
        routeData["controller"] = route.Controller;
        routeData["action"] = route.Action;
        // other stuff to add

        // Now let MvcHandler process the request.
        return new MvcHandler(requestContext);
    }
}

RouteConfig.cs中注册处理程序:

routes.MapRoute(
            "IUrlRouteHandler",
            "{*urlRouteHandler}").RouteHandler = new UrlRouteHandler();

这个好伙伴已经完成了here的大部分艰苦工作。您可以按照此操作。您要执行的操作与该文章中已完成操作之间的唯一区别是:您想按惯例从URL中查找内容,他正在使用存储在数据库中的URL来解决问题。

该文章的完整源代码位于GitHub上。