What can cause an ASP.NET MVC form to not submit / deserialize correctly?

It looks like it should work, but the form is being submitted with a model that hasn't been properly deserialized. Using latest ASP.NET MVC from nuget..NET 4.5

Very standard custom login view / controller.

View:

@model Alertera.Portal.Web.Models.RegisterViewModel
@{
    ViewBag.Title = "Register";
}

<h2>@ViewBag.Title.</h2>

@using (Html.BeginForm("Register", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    <h4>Create a new account.</h4>
    <hr />
    @*@Html.ValidationSummary()*@
    <div class="form-group">
        @Html.LabelFor(m => m.UserName, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.UserName, new { @class = "form-control" })
        </div>
    </div>


    <div class="form-group">
        @Html.LabelFor(m => m.FirstName, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.FirstName, new { @class = "form-control" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(m => m.LastName, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.LastName, new { @class = "form-control" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Register" />
        </div>
    </div>
}

      

Controller:

// POST: /Account/Register
[AcceptVerbs(HttpVerbs.Post)]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new User
        {
            UserName = model.UserName,
            FirstName = model.FirstName,
            LastName = model.LastName
        };

        user.SetEmail(model.Email);

        var result = await _userManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            _bus.Publish(new UserCreated(user));

            await SignInAsync(user, isPersistent: false);
            return RedirectToAction("Index", "Home");
        }
        AddErrors(result);
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

      

All messages come with an empty model or a fake token (it depends on whether I enable or disable validation). I'm just stumped and don't know where to look.

** EDIT **

If you disable antiforgery, ModelState is invalid, all fields in the model are empty, and error messages indicate that fields are required.

I am using Autofac with MVC extensions and the type bind is registered like this:

    builder.RegisterModelBinders(Assembly.GetExecutingAssembly());
    builder.RegisterModelBinderProvider();

      

Autofac works in general as the controller is created correctly and injected with the appropriate dependencies.

Edit 2: Created a custom binder, inheriting from DefaultModelBinder, per sentence so that I can see the transformation. It looks like the bindingContenxt model is null

enter image description here

The view model itself:

public class RegisterViewModel
{
  [Required]
  [Display(Name = "User name")]
  public string UserName { get; set; }

  [Required]
  [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
  [DataType(DataType.Password)]
  [Display(Name = "Password")]
  public string Password { get; set; }

  [Required]
  [DataType(DataType.Password)]
  [Display(Name = "Confirm password")]
  [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
  public string ConfirmPassword { get; set; }

  [Required]
  public string Email { get; set; }

  [Required]
  public string LastName { get; set; }

  [Required]
  public string FirstName { get; set; }
}

      

+3


source to share


1 answer


After a full day of troubleshooting, updating all nuget packages, make sure the web.config is all tight and contains the correct assembly redirects, the routes are neat and even fix the .NET framework and other time consuming and irrelevant activities, I finally figured it out:

A few weeks earlier, we implemented the Autofac binding, which will capture the serialized HttpContext along with other relevant data when the logging system requires it. (Imagine being able to log the request information along with the full exception stack inside the business object without polluting the business logic with session / registration data.) Unfortunately, as part of creating the binding, the HttpContext is serialized by Json.net, not during the event registration, but during the binding.



Apparently when Json.net seralizes the HttpContext, it actually reads the streams inside it for the first time, forcing the submitted form's data to read, so that when the Controller instance is created and the data for it is published, the streams have already been read and the Request.Form colection is empty ...

A simple fix for just creating a delegate to serialize the HttpContext fixes the issue

0


source







All Articles