I'm working on an ASP.NET Core application and need to create a custom model binder that can dynamically bind all route parameters to a model without specifying each parameter explicitly.
For example, given a route like api/{controller}/{action}/{id}/{name}
, I want to be able to bind all route parameters (id, name
, etc.) to a model dynamically and as well I want to handle a route like api/{controller}/{id}
with one custom model binder.
This is the custom model binder I created for the id
parameter. Currently, I only need the id, but I want to make it more general for future use. How can I improve this to dynamically handle all route parameters later on?
Custom model binder:
public class BaseIdRequestModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var idValue = bindingContext.ValueProvider.GetValue("id").FirstValue;
if (string.IsNullOrEmpty(idValue) || !long.TryParse(idValue, out var id))
{
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
var request = new BaseIdRequest<long> { Id = id };
bindingContext.Result = ModelBindingResult.Success(request);
return Task.CompletedTask;
}
}
Attribute :
public class FromRouteIdAttribute : ModelBinderAttribute
{
public FromRouteIdAttribute() : base(typeof(BaseIdRequestModelBinder))
{
}
}
Example :
[HttpGet("[action]/{id:long}")]
public ActionResult GetMeetEventById([FromRouteId] BaseIdRequest<long> reqeust)
{
var result = _meetingEventService.GetMeetingEventById(reqeust, reqeust.ChartLogin);
return Ok(result);
}
This is the custom model binder I created for the id parameter. Currently, I only need the id, but I want to make it more general for future use. How can I improve this to dynamically handle all route parameters later on?
Well, in order to implement your requirement you have to modify your BaseIdRequestModelBinder
, currently you are extracting the value directly from the bindingContext.ValueProvider.GetValue
by its name, but in future we don't know what would be the parameter name.
So if we want to deal with the N
number of dynamic route value, we should better use Dictionary
type parameter in controller action and then execute the loop over bindingContext.ActionContext.RouteData.Values
and bind the value.
Let's have a look in practice how we can implement that:
Update BaseIdRequestModelBinder:
public class FromRouteParametersAttribute : ModelBinderAttribute
{
public FromRouteParametersAttribute() : base(typeof(BaseIdRequestModelBinder))
{
}
}
public class BaseIdRequestModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var model = new DynamicRequestModel();
var routeValues = bindingContext.ActionContext.RouteData.Values;
foreach (var routeValue in routeValues)
{
if (routeValue.Key != "action" && routeValue.Key != "controller")
{
model.RouteParameters[routeValue.Key] = routeValue.Value?.ToString();
}
}
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
Note: I am using hte conditional to exclude "action" and "controller" from route values.
Update Model:
public class DynamicRequestModel
{
public Dictionary<string, string> RouteParameters { get; set; } = new Dictionary<string, string>();
}
Update Controller:
[HttpGet("GetMeetEventById/{id:long}/{name?}/{address}")]
public ActionResult GetMeetEventById([FromRouteParameters] DynamicRequestModel request)
{
return Ok(request.RouteParameters);
}
Output: