• ASP.NET Core中使用表达式树创建URL


    当我们在ASP.NET Core中生成一个action的url会这样写:

    var url=_urlHelper.Action("Index", "Home");
    

    这样的写法存在的问题在于我们传递了两个字符串类型的参数,而我们又无法避免对action和controller做重命名操作, 例如将index重命名为default, 你无法通过IDE在重命名action的过程中,将

    _urlHelper.Action("Index", "Home"); 
    

    重构为

    UrlHelper.Action("Default", "Home");
    

    所以我们的目标是:设计出具有静态检查的API,让IDE提示出这个错误来,甚至是重命名时直接把相关代码都能重命名。

    目标

    设计出类似两组API:

    var url = _urlHelper.Action((HomeController c) => c.Index());
    //期待输出 /home/index
    var link = _urlHelper.Link((ProductController c) => c.Details(10));
    //期待输出 http://locahost/product/details/10
    

    设计API

    根据上面的需求,定义两组API:

    public static string Action<TController>(this IUrlHelper helper, 
    Expression<Action<TController>> action)
    where TController : Controller
    {
       //实现
    }
    
    public static string Link<TController>(this IUrlHelper helper, 
    Expression<Action<TController>> action,
    string protocal = null, string host = null)
    where TController : Controller
    {
       //实现
    }
    

    实现API

    我们实际上最终还是要依赖ASP.NET Core提供的API:

    var link = helper.Action(action: actionName, controller: 
    controllerName, values: routes);
    

    所以问题变成了如何根据(HomeController c) => c.Index()这样的表达式来解析出actionName, ControllerName以及routeValues。

    1. 解析ControllerName

    解析ControllerName比较简单粗暴,因为我们已经从表达式树中得到了HomeController这个类型,直接取Home字符串即可:

    private static string GetControllerName(Type controllerType)
    {
        var controllerName = controllerType.Name.EndsWith("Controller")
            ? controllerType.Name.Substring(0,
            controllerType.Name.Length - "Controller".Length)
            : controllerType.Name;
        return controllerName;
    }
    

    2. 解析ActionName

    由于表达式(HomeController c) => c.Index()是一个MethodCallExpression类型,而Action的名字就是方法名:

    private static MethodCallExpression
    GetMethodCallExpression<TController>(
    Expression<Action<TController>> actionSelector)
    {
        var call = actionSelector.Body as MethodCallExpression;
        if (call == null)
        {
            throw new ArgumentException("You must call a method on " +
            typeof(TController).Name, "actionSelector");
        }
        
        return call;
    }
    
    var methodCallExpression = GetMethodCallExpression(action);
    var actionName = methodCallExpression.Method.Name;
    

    3. 解析RouteValues

    上面两步已经解析出了ControllerName和ActionName,也就是说通过上面的分析已经能完成下面的调用:

    var action = helper.Action(action: "index", controller: "home", values: null);
    //等价于
    var url = _urlHelper.Action((HomeController c) => c.Index());
    //输出 /home/index
    

    但是考虑下面的Action:

    [HttpGet,Route("product/{id}")]
    public IActionResult Details(int id)
    {
       //...
    }
    

    这个Action期待传入一个int类型的id,也就是说你要通过这样的方式来生成url:

    var action = helper.Action(action: "details", controller: 
    "product", values: new { id = 10 });
    

    所以要想让我们的API正常工作,还需要生成一个object类型:new { id = 10 }。而这个object类型里面的属性正好可以来自于表达式树的方法调用参数:

    var action = _urlHelper.Action((ProductController c) => c.Details(10));
    

    要想生成这个匿名对象,需要遍历方法调用表达式的所有参数,分别解析出属性名,例如id; 以及值,例如10。最后再把解析出来的参数字典生成为dynamic类型的对象:
    如何解析表达式树请查看expression-trees

    public class RouteValueExtractor
    {
        public static object GetRouteValues(MethodCallExpression call)
        {
            var routes = new Dictionary<string, object>();
    
            var parameters = call.Method.GetParameters();
            var pairs = call.Arguments.Select((a, i) => new
            {
                Argument = a,
                ParamName = parameters[i].Name
            });
            foreach (var item in pairs)
            {
                string name = item.ParamName;
                object value = GetValue(item.Argument);
                if (value != null)
                {
                    var valueType = value.GetType();
                    if (valueType.IsValueType)
                    {
                        routes.Add(name, value);
                    }
                    else
                    {
                        throw new NotSupportedException("Unsupported parameter type {0}");
                    }
    
                }
            }
    
            return DictionaryToObject(routes);
        }
    
        private static object GetValue(Expression expression)
        {
            if (expression.NodeType == ExpressionType.Constant)
            {
                return ((ConstantExpression) expression).Value;
            }
    
            throw new NotSupportedException("Unsupported parameter expression");
        }
    
        private static dynamic DictionaryToObject(IDictionary<string, object> dictionary)
        {
            var expandoObj = new ExpandoObject();
            var expandoObjCollection = (ICollection<KeyValuePair<string, object>>) expandoObj;
    
            foreach (var keyValuePair in dictionary)
            {
                expandoObjCollection.Add(keyValuePair);
            }
    
            dynamic eoDynamic = expandoObj;
            return eoDynamic;
        }
    }
    

    一个完整的API实现:

    public static string Action<TController>(this IUrlHelper helper, 
    Expression<Action<TController>> action)
    where TController : Controller
    {
        var controllerName = GetControllerName(typeof(TController));
        var methodCallExpression = GetMethodCallExpression(action);
        var actionName = methodCallExpression.Method.Name;
    
        var routes = RouteValueExtractor.GetRouteValues(methodCallExpression);
    
        var link = helper.Action(action: actionName, controller: 
        controllerName, values: routes);
    
        return link;
    }
    
  • 相关阅读:
    C# 动态生成word文档
    C# 利用SharpZipLib生成压缩包
    C# 程序异常关闭时的捕获
    轻松学习UML之用例图,时序图
    轻松学习UML之类图,状态图
    C# 一款属于自己的音乐播放器
    C# MessageBox自动关闭
    C# 用户控件之温度计
    Html富文本编辑器
    java工作流引擎证照库类型的流程设计 实现方案与演示案例
  • 原文地址:https://www.cnblogs.com/xiandnc/p/9746274.html
Copyright © 2020-2023  润新知