Reputation: 191
I have the following domain type modelled via a value type:
public readonly record struct Id
{
public string Value { get; }
public Id() => throw new InvalidOperationException("Please use the 'Of' factory method.");
public Id(string value)
=> this.Value = IsValid(value, out string? errorMessage) ? value : throw new ArgumentException(errorMessage);
public static bool IsValid([NotNullWhen(true)] string? value, [NotNullWhen(false)] out string? errorMessage)
{
errorMessage = string.IsNullOrWhiteSpace(value) is false ? null : $"Invalid {nameof(ImportId)} value.";
return errorMessage is null;
}
public static Id Of(string value) => new(value);
}
Please note:
string
in this case)..Of
factory method or via the constructor with parameters - to enforce 'correct by construction' semantics. So, an exception is thrown from the default constructor.Now, I'm trying to use this type as a parameter of a controller in my web api:
public record BeginImport(Id Id);
[HttpGet]
[Route("{id}")]
public string Get(
[ModelBinder(BinderType = typeof(ModelBinder), Name = $"id")]
Request.BeginImport request)
{
return request.Id.Value; // just a stand-in
}
To properly bind the parameter I use the following model binder:
public class ModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
ArgumentNullException.ThrowIfNull(bindingContext);
ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == ValueProviderResult.None || valueProviderResult.FirstValue is null)
{
bindingContext.ModelState.TryAddModelError(
bindingContext.ModelName, $"The {bindingContext.ModelName} field is required.");
return Task.CompletedTask;
}
if (Id.IsValid(valueProviderResult.FirstValue, out string? errorMessage))
{
Id id = Id.Of(valueProviderResult.FirstValue);
BeginImport beginImport = new(id);
bindingContext.Result = ModelBindingResult.Success(beginImport);
}
else
{
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, errorMessage);
}
return Task.CompletedTask;
}
}
Now, the binder executes and does its job just fine assigning a correct value to the bindingContext.Result
. But between the binder exiting and my controller getting to run, some code (in the asp.net core guts) makes a call to the Id's default constructor. And everything blows up :)
Two questions:
Thanks in advance.
Upvotes: 0
Views: 36