12

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?

RonC
  • 31,330
  • 19
  • 94
  • 139

2 Answers2

16

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!

Pang
  • 9,564
  • 146
  • 81
  • 122
RonC
  • 31,330
  • 19
  • 94
  • 139
  • 1
    I love you <3 hahaha why MS changed this is beyond me -_- – Zorkind Aug 13 '18 at 03:10
  • 9
    https://github.com/aspnet/Mvc/issues/7630 suggests that adding the attribute [DisplayFormat(ConvertEmptyStringToNull = false)] per property would work. – Jeremy Lakeman Nov 13 '18 at 03:55
  • @JeremyLakeman - That's a great solution if a person only needs the functionality in one or two places. – RonC Nov 13 '18 at 13:14
  • 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. – Dunge Sep 05 '21 at 19:47
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

Joe Audette
  • 35,330
  • 11
  • 106
  • 99
  • 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. – RonC Jun 05 '17 at 19:55
  • That works, as does setting the property manually to a blank string in the post action method if the property is null but both approaches obscure whether the field was posted back or not and require code throughout the application. What I really want is a way to globally change the model binder so that I only have to change this behavior in one place. – RonC Jun 05 '17 at 20:24