DreamingOfSleep
DreamingOfSleep

Reputation: 1458

ASP.NET Core - Data values not bound when trying to use a partial view to lay out form controls

I'm trying to create a partial view to save on the amount of boilerplate code I need when creating forms. A simple version of what I have is as follows...

@model FormRowViewModel

<div class="form-group row">
  <label for="@Model.PropertyName" class="col-lg-2 col-form-label">@Model.Label</label>
  <div class="col-lg-10 input-group">
    <input type="text" asp-for="@Model.PropertyName" class="form-control">
  </div>
</div>

...where FormRowViewModel looks like this...

public class FormRowViewModel {
  public FormRowViewModel(string propertyName, string label) {
    PropertyName = propertyName;
    Label = label;
  }

  public string PropertyName { get; set; }
  public string Label { get; set; }
}

The idea is to use it in a view like this...

@model ContactViewModel

<form method="post" asp-controller="Home" asp-action="Index">
  <div asp-validation-summary="All" class="text-danger"></div>
  @await Html.PartialAsync("_FormRow", new FormRowViewModel("UserName", "Your name"))
  @await Html.PartialAsync("_FormRow", new FormRowViewModel("Email", "Email"))
  @await Html.PartialAsync("_FormRow", new FormRowViewModel("Telephone", "Telephone"))
  <div class="form-group row">
    <div class="offset-sm-2 col-lg-10">
      <button type="submit" class="btn btn-primarySubmit</button>
    </div>
  </div>
</form>

This works, in that it creates the HTML (almost) as expected, but has two problems...

1) The generated HTML includes value attributes that set the content of the textboxes to the property names...

<input type="text" class="form-control" id="PropertyName"
   name="PropertyName" value="UserName">

2) Whatever I put in the textboxes, when the form is posted back to the server, the view model properties are all empty strings. Even the property names that were added don't come through.

In case it helps, here is the controller action that handles the view...

public IActionResult Index() => 
  View();

[HttpPost]
public IActionResult Index(ContactViewModel vm) {
  if (!ModelState.IsValid) {
    return View(vm);
  }
  // Next line added so I can see when it worked
  return RedirectToAction(nameof(Privacy));
}

Anyone know what I'm doing wrong? Thanks

Upvotes: 0

Views: 50

Answers (1)

Chris Pratt
Chris Pratt

Reputation: 239430

Your whole approach here is incorrect. It seems what you're looking for is editor templates. Essentially, you need to create partial views in Views\Shared\EditorTemplates that correspond with types or members of the DataType enum, and add your custom HTML there. For example, you can create a String.cshtml view:

@model string
<div class="form-group row">
    <label asp-for="@Model" class="col-lg-2 col-form-label"></label>
    <div class="col-lg-10 input-group">
        <input asp-for="@Model" class="form-control">
    </div>
</div>

Then, for any string property:

@Html.EditorFor(x => x.MyStringProp)

And your custom template will be used, with the proper name binding.

Alternatively, you can create custom taghelpers, but the methodology for that is a bit more complicated, since you'll need to handle the HTML generation in code. If you're interested in that approach, though, look at the source for the built-in tag helpers and create your own based on that.

Upvotes: 1

Related Questions