ASP.NET Core pass Null parameters to controller endpoints

377 views Asked by At

Using a model binder it is possible to pass a NULL complex type to a controller endpoint.

This solution does not work for all controller endpoints, rebuilding the model is also not straight forward.

Is there a better solution to pass a NULL complex type parameter in ASP.NET Core?

This functionality works as expected (allows NULLs) using .NET Standard without any modifications.

Using the following IModelBinder and IModelBinderProvider:

public class NullModelBinderProvider : IModelBinderProvider
{
        public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            if (context.Metadata.ModelType == typeof(BreakSize))
            {
                return new BinderTypeModelBinder(typeof(NullModelBinder));
            }

            return null;
        }
}

public class NullModelBinder : IModelBinder
{
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
                throw new ArgumentNullException("bindingContext");
            // If null than pass null to the controller - this works
            if (bindingContext.HttpContext.Request.Form.ContainsKey(bindingContext.FieldName) && string.IsNullOrEmpty(bindingContext.HttpContext.Request.Form[bindingContext.FieldName]))
                bindingContext.Result = ModelBindingResult.Success(null);
            else
                
                bindingContext.Result = ModelBindingResult.Success(new BreakSize()); // Todo set values for BreakSize from the form

            return Task.CompletedTask;
        }
}

enter image description here

3

There are 3 answers

1
karolgro On

Assuming your controller action looks like this:

public void Endpoint(BreakSize size) { /**/ }

ASP.NET Core by default performs validation, i.e. it will check for nulls and throw 400. If you want to allow nulls, you need to use mark it as nullable:

public void Endpoint(BreakSize? size) { /**/ }

or

public void Endpoint([MaybeNull] BreakSize size) { /**/ }
0
Mert Durukan On
  1. Optional Parameters: Your First Line of Defense

Make those complex type parameters optional using Nullable or T?. This allows null values to pass through without model binder surgery. Example:

public IActionResult MyAction([FromBody] BreakSize? breakSize)
{
    // Handle null or non-null breakSize like a boss
}
  1. Query Parameters: A Sneaky Sidestep

Consider query parameters instead of body parameters for those optional data ninjas. They naturally embrace null values. Example:

public IActionResult MyAction([FromQuery] BreakSize breakSize)
{
// Null or not, you got this
}
  1. Custom Model Binding: For When You Need to Get Your Hands Dirty

Create a custom model binder to explicitly handle null values, giving you granular control over the binding process. Example:

public class BreakSizeModelBinder : IModelBinder
  {
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
      // Implement logic to handle null values and bind properties from request
    }
  }
  1. Action Filters: The Gatekeepers

Intercept requests and modify models before they reach action methods using action filters. Perfect for custom null-value handling and other preprocessing shenanigans. Example:

public class NullBreakSizeFilter : ActionFilterAttribute
  {
    public override void OnActionExecuting(ActionExecutingContext context)
    {
      // Check for null BreakSize parameter and handle it
    }
  }
  1. Value Providers (ASP.NET Core 6+): The New Kids on the Block

Leverage value providers for declarative null-value handling, offering flexibility through dependency injection. Example:

public IActionResult MyAction([FromServices] IValueProvider<BreakSize> valueProvider)
  {
    BreakSize breakSize = valueProvider.GetValue(bindingContext);
    // Handle null or non-null breakSize
  }

Frequency of null values: If null values are common, optional parameters or query parameters might be simpler. Level of control needed: Custom model binding or action filters provide more granular control. Dependency injection: Value providers can be injected as services for greater flexibility. Readability and maintainability: Choose an approach that is clear and easy to understand for future developers.

0
Dinuda Yaggahavita On

There is a better solution to pass a NULL complex type parameter in ASP.NET Core. You can use a nullable value type.

For example, if you have a complex type called BreakSize like so:

public class BreakSize?
{
    public int? h { get; set; }
    public int? w { get; set; }
}

Then, you can use this type as a parameter in your controller endpoint. For example:

public IActionResult MyAction([FromBody] BreakSize? breakSize)
{
    return Ok();
}

In case you call this endpoint with a null value for the {breakSize} parameter, your controller action's {breakSize} variable will also be null.

The reason this method works is that the model binding system in ASP.NET Core supports nullable value types. The model binding system will automatically transform null values to null when a request is bound to a nullable value type.

Because it is more straightforward and doesn't require you to create any extra code, this technique is preferable than utilizing a custom model binder. Furthermore, it is compatible with all controller endpoints, independent of their level of complexity.