27
loading...
This website collects cookies to deliver better user experience
public record struct SomeWrapperType(int Value)
{
public int Value { get; } = Value;
public static bool TryParse(string? value, out SomeWrapperType result)
{
if (int.TryParse(value, out var parsed))
{
result = new SomeWrapperType(parsed);
return true;
}
result = default;
return false;
}
}
int
(hence the name 🙂). In this case, I don’t have specific logic, just wanted to encapsulate the int
value as a detail, so I can pass SomeWrapperType
around the application.TryParse
method to help with creating an instance of the type from a string
(similar idea to int.TryParse
or Guid.TryParse
).public class SomeWrapperTypeModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var modelName = bindingContext.ModelName;
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
var value = valueProviderResult.FirstValue;
if (bindingContext.ModelType == typeof(SomeWrapperType)
&& SomeWrapperType.TryParse(value, out var result))
{
bindingContext.Result = ModelBindingResult.Success(result);
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}
if
, where it’s using the SomeWrapperType.TryParse
to try to get an instance of the type from the provided string
.public class SomeWrapperTypeModelBinderProvider : IModelBinderProvider
{
public IModelBinder? GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (typeof(SomeWrapperType).IsAssignableFrom(context.Metadata.ModelType))
{
return new BinderTypeModelBinder(typeof(SomeWrapperTypeModelBinder));
}
return null;
}
}
builder.Services.AddControllers(options =>
{
options.ModelBinderProviders.Add(new SomeWrapperTypeModelBinderProvider());
});
[HttpGet("basic")]
public IActionResult GetWithNoCustomization(SomeWrapperType wrapper)
=> Ok(wrapper.Value);
SomeWrapperType
in there, further showing that it’s being treated as a complex type.builder.Services.AddSwaggerGen(options =>
{
// ...
options.MapType<SomeWrapperType>(() => new OpenApiSchema { Type = "string" });
});
SomeWrapperType
should be treated as a simple type, in this case, like a regular string
. Let’s see what happened in the Swagger UI:string
(and no longer shows up in the schemas section), but it’s still showing up in the request body.[FromQuery]
to the parameter, right? Let’s try it![HttpGet("from-query")]
public IActionResult GetWithFromQuery([FromQuery]SomeWrapperType wrapper)
=> Ok(wrapper.Value);
wrapper
and be a string
(as we configured with Swashbuckle). Unfortunately, we see the name Value
and type int
, both referring to SomeWrapperType
's internal property.SomeWrapperType
, did the following:[TypeConverter(typeof(SomeWrapperTypeTypeConverter))]
public record struct SomeWrapperType(int Value)
{
public int Value { get; } = Value;
public static bool TryParse(string? value, out SomeWrapperType result)
{
if (int.TryParse(value, out var parsed))
{
result = new SomeWrapperType(parsed);
return true;
}
result = default;
return false;
}
private class SomeWrapperTypeTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
=> sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
var stringValue = value as string;
if (TryParse(stringValue, out var result))
{
return result;
}
return base.ConvertFrom(context, culture, value);
}
}
}
TypeConverter
, implemented the conversion from string
to SomeWrapperType
, and finally used it with the TypeConverterAttribute
.FromQuery
attribute isn’t needed).TryParse
method with a signature like I showed earlier, as well as a BindAsync
method, are detected and used for parameter binding (more information on the official docs).app.MapGet(
"/sample/minimal",
(SomeWrapperType wrapper) => Results.Ok(wrapper.Value));
TypeConverter
, implement conversion logic and decorate the type with the TypeConverterAttribute
.TryParse
and BindAsync
will do the trick.MapType
to configure the schema when using Swashbuckle.