上一篇文章,我介绍了使用 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 ActionResult<Product> 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 ConcurrentDictionary<Type, Delegate> StronglyTypedIdFactories = new();public static Func<TValue, object> GetFactory<TValue>(Type stronglyTypedIdType)where TValue : notnull{return (Func<TValue, object>)StronglyTypedIdFactories.GetOrAdd(stronglyTypedIdType,CreateFactory<TValue>);}private static Func<TValue, object> CreateFactory<TValue>(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}' doesn't 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.Lambda<Func<TValue, 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 StronglyTypedIdConverter<TValue> : 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)}' doesn't 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.GetFactory<TValue>(_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 = (StronglyTypedId<TValue>)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);}
}

然后再创建一个非泛型的 Converter

public class StronglyTypedIdConverter : TypeConverter
{private static readonly ConcurrentDictionary<Type, 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 StronglyTypedId<TValue>(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

使用 C# 9 的records作为强类型ID - 路由和查询参数相关推荐

  1. 使用 C# 9 的records作为强类型ID - JSON序列化

    使用 C# 9 的records作为强类型ID - 路由和查询参数 在本系列的上一篇文章中使用 C# 9 的records作为强类型ID - 路由和查询参数,我们注意到强类型ID的实体,序列化为 JS ...

  2. 使用 C# 9 的records作为强类型ID - 初次使用

    强类型ID 实体通常是整数,GUID或者string类型,因为数据库直接支持这些类型,但是,如果实体的ID的类型是一样的,比如都是整数的ID,这有可能会出现ID值传错的问题,看下边的示例. publi ...

  3. 数据库分库分表和带来的唯一ID、分页查询问题的解决

    数据库分库分表和带来的唯一ID.分页查询问题的解决 参考文章: (1)数据库分库分表和带来的唯一ID.分页查询问题的解决 (2)https://www.cnblogs.com/hanzhong/p/1 ...

  4. php join a.id b.id,mysql,sql_MySQL A left join B on B.cid=A.id 左链接查询失败,求解,mysql,sql - phpStudy...

    MySQL A left join B on B.cid=A.id 左链接查询失败,求解 mysql> desc fb_category; +-------+-------------+---- ...

  5. vue怎么根据id获取组件_vue子组件,如何根据父组件传进来的id,去查询详情,并在子组件中显示?...

    如果我在组件的created方法里面,根据id去后台查询详情,可以正常显示,不报错,但是当父组件id值改变后,并不会再次去后台查询了, ,假如我后台返回的对象时detail,如果写在computed里 ...

  6. Candence中查看MOS管阈值电压Vth、Vgs、Vds、跨导gm、Id等详细MOS参数的方法

    Candence中查看MOS管阈值电压Vth.Vgs.Vds.gm.Id等详细MOS参数的方法 ADE仿真结束后,点击工具栏Results→ Print→ Transient Operating Po ...

  7. 淘宝产品ID在哪儿查询?

    淘宝产品ID在哪儿查询?这种全是开淘宝网店的基本知识,可是一部分的初学者商家还不掌握这种,下边看来下什么是淘宝店产品ID?淘宝产品id在哪儿查询? 什么是淘宝店产品ID? 商品ID即淘宝产品序号,商品 ...

  8. MySQL 一个字段,用’,‘隔开,存储多个id,关联查询

    MySQL 一个字段,用','隔开,存储多个id,关联查询 因为朋友过来问我,一个商品表的一个颜色的关联字段,里面放着多个颜色的id,这些id用逗号隔开,然后想要查出一条商品记录上显示出所有颜色.实现 ...

  9. 查看计算机id和密码,ID号可以查询很多信息,并迅速去当地更改密码

    照片由CFP提供 在广州市地方网站上输入您的号码以查询个人所得税和其他信息 访问广州市地方网站更改您的个人密码!昨天,有关网上税务查询的微博很快成为热点. 广州市地税局网站查询系统存在漏洞. 只要您具 ...

最新文章

  1. linux学习——大话linux网络
  2. 网站服务器的解决方案有,Web网站服务器DDOS攻击的解决方案
  3. 监控利器nagios
  4. 有关linux下redis overcommit_memory的问题,以及导致的:Cannot allocate memory问题
  5. AdapterView及其子类之二:使用ListActivity及ArrayAdapter创建列表
  6. org.hibernate.PersistentObjectException: detached entity passed to persist
  7. 直接请求接口_http类型的post和get接口测试
  8. Hadoop源生实用工具之distcp
  9. java socket 线程池_程序员:java使用线程池和TCP实现简单多轮聊天系统
  10. 一个简单的Android客户端从服务器端获取json数据并解析的实现代码
  11. [ 2022年4月8日更新 ]Typecho Handsome主题美化教程
  12. php api查询开发,PHP 开发API接口 登记,登录,查询用户资料
  13. lingo数学软件完整教程
  14. 2.9 使用快捷键快速隐藏和显示行列 [原创Excel教程]
  15. 显控触摸屏编程手册_深圳显控AKWORKSHOP触摸屏与ALLENBRADLEY通讯手册.pdf
  16. Mac怎么安装Andriod模拟器
  17. 华为USG防火墙搭建IPsec***实战
  18. 二手交易APP开发主要功能有哪些?(二)
  19. 发布一个JINI服务
  20. 加壳工具推荐-无需编程自动化加壳

热门文章

  1. Android 电量优化
  2. (转)yi_meng linux 下 ifcfg-eth0 配置 以及ifconfig、ifup、ifdown区别
  3. Html5学习笔记1 元素 标签 属性
  4. Mealy状态机的一点理解
  5. 公布一个软件,轻新视频录播程序,H264/AAC录制视音频,保存FLV,支持RTMP直播...
  6. 数组的几种排序算法的实现(1)
  7. robocopy帮助
  8. 算法的力量(李开复)
  9. MacBook刷机勘错篇
  10. 如何使用卡巴斯基急救盘清理感染的PC