skip to Main Content

I made a strongly typed id to prevent ids from entity A from being mixed with entity B, like so:

public record struct Id<TEntity>(int Value);

I also created and registered an IModelBinder and a IModelBinderProvider, so that I can have the following signature in my controller:

[HttpGet("{id}")]
public async Task<ActionResult<Product>> GetById(Id<Product> id);

This has one problem however. While int parameters have the [FromRoute] binding source by default, my Id struct defaults to the [FromBody] binding source, requiring me to explicitly add the attribute:

[HttpGet("{id}")]
public async Task<ActionResult<Product>> GetById([FromRoute] Id<Product> id);

So my question is, how can I change the default binding source from the binder/provider, without using annotations (including on Id)?

2

Answers


  1. For change the default source IModelBinder in ASP.NET, can use the [ModelBinder] attribute on your Id type. By applying this you can specify the default binding source for your model. Model Binding in ASP.NET Core

    If don’t understand from this source, see below example:

    [ModelBinder(BindingSource = BindingSource.Path)]
    public record struct Id<TEntity>(int Value);
    

    After applying this your controller action can be simplified as follows:

    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> GetById(Id<Product> id)
    {
        // Your implementation here
    }
    

    ASP.NET will automatically bind the Id parameter from the route by default, as specified in the [ModelBinder] attribute.

    Hope help you! :))

    Login or Signup to reply.
  2. To change the default binding source for your custom Id<TEntity> struct without using annotations on the Id parameter itself, you can customize the behavior in your custom IModelBinderProvider and IModelBinder. By default, your Id<TEntity> struct is being treated as a complex type, which results in the [FromBody] binding source being used. To change this behavior, you’ll need to specify a custom model binder for your Id<TEntity> type and set the default binding source there.

    Here’s how you can achieve this:

    1. Implement a custom model binder for your Id<TEntity> struct:
    using System;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc.ModelBinding;
    
    public class IdModelBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            // Get the value from the route data
            var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.FieldName);
    
            if (valueProviderResult == ValueProviderResult.None)
            {
                return Task.CompletedTask;
            }
    
            try
            {
                // Parse the value and create the Id<TEntity> instance
                var value = Convert.ChangeType(valueProviderResult.FirstValue, typeof(int));
                var id = Activator.CreateInstance(bindingContext.ModelType, value);
                bindingContext.Result = ModelBindingResult.Success(id);
            }
            catch
            {
                bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, "Invalid Id");
            }
    
            return Task.CompletedTask;
        }
    }
    
    1. Implement a custom model binder provider:
    using Microsoft.AspNetCore.Mvc.ModelBinding;
    
    public class IdModelBinderProvider : IModelBinderProvider
    {
        public IModelBinder? GetBinder(ModelBinderProviderContext context)
        {
            if (context.Metadata.ModelType.IsGenericType && context.Metadata.ModelType.GetGenericTypeDefinition() == typeof(Id<>))
            {
                return new BinderTypeModelBinder(typeof(IdModelBinder));
            }
    
            return null;
        }
    }
    
    1. Register your custom model binder provider in your application’s Startup.cs:
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
    
        public IConfiguration Configuration { get; }
    
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers(options =>
            {
                // Add your custom model binder provider
                options.ModelBinderProviders.Insert(0, new IdModelBinderProvider());
            });
        }
    
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
    
            app.UseRouting();
    
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
    

    With the above implementation, your Id<TEntity> struct should now bind from the route by default without needing the [FromRoute] attribute on the Id parameter in your controller actions.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search