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

上一篇文章,我介绍了使用 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/305694.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

公司高层要我转Java 我直接邮件回怼...

2020年艰难而短暂&#xff0c;而互联网的历史车轮还是轰隆隆一路向前&#xff0c;服务网格/云原生/Serverless架构&#xff0c;各种新架构大行其道。重新审视当下主流的编程语言&#xff0c;天下第一的Java已老&#xff0c;下行多年的.NET却浴火重生&#xff0c;焕发生机&#…

.NET斗鱼直播弹幕客户端(2021)

.NET斗鱼直播弹幕客户端(2021)离之前更新的两篇《.NET斗鱼直播弹幕客户端》已经有一段时间&#xff0c;近期有许多客户向我反馈刚好有这方面的需求&#xff0c;但之前的代码不能用了——但网上许多流传的Node.js、Python脚本却可以用&#xff0c;这岂能忍&#xff1f;&#xff…

Exceptionless服务端本地化部署

背景分布式异常日志收集框架Exceptionless是开源的工具&#xff0c;根据官方给出的说明&#xff1a;Exceptionless提供两种使用方式&#xff0c;一种是官网创建账号,需要付费&#xff0c;免费版有限制&#xff1b;一种是自己搭建本地项目&#xff0c;无任何限制。准备安装包准备…

ABP vnext模块化架构的最佳实践的实现

在上一篇文章《手把手教你用Abp vnext构建API接口服务》中&#xff0c;我们用ABP vnext实现了WebAPI接口服务&#xff0c;但是并非ABP模块化架构的最佳实践。我本身也在学习ABP&#xff0c;我认为ABP新手应该从最佳实践开始学习&#xff0c;可以少走很多弯路&#xff0c;所以写…

iphone查看删除的短信_手机资讯:iPhone手机可以批量删除短信吗如何操作

如今使用IT数码设备的小伙伴们是越来越多了&#xff0c;那么IT数码设备当中是有很多知识的&#xff0c;这些知识很多小伙伴一般都是不知道的&#xff0c;就好比最近就有很多小伙伴们想要知道iPhone手机可以批量删除短信吗如何操作&#xff0c;那么既然现在大家对于iPhone手机可…

如何在 C# 8 中使用 模式匹配

模式匹配 是在 C# 7 中引入的一个非常????的特性&#xff0c;你可以在任何类型上使用 模式匹配&#xff0c;甚至是自定义类型&#xff0c;而且在 C# 8 中得到了增强&#xff0c;引入了大量的新模式类型&#xff0c;这篇文章就来讨论如何在 C# 8 中使用模式匹配。C# 8 中的表…

Hadoop 中zoo_0基础如何入门HADOOP

原标题&#xff1a;0基础如何入门HADOOP学习一样东西&#xff0c;肯定先要了解这个东西是什么&#xff0c;那什么是HADOOP呢&#xff1f;我们就来看看什么是HADOOP和如何学习HADOOP及学习内容。一&#xff0c;什么是HADOOPHADOOP是apache旗下的一套开源软件平台HADOOP提供的功能…

.NET 5 程序高级调试-WinDbg

上周和大家分享了.NET 5开源工作流框架elsa&#xff0c;程序跑起来后&#xff0c;想看一下后台线程的执行情况。抓了个进程Dump后&#xff0c;使用WinDbg调试&#xff0c;加载SOS调试器扩展&#xff0c;结果无法正常使用了&#xff1a;0:000> .loadby sos clrUnable to find…

.Net在线编辑工具.NET Fiddle

介绍推荐工具&#xff1a;.NET Fiddle推荐理由&#xff1a;在线调试&#xff0c;编译&#xff0c;运行.net代码&#xff0c;同时支持C#&#xff0c;VB.NET&#xff0c;F#推荐说明&#xff1a;&#xff1a;对于.NET开发者来说是福音&#xff0c;因为我们可以不用再担心环境与庞大…

Typora markdown公式换行等号对齐_下了31个markdown编辑器,我就不信选不出一个好用的...

markdown编辑器测评标准总体标准渲染领域编辑领域数据管理其他TyporaVnoteMwebJoplinZettlrmacdownulyssesMarktextghostwriterfocusedbywordmarkedFarBoxNotablebear(熊掌笔记)iA writerMarxico(马克飞象)JetBrains系列的IDEsublime&#xff08;贫穷&#xff0c;没有插件&…

