[TL/DR? – skip to the bottom]
I ran into a problem with an ASP.NET MVC Core app today and it took me a while to figure it out. Here’s the problem: on an extremely simple and straightforward form, I couldn’t get a field in my model to POST back to the server. Every time that I’d click the submit button on my form, it would post back to my controller action but that one property would always be null.
I put a breakpoint in the controller action code. The property on my model was null but when I looked at the Request.Form collection, the value from the HTML form was actually getting sent to the server.
SO WHY IS THAT PROPERTY NULL!!!!?!??!??!?!
Here’s the field that’s going missing. It’s nothing special.
Here’s the code in the Index.cshtml file for that Message field:
<div class="form-group">
@Html.LabelFor(model => model.Message, htmlAttributes: new { @class = "control-label" })
@Html.EditorFor(model => model.Message, new { htmlAttributes = new { @class = "form-control" } })
</div>
And here’s the the code for the Model:
public class EmailTestViewModel
{
[Display(Name = "from email")]
public string FromEmail { get; set; }
[Display(Name = "from name")]
public string FromName { get; set; }
[Display(Name = "recipient email")]
public string RecipientEmail { get; set; }
[Display(Name = "recipient name")]
public string RecipientName { get; set; }
[Display(Name = "subject")]
public string Subject { get; set; }
[Display(Name = "message")]
public string Message { get; internal set; }
[Display(Name = "result message")]
public string ResultMessage { get; set; }
}
Here’s the method in the Controller:
[HttpPost]
public async Task<IActionResult> Index(EmailTestViewModel model)
{
await _EmailService.SendEmail(
model.RecipientEmail, model.RecipientName,
model.Subject, model.FromEmail,
model.FromName, model.Message,
$"<p>{model.Message}</p>");
model.ResultMessage = "sent";
return View(model);
}
It’s absolutely nothing special. This code couldn’t be any simpler. But when I run the code and hit the breakpoint in the controller, the Message property is null.
But the absolutely bizarre thing is that if I look at the Request.Form dictionary, the value is there and it’s got exactly the value that I’d expect.
So what’s going wrong?
The Cause of the Problem and Then The Solution
Well, the cause of the problem turned out to be coder error. (Surprise!)
Once I saw that the value was in the Request.Form dictionary but wasn’t getting populated on the model, I started thinking about how model binding works in ASP.NET MVC Core. It’s going to take the values that have been POSTed from the form and try to match the names up with the public properties on the model.
When I created the code for the Message property, I’d somehow marked the setter to be internal
. The internal visibility modifier makes code visible only to other pieces of code inside the same assembly. Since the ASP.NET MVC model binder is in a different assembly, the binder doesn’t see that setter property as being available so it skips setting that value. Therefore, that Message property is always null on postback.
The solution was easy. Remove that extraneous internal
modifier and the code worked just fine.
I hope this helps.
-Ben