I'm working with ASP.NET MVC
and have a problem with the value sent from Ajax
to my controller.
Let's say I have SampleViewModel
like this:
public class SampleViewModel
{
private string _firstName = string.Empty;
public SampleViewModel()
{
_firstName = string.Empty;
}
public string FirstName
{
get { return _firstName; }
set { _firstName = value ?? string.Empty; }
}
public string LastName { get; set; }
public string FullName { get; set; }
}
Controller
[HttpPost]
public JsonResult ActionSubmit(SampleViewModel model)
{
var result = "";
if(model.FirstName == null)
result += "\nFirstName is null";
if(model.LastName == null)
result += "\nLastName is null";
return Json(result);
}
Ajax
$('.submit').click(function() {
$.ajax({
url: '@Url.RouteUrl(new{ action="ActionSubmit", controller="Home"})',
data: JSON.stringify({ FirstName: '', LastName: '', FullName: 'Phong_Nguyen' }),
// Even though I use { FirstName: '', LastName: '', FullName: 'Phong_Nguyen' } without JSON.stringify
type: 'POST',
dataType: 'json',
contentType: "application/json; charset=utf-8",
success: function(resp) {
alert(resp);
}});
});
As you can see, I send empty value but on the controller's end, I get null (the response value always "LastName is null"):
Question
Why is it that when in Ajax I am sending empty
, I get null
value in my controller?
Is there a better way and more elegant to resolve my problem like below?
public string FirstName
{
get { return _firstName; }
set { _firstName = value ?? string.Empty; }
}
Why is it that when in Ajax I am sending empty, I get
null
value in my controller?
string
is a reference type, and its default value is null
. The ModelBinder
sets the properties to their default value if no value is provided in the request.
Is there a better way and more elegant to resolve my problem like below?
You can annotate the property with [DisplayFormat(ConvertEmptyStringToNull = false)]
, so the empty string value is preserved.
You can write a custom ModelBinder
that sets ConvertEmptyStringToNull
to false
, and apply it globally.
public class NullStringModelBinder : DefaultModelBinder {
public override object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext) {
bindingContext.ModelMetadata.ConvertEmptyStringToNull = false;
return base.BindModel(controllerContext, bindingContext);
}
}
//register it in Application_Start()
ModelBinders.Binders.Add(typeof(string), new NullStringModelBinder());
This particular change has been documented here and it is one of the breaking changes from MVC 1.0
. This logic of binding empty string to null
s is controlled with the ModelMetadata.ConvertEmptyStringToNull
property which is used by the DefaultModelBinder
.
Now if you do not want to annotate all your properties, you can create a custom model binder:
public class EmptyStringModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
bindingContext.ModelMetadata.ConvertEmptyStringToNull = false;
Binders = new ModelBinderDictionary() { DefaultBinder = this };
return base.BindModel(controllerContext, bindingContext);
}
}
And set it in your Global.asax
:
ModelBinders.Binders.DefaultBinder = new EmptyStringModelBinder();
Or in your specific action:
[HttpPost]
public JsonResult ActionSubmit([ModelBinder(typeof(EmptyStringModelBinder))SampleViewModel model)
Why was this done?
This was done because the default value of a string
is null
and because string
is a reference type
and the default value for all reference types is null
. Hence, this change of the framework might be reasonable. But on the other hand, we should try to avoid null values at all, therefore we need to write a custom model binder to avoid such cases.
There is a question on why the default value of the string type null instead of an empty string?. You can go over this to understand more about why this change was done.
As per @Anton: In c# 8.0
you can turn on null checks to avoid NullReferenceException
and set to reference types default values instead of null
I decided summary from @Rahul Sharma's and @rhytonix's answers along with giving you examples and more detailed explanations.
- Why is it that when in Ajax I am sending empty, I get null value in my controller?
This is simply because MVC 2.0
defaults to initializing strings to null. To be more precise, if an empty
string means has no value, So .NET sets the default value of its. And the default string (belonging to reference type) is null
.
More details in Model String Property Binding Breaking Change
- Is there a better way and more elegant to resolve my problem like below?
There are some ways to bind String property as string.Empty
instead of null
1. From C# 6, You can use DefaultValueAttribute to have auto-property an initial value like below
public string LastName => string.Empty;
Basically, This way is the same as the OP's solution mentioned in the post, Just more elegant.
2. Custom default implementation of IModelBinder
by inheriting from DefaultModelBinder
and changing the ConvertEmptyStringToNull
value to false on the internal ModelMetaData
object.
public sealed class EmptyStringModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
bindingContext.ModelMetadata.ConvertEmptyStringToNull = false;
return base.BindModel(controllerContext, bindingContext);
}
}
Then in Application_Start()
method of Global.asax.cs
you need to do like below to complete
protected void Application_Start()
{
ModelBinders.Binders.DefaultBinder = new EmptyStringModelBinder();
RegisterRoutes( RouteTable.Routes );
}
3. Use DisplayFormatAttribute.ConvertEmptyStringToNull Property like below
[DisplayFormat(ConvertEmptyStringToNull = false)]
public string LastName { get; set; }
Simply because in ModelMetadata
true
if empty string values are automatically converted tonull
; otherwise,false
. The default istrue
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With