14

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?

3 Answers 3

19

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!

Sign up to request clarification or add additional context in comments.

4 Comments

I love you <3 hahaha why MS changed this is beyond me -_-
github.com/aspnet/Mvc/issues/7630 suggests that adding the attribute [DisplayFormat(ConvertEmptyStringToNull = false)] per property would work.
@JeremyLakeman - That's a great solution if a person only needs the functionality in one or two places.
I wish we could do that for only a few controller actions and not the whole project. Adding the attribute would work, but in my case the model class is shared in another project and do not reference ASP.NET so I can't add the attribute. In previous ASP (non-core) version, I would override DefaultModelBinder, but it doesn't seem possible anymore.
1

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.

Comments

0

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

1 Comment

That would have been really cool if that had worked. It'd be such a simple solution. But I just tried and alas MyField is still null. It would appear that the model binder is explicitly setting the value to null in cases where the for field is left blank. To be sure I repeated the test a couple times observing different values in the debugger. If I set the default to "hello" then hello comes back in the model property as expected but if I set the default to String.Empty I get null back.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.