Asp.net mvc 4 MVC 4 ModelState invalid because it is trying to auto-fill values and not accept new ones

Asp.net mvc 4 MVC 4 ModelState invalid because it is trying to auto-fill values and not accept new ones,asp.net-mvc-4,modelstate,modelstatedictionary,Asp.net Mvc 4,Modelstate,Modelstatedictionary,I have a comments section. In the view there is only a comments editor box. @using (Html.BeginForm(new { courseID = @ViewBag.courseID, userName = @User.Identity.Name })) { @Html.ValidationSummary(true) <div class="NewComment"> <div class="editor-field"> @Html.TextAreaFor(model => model.CommentText, new { maxLength = 500}) @Html.ValidationMessageFor(model => model.CommentText) </div> <input type="submit" class="

I have a comments section. In the view there is only a comments editor box.

@using (Html.BeginForm(new { courseID = @ViewBag.courseID, userName = @User.Identity.Name }))
{
    @Html.ValidationSummary(true)

    <div class="NewComment">
        <div class="editor-field">
            @Html.TextAreaFor(model => model.CommentText, new { maxLength = 500})
            @Html.ValidationMessageFor(model => model.CommentText)
        </div>
        <input type="submit" class="PostComment" value="Post Comment" />
        <div id="Counter" class="CommentCounter"/>
    </div>

}

The model has the course the comment is linked too, date, comment text, and user. The other values get filled in the create post method.

[HttpPost]
public ActionResult AddComment(CourseComment coursecomment, int courseID, String userName)
{
    userName = userName.Split('\\')[1];
    coursecomment.CommentDate = System.DateTime.Now;
    coursecomment.CourseID = courseID;
    coursecomment.UserName = db.Users.FirstOrDefault(u => u.UserName == userName).UserID;
    if (ModelState.IsValid)
    {
        db.CourseComments.AddObject(coursecomment);
        db.SaveChanges();
    }
    return RedirectToAction("Details", "Course", new { courseID = courseID });
}

The problem occurs here. The model is trying to use the

userName
parameter for the value for
courseComment.UserName
before I actually do the work and set it. After it gets set the
ModelState
doesn't change.

Example,

domain\abc123
gets passed into the post method and also set in the
ModelState
for
UserName
. I do some work, change the
userName
to
abc123
and find the linked ID, lets say ID = 1, to that user with said name, then plug that into the
courseComment.UserName
ModelState
still leaves the
domain\abc123
in there and the model stays invalid.

Now, this was working original, then I changed the underlying database around, mainly just column names and some relations.

My solution for this.

Move receiving the username from the post method

[HttpPost]
public ActionResult AddComment(CourseComment coursecomment, int courseID)
{
    coursecomment.CommentDate = System.DateTime.Now;
    coursecomment.CourseID = courseID;
    coursecomment.UserName = db.Users.FirstOrDefault(u => u.UserName == userName).UserID; //Moved
    if (ModelState.IsValid)
    {
        db.CourseComments.AddObject(coursecomment);
        db.SaveChanges();
    }
    return RedirectToAction("Details", "Course", new { courseID = courseID });
}

to a get method.

[HttpGet]
public JsonResult GetUserName(string userName)
{
    var ret = db.Users.FirstOrDefault(u => u.UserName == userName).UserID;
    return Json(ret, JsonRequestBehavior.AllowGet);
}

Then changed the view to be like

@Html.HiddenFor(model => model.UserName)

....

<script type="text/javascript">
$(function () {
    var userName = '@User.Identity.Name.Split('\\')[1]';
    $.ajax({
        url: '@Url.Action("GetUserName", "CourseComment")',
        data: { userName: userName },
        type: 'get'
    }).done(function (data) {
        $('#UserName').val(data);
    });
});

#1

The problem is that all the view cares about is what's in ModelState. This is confusing to many devs, but it's logical when you think about it.

Essentially, ModelState is composed from the values of Model, of course, but then also from values in ViewBag, ViewData, and Request, which override anything set via Model. To understand why, imagine a scenario where the user is editing an existing object, but makes an error in one of the values, causing the form to be returned to correct their error. If the values from Model were use, the users edits would be completely undone, replaced with the original values on the object. However, by using the values from Request, ModelState preserves the users submitted values, allowing them to only make the necessary corrections.

Long and short, you have to be very careful about naming request parameters, ViewBag properties, etc., the same as properties on your model. Probably the simplest solution in your scenario is to just change the request param, userName to something else.

Also, for what it's worth, ModelState is case insensitive, so UserName is the same as userName, username, USERNAME or UsErNaMe.


#2

Good for info. I ended up doing a hidden field for it. Then when the page was loaded value was received form javascript/ajax call.