【问题标题】:ASP.NET Core UrlHelper customize parameters serializationASP.NET Core UrlHelper 自定义参数序列化
【发布时间】:2021-03-06 02:00:26
【问题描述】:

我想为 ASP.NET Core 中的操作构建 url。有一种方便的方法可以做到这一点 - 使用IUrlHelper 接口,它可以通过Url 属性在任何控制器中访问。不幸的是,UrlHelper 使用ToString() 方法进行参数序列化,我不知道如何为特定类型自定义序列化。这会导致请求模型中的自定义类型出现问题:

public class ComplexFilter {
    public string A { get; set; }
    public string B { get; set; }
    public override string ToString() {
        return $"[A = {A}, B = {B}]";
    }
}
public class WebRequest {
    [FromRoute]
    public string Id { get;set; }
    [FromQuery]
    public ComplexFilter Filter { get;set; }
}

我想使用 WebRequest 类作为我的控制器操作的参数,其 url 结构类似于 search/{id}?filter=A-B:

[HttpGet("search/{id}")]
public ActionResult Search(WebRequest request) {
    return Ok();
}

我不想覆盖我的ComplexFilterToString() 方法,因为它用于应用程序的其他部分,并且具有更友好的日志记录和调试格式。 在这种情况下有两个问题:

  1. 我需要从请求中反序列化ComplexFilter。这可以在TypeDescriptor 的帮助下完成:TypeDescriptor.AddAttributes(typeof(ComplexFilter), new TypeConverterAttribute(typeof(ComplexFilterTypeConverter)));
  2. 我需要序列化ComplexFilter 以生成链接。如果我只是调用Url.Action("Search", new WebRequest { Id = "id", Filter = new ComplexFilter { A = "A", B = "B" } }),那么我会得到以下网址:/action/id?filter=[A = \"A\", B = \"B\"],但我想得到/action/id?filter=A-B

所以我的问题是 - 如何自定义 UrlBuilder 参数序列化行为,为 CustomFilter 类添加自定义序列化逻辑?

【问题讨论】:

    标签: c# asp.net-core


    【解决方案1】:

    默认的UrlHelper(实现IUrlHelper)会将您的值对象转换为键值对列表,其中ToString() 将用于转换属性值。我怀疑在内部它将使用RouteValueDictionary 转换值对象,然后将链接生成委托给一些内部LinkGeneratorasp.net core 中使用的新概念)。我没有参考源代码,所以这只是猜测。基本上没有其他意义让它不使用ToString(),而是使用其他一些来解析属性值。所以你可以只依赖一个自定义的UrlHelper

    以下解决方案需要您做两件事:

    • 将值对象转换为IDictionary<string,string>,您可以在其中使用您选择的方法来转换属性值,而不是使用默认的ToString()
    • 创建自定义UrlHelper 以覆盖Action 方法以在调用基本方法之前应用上述自定义转换。为方便起见,这是可选的。否则,您需要调用一个方法来应用上述转换,然后再将其传递给默认的UrlHelper.Action

    为了帮助识别哪种类型支持自定义方法以在您想要的逻辑中生成字符串值,您可以定义必须由该类型实现的特定接口。我将该接口命名为ILinkGeneratedString。完整代码如下:

    public interface ILinkGeneratedString
    {
        string LinkGeneratedString { get; }
    }
    
    //an extension method used to convert property value to a string
    public static class UrlValuesExtensions
    {
        public static object ToUrlValues(this object o)
        {
            if (o == null || o is RouteValueDictionary) return o;
            var values = new Dictionary<string, string>();
            //only public readable properties are read
            //this excludes the indexers (a special type of property) as well
            foreach(var prop in o.GetType().GetProperties()
                                           .Where(p => p.CanRead && p.GetIndexParameters().Length == 0))
            {
                var pv = prop.GetValue(o);
                var value = pv == null ? "" :
                            pv is ILinkGeneratedString ls ? ls.LinkGeneratedString : pv.ToString();
                values[prop.Name] = value;
            }
            return values;
        }
    }
    
    //the custom UrlHelper
    public class CustomUrlHelper : UrlHelper
    {
        public CustomUrlHelper(ActionContext actionContext) : base(actionContext)
        {
        }
        public override string Action(UrlActionContext actionContext)
        {            
            //apply the conversion here
            actionContext.Values = actionContext?.Values?.ToUrlValues();
            return base.Action(actionContext);
        }
    }
    
    //we need a custom IUrlHelperFactory as well
    public class CustomUrlHelperFactory : IUrlHelperFactory
    {
        public IUrlHelper GetUrlHelper(ActionContext context)
        {
            return new CustomUrlHelper(context);
        }
    }
    

    现在在Startup.ConfigureServices,你需要像这样注册自定义IUrlHelperFactory

    services.AddSingleton<IUrlHelperFactory, CustomUrlHelperFactory>();
    

    要为您的ComplexFilter 使用自定义ToString() 来生成链接,您需要像这样实现ILinkGeneratedString(正如我之前提到的):

    public class ComplexFilter : ILinkGeneratedString {
        public string A { get; set; }
        public string B { get; set; }
        public override string ToString() {
          return $"[A = {A}, B = {B}]";
        }
        public string LinkGeneratedString => $"{A}-{B}";
    }
    

    对于需要类似自定义链接生成的任何其他类型,您只需实现 ILinkGeneratedString。这样就很方便了。

    【讨论】:

    • 感谢您的回答。我决定使用更简单的方法并引入facade dto - WebComplexFilter,它具有必要的ToString() 运算符和一对隐式转换运算符,用于从/到ComplexFilter 的转换。
    • 另外,我很惊讶ASP NET Core 在这种情况下无法从外部配置序列化行为。
    • 不客气,我认为我在这里介绍的方法基本上是如何扩展它。默认实现使用 ToString 但在您的情况下不可用,因此您需要另一种方法(LinkGeneratedString 在我的实现中)。添加这样的自定义 UrlHelper 后,它的工作方式与默认 UrlHelper 一样,但具有更多逻辑来处理通过路由值公开的任何接口。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-12
    • 2020-07-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多