我想在ASP中构建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();
}
我不想覆盖ComplexFilter
的ToString()
方法,因为它在应用程序的其他部分使用,并且具有更友好的日志记录和调试格式。在这种情况下有两个问题:
- 我需要从请求反序列化
ComplexFilter
。这可以在TypeDescriptor
的帮助下完成:TypeDescriptor.AddAttributes(typeof(ComplexFilter), new TypeConverterAttribute(typeof(ComplexFilterTypeConverter)));
- 我需要序列化
ComplexFilter
链接生成。如果我简单地调用Url.Action("Search", new WebRequest { Id = "id", Filter = new ComplexFilter { A = "A", B = "B" } })
,那么我得到以下url:/action/id?filter=[A = "A", B = "B"]
,但我想得到/action/id?filter=A-B
。
所以我的问题是-我如何自定义UrlBuilder参数序列化行为,为CustomFilter
类添加自定义序列化逻辑?
默认的UrlHelper
(实现IUrlHelper
)将把您的值对象转换为键值对列表,其中ToString()
将用于转换属性值。我怀疑在将链接生成委托给某些内部LinkGenerator
(asp.net core
中使用的新概念)之前,它会在内部使用RouteValueDictionary
来转换值对象。我没有参考源代码,所以这只是猜测。基本上没有其他的点使它不使用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
。那样很方便。