WSUS专题之二:部署与规划1

部署场景: 我们这里仅讨论和Internet完全物理隔离的企业内网的WSUS部署 Internet断开的WSUS服务器环境 部署WSUS服务时&#xff0c;并不要求你必须连接到Internet。对于没有连接到Internet的网络环境&#xff0c;你一样可以部署WSUS服务。通过在其他连接到Internet上的WSUS服务…

.Net Core in Docker - 使用阿里云Codepipeline及阿里云容器镜像服务实现持续交付/部署(CD)...

上一次演示了如何.Net Core in Docker - 使用阿里云Codepipeline及阿里云容器镜像服务实现持续集成&#xff08;CI&#xff09;&#xff0c;讲到这里我们push一下代码后就自动编译、自动跑单元测试、自动构建镜像、自动推送镜像到私仓。那么离我们最初设定的目标只差那么一小步…

spyder pyecharts不显示_我的显示器需要定时校色吗?

在对图像色彩有要求的领域中&#xff0c;显示器的色彩准确是相当重要的。专业的显示器&#xff0c;能够具有更大的色域&#xff0c;更大的色深&#xff0c;以及更精确的ΔE色准值。这也是一个专业显示器所应有的品质。但是&#xff0c;我们在讨论色彩准确性的同时&#xff0c;往…

外观模式(Façade Pattern)

概述 在软件开发系统中&#xff0c;客户程序经常会与复杂系统的内部子系统之间产生耦合&#xff0c;而导致客户程序随着子系统的变化而变化。那么如何简化客户程序与子系统之间的交互接口&#xff1f;如何将复杂系统的内部子系统与客户程序之间的依赖解耦&#xff1f;这就是要说…

WTM5.0发布,全面支持.net5

点击上方蓝字关注我们WTM5.0全面支持.net5WTM5.0是WTM框架开源2年以来最大的一次升级&#xff0c;全面支持.net5&#xff0c;大幅重构了底层代码&#xff0c;针对广大用户提出的封装过度&#xff0c;不够灵活&#xff0c;性能不高等问题进行了彻底的修改。这次升级使WTM继续保持…

rsa 模数 指数转换 c语言_模数转换,你必须知道的8个经典ADC转换电路方案

模数转换器即A/D转换器&#xff0c;或简称ADC&#xff0c;通常是指一个将模拟信号转变为数字信号的电子元件。通常的模数转换器是将一个输入电压信号转换为一个输出的数字信号。由于数字信号本身不具有实际意义&#xff0c;仅仅表示一个相对大小。故任何一个模数转换器都需要一…

linux定时关机命令_win10电脑定时关机命令

电脑定时关机命令可以帮助用户们很好的去设置电脑自动关机等&#xff0c;自己无需操作&#xff0c;电脑也会在对应的时间自动关机&#xff0c;使用起来还是非常方便的&#xff0c;现在就来看看电脑定时关机命令教程吧~电脑定时关机命令是什么&#xff1a;一、CMD设置关机1、点击…

为你的项目启用可空引用类型

为你的项目启用可空引用类型IntroC# 从 8.0 开始引入了可空引用类型&#xff0c;我们可以为项目启用可空引用类型来借助编译器来帮助我们更好的处理代码中的空引用的处理&#xff0c;可以避免我们写很多不必要 null 检查&#xff0c;提高我们的效率Why为什么我们要启用可空引用…

有哪些编辑软件可以编辑c语言,可以推荐一个手机上最好用且免费的c语言编辑器吗?...

C4droid(又名C编译器)呗&#xff0c;一个既可以编辑&#xff0c;还可以运行C语言的手机编程软件&#xff0c;下面我简单介绍一下这个软件的安装和使用&#xff1a;1.首先&#xff0c;安装C4droid&#xff0c;这个直接在手机应用中搜索就行&#xff0c;如下&#xff0c;大概也就…

cas 4.2.7 官方手册_海城市地区,保险手册核验的简单流程

最近海城市社保正在进行保险手册的核验工作&#xff0c;据说是要将当地社保数据并网&#xff0c;由省社保机构监督管理。我们这个百万人口的县级市&#xff0c;核验工作只由一个部门在固定的办事大厅里完成&#xff0c;工作量也是相当大了。核验工作自9月末开始&#xff0c;已进…