Search code examples
c#.netwebapi

Generic GET web api endpoint for System.Type and Id


I'm using web api in net 7. Is there any way to make the binding engine understand a Type and an object id parameter?

I have an endpoint that I want to reuse to get an entity of a certain type by id.

AFAIK generic types are not supported in an action, so I guess what I need is something like this:

[HttpGet("{id}")]
public object Get(Type type, object id) 
{
    // call _entityFrameWorkDatabaseContext.Find(Type type, object id)
}

I would like to invoke it using something like /theAction/1?type=<AssemblyQualifiedName>. Is it possible?


Solution

  • A custom model binder should be able to get you what you need.

    public class TypeEntityBinder : IModelBinder
    {
    
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }
    
            var modelName = bindingContext.ModelName;
    
            // Try to fetch the value of the argument by name
            var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
    
            if (valueProviderResult == ValueProviderResult.None)
            {
                return Task.CompletedTask;
            }
    
            bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);
    
            var value = valueProviderResult.FirstValue;
    
            // Check if the argument value is null or empty
            if (string.IsNullOrEmpty(value))
            {
                return Task.CompletedTask;
            }
    
            // Treat it like a string request parameter
            if (!string.TryParse(value, out var type))
            {
                bindingContext.ModelState.TryAddModelError(
                    modelName, "Type must be a string.");
    
                return Task.CompletedTask;
            }
    
            // In a debugging session, make sure the `type` is binding correctly
    
            // Make sure the assembly name is correct and matches a real type
            var model = Type.GetType($"namespace.{type}, assemblyName");
            // Bind the result to Type
            bindingContext.Result = ModelBindingResult.Success(model);
            return Task.CompletedTask;  // <-- run for the hills!
        }
    }
    

    Then, simply slap the attribute to active the custom binder.

    Get([ModelBinder(BinderType = typeof(TypeEntityBinder))] Type type, object id) 
    

    Keep in mind, this is mostly theoretical code. One snag you could run into is the custom binder does not register properly in the request pipeline, so make sure it is actually included. Also, check the contents of the type variable via debugging techniques.

    https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-7.0