RonC
RonC

Reputation: 33917

Asp.Net Core Model binding, how to get empty field to bind as a blank string?

If a form like the one below is submitted and MyField is left blank on the form, then by default Asp.Net Core model binding will place null into the corresponding property on the model as indicated below.

Example Form

 <form asp-controller="SomeController" asp-action="SomeAction">
        <label asp-for="MyField">My Field</label><input asp-for="MyField" type="text" />    
        <button type="submit">Submit</button>
 </form>


Example Model

 public class MyModel{
     public string MyField { get; set; }
 }


Example Action Method

    [HttpPost]
    public IActionResult Post(MyModel m) {
          //m.MyField will be null if the field was left empty
          //but I want it set to a blank string by the model binder
    }

However, since MyField is actually transmitted in the Http Post body I'd prefer that the model binder set the MyField property on the model to a blank string rather than setting it to null. I'd prefer to reserve null for cases where MyField is not transmitted in the Http Post body. How can the model binder be changed to exhibit this behavior?

Upvotes: 14

Views: 8199

Answers (3)

Alex from Jitbit
Alex from Jitbit

Reputation: 60942

Adding my 2c just in case if you want to override ConvertEmptyStringToNull behavior at parameter level in an action method this is how you do it (works in .NET 5, .NET 6, probably later versions too):

Suppose you have an action method in your controller like this:

public IActionResult DoStuff(int id, string name)

And you want to be able to pass empty-string to the "name" parameter.

Step 1 - add this class:

[AttributeUsage(AttributeTargets.Parameter)]
public class AllowEmptyAttribute : DisplayFormatAttribute
{
    public AllowEmptyAttribute() : base() { ConvertEmptyStringToNull = false; }
}

Step 2 - use in controller

public IActionResult DoStuff(int id, [AllowEmpty] string name)

This might be hacky way to change it, but it works.

Upvotes: 1

RonC
RonC

Reputation: 33917

Studying the ASP.NET Core code for SimpleTypeModelBinder at https://github.com/aspnet/Mvc/blob/rel/1.1.3/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinder.cs I could see that there is a ModelMetadata.ConvertEmptyStringToNull setting which is set to true by default that is causing the blank string to be converted to null on data binding. But the property is read only so at first I couldn't figure out how to changes its value.

@rsheptolut's post on this page https://github.com/aspnet/Mvc/issues/4988 led me to a solution.

Solution:

The value has to get set at startup. This can be done via this class:

public class CustomMetadataProvider : IMetadataDetailsProvider, IDisplayMetadataProvider {
    public void CreateDisplayMetadata(DisplayMetadataProviderContext context) {

        if (context.Key.MetadataKind == ModelMetadataKind.Property) {
    
            context.DisplayMetadata.ConvertEmptyStringToNull = false;
        }
    }
}

When it's hooked into MvcOptions in the ConfigureServices method of the startup.cs file like so

services.AddMvc()
       .AddMvcOptions(options => options.ModelMetadataDetailsProviders.Add(new CustomMetadataProvider ())); 

Now site wide, the default for a blank field that is posted back will be for the data binder to set the corresponding model property to a blank string rather than to null. Yea!

Upvotes: 19

Joe Audette
Joe Audette

Reputation: 36736

Have you tried making the property have a default value of empty string?

public string MyField { get; set; } = string.Empty;

an uglier solution to try is:

private string myField  = string.Empty;
public string MyField 
{
    get { return myField ?? string.Empty; }
    set { myField = value; }
}

I think it should work

Upvotes: 0

Related Questions