I would like to create attribute that will tell that parameter should be filled from action arguments, not from query, route, header or body.
ASP.NET is already binding it correctly from ActionArguments on its own, but I want to prevent it from binding via other means.
I have ActionFilterAttribute
for method that set
context.ActionArguments["set"] = filledSet;
So far I'm in my methods I have
[FillSet]
public void MyMethod([FromServices] HashSet<string> set = null)
{
// Do stuff.
}
If I don't use = null
I will get
System.InvalidOperationException: No service for type System.Collections.Generic.HashSet1[System.String]' has been registered.
I used FromService
so it shouldn't try to bind from other sources.
I would like to have something like
[FillSet]
public void MyMethod([FromActionArguments] HashSet<string> set)
{
// Do stuff.
}
EDIT1: I tried to change it to BindNeverAttribute but now I got
System.InvalidOperationException 'Test.Controllers.TestController.Get (Test)' has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body. ...
In the end I create Attribute with name SetDefault. I'm not sure, if there might be some bad case, but for now it seems to work.
Classes that I created:
/// <inheritdoc/>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public class SetDefaultAttribute : Attribute, IBindingSourceMetadata
{
/// <inheritdoc/>
public BindingSource BindingSource => BindingSource.Custom;
}
/// <inheritdoc/>
public class DefaultValueModelBinder : IModelBinder
{
/// <inheritdoc/>
public Task BindModelAsync(ModelBindingContext bindingContext)
{
ArgumentNullException.ThrowIfNull(bindingContext);
var modelType = bindingContext.ModelType;
object defaultValue = GetDefaultValue(modelType);
bindingContext.Result = ModelBindingResult.Success(defaultValue);
return Task.CompletedTask;
}
private object GetDefaultValue(Type type)
{
if (type.IsValueType)
{
return Activator.CreateInstance(type);
}
return null;
}
}
/// <inheritdoc/>
public class DefaultValueModelBinderProvider : IModelBinderProvider
{
/// <inheritdoc/>
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
ArgumentNullException.ThrowIfNull(context);
if ((context.Metadata is DefaultModelMetadata defaultModelMetadata
&& defaultModelMetadata.Attributes?.ParameterAttributes?.Any(x => x is SetDefaultAttribute) == true)
|| context.Metadata?.BinderType == typeof(DefaultValueModelBinder))
{
return new DefaultValueModelBinder();
}
return null;
}
}
Added to startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new DefaultValueModelBinderProvider());
});
}
Used like:
[FillSet]
[HttpPost("test")]
public void MyMethod([FromQuery] int id, [SetDefault] HashSet<string> set, [SetDefault] int someNumber)
{ /* Code here. */ }