网站的栏目是什么,wordpress wp_head(),seo推广优化外包价格,深圳企业有哪些上一篇文章#xff0c;我介绍了使用 C# 9 的record类型作为强类型id#xff0c;非常简洁public record ProductId(int Value);但是在强类型id真正可用之前#xff0c;还有一些问题需要解决#xff0c;比如#xff0c;ASP.NET Core并不知道如何在路由参数或查询字符串参数中… 上一篇文章我介绍了使用 C# 9 的record类型作为强类型id非常简洁public record ProductId(int Value);
但是在强类型id真正可用之前还有一些问题需要解决比如ASP.NET Core并不知道如何在路由参数或查询字符串参数中正确的处理它们在这篇文章中我将展示如何解决这个问题。路由和查询字符串参数的模型绑定假设我们有一个这样的实体public record ProductId(int Value);public class Product
{public ProductId Id { get; set; }public string Name { get; set; }public decimal UnitPrice { get; set; }
}
和这样的API接口[ApiController]
[Route(api/[controller])]
public class ProductController : ControllerBase
{...[HttpGet({id})]public ActionResultProduct GetProduct(ProductId id){return Ok(new Product { Id id,Name Apple,UnitPrice 0.8M });}
}
现在我们尝试用Get方式访问这个接口 /api/product/1{type: https://tools.ietf.org/html/rfc7231#p-6.5.13,title: Unsupported Media Type,status: 415,traceId: 00-3600640f4e053b43b5ccefabe7eebd5a-159f5ca18d189142-00
}
现在问题就来了返回了415.NET Core 不知道怎么把URL的参数转换为ProductId由于它不是int是我们定义的强类型ID并且没有关联的类型转换器。实现类型转换器这里的解决方案是为实现一个类型转换器ProductId很简单public class ProductIdConverter : TypeConverter
{public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) sourceType typeof(string);public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) destinationType typeof(string);public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value){return value switch{string s new ProductId(int.Parse(s)),null null,_ throw new ArgumentException($Cannot convert from {value} to ProductId, nameof(value))};}public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType){if (destinationType typeof(string)){return value switch{ProductId id id.Value.ToString(),null null,_ throw new ArgumentException($Cannot convert {value} to string, nameof(value))};}throw new ArgumentException($Cannot convert {value ?? (null)} to {destinationType}, nameof(destinationType));}
}
请注意为简洁起见我只处理并转换string,在实际情况下我们可能还希望支持转换int我们的ProductId使用TypeConverter特性将该转换器与记录相关联[TypeConverter(typeof(ProductIdConverter))]
public record ProductId(int Value);
现在让我们尝试再次访问这个接口{id: {value: 1},name: Apple,unitPrice: 0.8
}
现在是返回了但是还有点问题id 在json中显示了一个对象如何在json中处理是我们下一篇文章给大家介绍的现在还有一点是我上面写了一个ProductId的转换器但是如果我们的类型足够多那也有很多工作量所以需要一个公共的通用转换器。通用强类型id转换器首先让我们创建一个Helper•检查类型是否为强类型ID并获取值的类型•获取值得类型创建并缓存一个委托public static class StronglyTypedIdHelper
{private static readonly ConcurrentDictionaryType, Delegate StronglyTypedIdFactories new();public static FuncTValue, object GetFactoryTValue(Type stronglyTypedIdType)where TValue : notnull{return (FuncTValue, object)StronglyTypedIdFactories.GetOrAdd(stronglyTypedIdType,CreateFactoryTValue);}private static FuncTValue, object CreateFactoryTValue(Type stronglyTypedIdType)where TValue : notnull{if (!IsStronglyTypedId(stronglyTypedIdType))throw new ArgumentException($Type {stronglyTypedIdType} is not a strongly-typed id type, nameof(stronglyTypedIdType));var ctor stronglyTypedIdType.GetConstructor(new[] { typeof(TValue) });if (ctor is null)throw new ArgumentException($Type {stronglyTypedIdType} doesnt have a constructor with one parameter of type {typeof(TValue)}, nameof(stronglyTypedIdType));var param Expression.Parameter(typeof(TValue), value);var body Expression.New(ctor, param);var lambda Expression.LambdaFuncTValue, object(body, param);return lambda.Compile();}public static bool IsStronglyTypedId(Type type) IsStronglyTypedId(type, out _);public static bool IsStronglyTypedId(Type type, [NotNullWhen(true)] out Type idType){if (type is null)throw new ArgumentNullException(nameof(type));if (type.BaseType is Type baseType baseType.IsGenericType baseType.GetGenericTypeDefinition() typeof(StronglyTypedId)){idType baseType.GetGenericArguments()[0];return true;}idType null;return false;}
}
这个 Helper 帮助我们编写类型转换器现在我们可以编写通用转换器了。public class StronglyTypedIdConverterTValue : TypeConverterwhere TValue : notnull
{private static readonly TypeConverter IdValueConverter GetIdValueConverter();private static TypeConverter GetIdValueConverter(){var converter TypeDescriptor.GetConverter(typeof(TValue));if (!converter.CanConvertFrom(typeof(string)))throw new InvalidOperationException($Type {typeof(TValue)} doesnt have a converter that can convert from string);return converter;}private readonly Type _type;public StronglyTypedIdConverter(Type type){_type type;}public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType){return sourceType typeof(string)|| sourceType typeof(TValue)|| base.CanConvertFrom(context, sourceType);}public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType){return destinationType typeof(string)|| destinationType typeof(TValue)|| base.CanConvertTo(context, destinationType);}public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value){if (value is string s){value IdValueConverter.ConvertFrom(s);}if (value is TValue idValue){var factory StronglyTypedIdHelper.GetFactoryTValue(_type);return factory(idValue);}return base.ConvertFrom(context, culture, value);}public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType){if (value is null)throw new ArgumentNullException(nameof(value));var stronglyTypedId (StronglyTypedIdTValue)value;TValue idValue stronglyTypedId.Value;if (destinationType typeof(string))return idValue.ToString()!;if (destinationType typeof(TValue))return idValue;return base.ConvertTo(context, culture, value, destinationType);}
}
然后再创建一个非泛型的 Converterpublic class StronglyTypedIdConverter : TypeConverter
{private static readonly ConcurrentDictionaryType, TypeConverter ActualConverters new();private readonly TypeConverter _innerConverter;public StronglyTypedIdConverter(Type stronglyTypedIdType){_innerConverter ActualConverters.GetOrAdd(stronglyTypedIdType, CreateActualConverter);}public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) _innerConverter.CanConvertFrom(context, sourceType);public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) _innerConverter.CanConvertTo(context, destinationType);public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) _innerConverter.ConvertFrom(context, culture, value);public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) _innerConverter.ConvertTo(context, culture, value, destinationType);private static TypeConverter CreateActualConverter(Type stronglyTypedIdType){if (!StronglyTypedIdHelper.IsStronglyTypedId(stronglyTypedIdType, out var idType))throw new InvalidOperationException($The type {stronglyTypedIdType} is not a strongly typed id);var actualConverterType typeof(StronglyTypedIdConverter).MakeGenericType(idType);return (TypeConverter)Activator.CreateInstance(actualConverterType, stronglyTypedIdType)!;}
}
到这里我们可以直接删除之前的 ProductIdConvert 现在有一个通用的可以使用现在.NET Core 的路由匹配已经没有问题了接下来的文章我会介绍如何处理在JSON中出现的问题。[TypeConverter(typeof(StronglyTypedIdConverter))]
public abstract record StronglyTypedIdTValue(TValue Value)where TValue : notnull
{public override string ToString() Value.ToString();
}
原文作者: thomas levesque 原文链接https://thomaslevesque.com/2020/11/23/csharp-9-records-as-strongly-typed-ids-part-2-aspnet-core-route-and-query-parameters/最后欢迎扫码关注我们的公众号 【全球技术精选】专注国外优秀博客的翻译和开源项目分享也可以添加QQ群 897216102