日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > C# >内容正文

C#

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

發布時間:2023/12/4 C# 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用 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

總結

以上是生活随笔為你收集整理的使用 C# 9 的records作为强类型ID - 路由和查询参数的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。