.net-coreasp.net-core-webapi

.NET Core Web API optional query parameter is still not null


There is many similar questions on StackOverflow then mine, but no one says about my issue.

I have REST API controller with following method:

    [HttpGet("Lists")]
    public Codebook GetFilterLists([FromQuery] FilterQuery? filterQuery = null)
    {
        // some select and return code
    }

All properties of FilterQuery class are display as optional on Swagger UI. When I leave all of them empty and click on "Execute" button, Swagger UI makes this GET query: https://localhost:7137/api/PublicWeb/Lists

Even so, the filterQuery parameter has value - instance of filterQuery with default values. enter image description here

I tried change JsonSerializerOptions.DefaultIgnoreCondition settings in Program.cs, but it has no effect for this.

Do you have any idea, what I have wrong?


Solution

  • Well according to your scenario, at the begining I want to share, you may know nullable and optional are completely different concepts. If you declare a parameter with int? that marks it as nullable, but it is not optional: You must pass either a non-null integral value or null. Adding = null, however, is the syntax required for declaring the default value of an optional parameter. You could check more details here.

    .NET Core Web API optional query parameter is still not null. Do you have any idea, what I have wrong?

    The behavior you are getting is expected because the property is set to null or a default value if its simple type but yours one is complex type thus, the value setting not null.

    In addition to this, Non-nullable value types are set to default(T). Means it creates an instance but yours one are nullable and breaks the convension and consequently it will not find any source property matching with it that results in not null.

    Note: You can find more details here in the official documentation.

    I tried change JsonSerializerOptions.DefaultIgnoreCondition settings in Program.cs, but it has no effect for this.

    No, JsonSerializerOptions cannot resolve your issue. You would require Custom model binder to implement what you are expecting. Here I am providing you a sample illustration, but you have to improve it based on your requirement.

    Custom Model Binder:

    public class NullEntityBinder : IModelBinder
        {
           
            public NullEntityBinder()
            {
             
            }
    
            public Task BindModelAsync(ModelBindingContext bindingContext)
            {
                if (bindingContext == null)
                {
                    throw new ArgumentNullException(nameof(bindingContext));
                }
    
                 var model = new FilterQuery();
    
    
    
                if (bindingContext.ValueProvider.GetValue("AreaOfAdjustmentId").FirstOrDefault() != null)
                {
                    model.AreaOfAdjustmentId = Convert.ToUInt32(bindingContext.ValueProvider.GetValue("AreaOfAdjustmentId").FirstOrDefault());
                }
    
                if (bindingContext.ValueProvider.GetValue("DocumentName").FirstOrDefault() != null)
                {
                    model.DocumentName = bindingContext.ValueProvider.GetValue("DocumentName").FirstOrDefault();
                }
    
                if (model.AreaOfAdjustmentId == null && model.DocumentName == null)
                {
                    bindingContext.Result = ModelBindingResult.Success(null);
                }
    
                bindingContext.Result = ModelBindingResult.Success(model);
                return Task.CompletedTask;
            }
        }
    

    Wrap Your Model With Binder:

    [ModelBinder(BinderType = typeof(NullEntityBinder))]
    public class FilterQuery
    {
       
        public uint? AreaOfAdjustmentId { get; set; }
        public string? DocumentName { get; set; }
       
    }
    

    Note: In my test I have used two properties for demonstration; you would need to implement logic based on your other property as well.

    Output:

    enter image description here

    Note: If you would like to know more details on it you could check our official documentation here