模型绑定器验证不适用于 IPAddress 类型



我需要在端点发送带有IPAddress的DTO。

public class TestDto
{
public TestDto(IPAddress property)
{
Property = property;
}
public IPAddress Property { get; }
}

我有一个自定义JSON转换器。它可以正常工作。

public sealed class IPAddressConverter : JsonConverter<IPAddress>
{
public override IPAddress Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var ip = reader.GetString();
var ipAddress = IPAddress.Parse(ip!);
return ipAddress;
}
public override void Write(Utf8JsonWriter writer, IPAddress value, JsonSerializerOptions options)
{
var ip = value.ToString();
writer.WriteStringValue(ip);
}
}

不幸的是,默认模型绑定验证器试图获取ScopeId和Address,这会导致异常。

System.Net.Sockets.SocketException (10045): The attempted operation is not supported for the type of object referenced.
at System.Net.IPAddress.get_ScopeId()
at Microsoft.Extensions.Internal.PropertyHelper.CallNullSafePropertyGetter[TDeclaringType,TValue](Func`2 getter, Object target)
at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.DefaultComplexObjectValidationStrategy.Enumerator.<>c__DisplayClass13_1.<MoveNext>b__1()
at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationEntry.get_Model()
at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitChildren(IValidationStrategy strategy)
at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType(IValidationStrategy defaultStrategy)
at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitImplementation(ModelMetadata& metadata, String& key, Object model)
at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model)
at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitChildren(IValidationStrategy strategy)
at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType(IValidationStrategy defaultStrategy)
at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitImplementation(ModelMetadata& metadata, String& key, Object model)
at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model)
at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Validate(ModelMetadata metadata, String key, Object model, Boolean alwaysValidateAtTopLevel, Object container)
at Microsoft.AspNetCore.Mvc.ModelBinding.ObjectModelValidator.Validate(ActionContext actionContext, ValidationStateDictionary validationState, String prefix, Object model, ModelMetadata metadata, Object container)
at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.EnforceBindRequiredAndValidate(ObjectModelValidator baseObjectValidator, ActionContext actionContext, ParameterDescriptor parameter, ModelMetadata metadata, ModelBindingContext modelBindingContext, ModelBindingResult modelBindingResult, Object container)
at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, Object value, Object container)
at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<<CreateBinderDelegate>g__Bind|0>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

如何修复?

这是因为ValidationVisitor将IPAddress类型视为复杂类型,并尝试验证其所有属性。但是,当涉及到IPv4时,ScopeId属性的get方法不可访问,从而导致错误。

所以我们只需要确保ValidationVisitor在验证期间将IPAddress类型视为简单类型以避免此错误。是否认为一个类型是复杂的取决于元数据的IsComplexType属性。

if (Metadata.IsComplexType)
{
return VisitComplexType(DefaultComplexObjectValidationStrategy.Instance);
}

IsComplexType属性的值取决于是否存在从字符串到指定类型的转换器。

IsComplexType = !TypeDescriptor.GetConverter(ModelType).CanConvertFrom(typeof(string));

所以我们只需要为IPAddress添加适当的转换器。

这是我的解决方案:

public class IPAddressDescriptionProvider : TypeDescriptionProvider
{
private static TypeDescriptionProvider defaultTypeProvider = TypeDescriptor.GetProvider(typeof(IPAddress));
public IPAddressDescriptionProvider() : base(defaultTypeProvider)
{
}
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
return new IPAddressDescriptor();
}
}
public class IPAddressDescriptor : ICustomTypeDescriptor
{
public TypeConverter GetConverter()
{
return new IPAddressConverter();
}
//Other methods are not used in this problem and can be implemented with throw new NotImplementedException();
}
public class IPAddressConverter: TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
}

在Startup中添加提供商:

TypeDescriptor.AddProvider(new IPAddressTypeDescriptionProvider(), typeof(IPAddress));

相关内容