0

I created an editor template to display text boxes included in a form.

    @model WebApplication1.Models.PersonViewModel

<input type="text" class="form-control" name="@ViewData.TemplateInfo.HtmlFieldPrefix" value=""/>

I simply want to include the 'form-control' class in all my input text boxes used to collect data for this model.

public class PersonViewModel
{ 
    [UIHint("_TextFormControl")]
    public string FirstName { get; set; }
    [UIHint("_TextFormControl")]
    public string LastName { get; set; }
    public int Age { get; set; }

}  

This works great when the form starts out blank, however, when I use the template for forms pre-populated with data, like when I want to edit an existing model, I get the error message: "The model item passed into the dictionary is of type 'System.String', but this dictionary requires a model item of type 'WebApplication1.Models.PersonViewModel'."

Here is my controller:

 // GET: /Person/Edit/5
    [HttpGet]
    public ActionResult Edit(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }

        Person person = db.Persons.Find(id);

        if (person == null)
        {
            return HttpNotFound();
        }

        return View(person);
    }

How do I allow this editor to be used with existing data, as well as new forms?

Matt
  • 4,462
  • 5
  • 25
  • 35

1 Answers1

1

You have created an EditorTemplate for typeof PersonViewModel but your use of [UIHint("_TextFormControl")] applied to typeof string means your passing string to the template, not PersonViewModel. Its unclear exactly what your trying to achieve here but based on your current model, he template needs to be

@model String
@Html.TextBox("") // or @Html.TextBoxFor(m => m)

However this is just the same as using @Html.EditorFor(m => m.FirstName) (or TextBoxFor()) in the view. One reason you might do something like this is generate multiple html elements, for example

@model String
@Html.Label("")
@Html.TextBox("")
@Html.ValidationMessage("")

so that using @Html.EditorFor(m => m.FirstName) in the main view would generate the label, textbox and placeholder for validation messages.

But one of the real benefits of using custom editor templates is when you use them for complex types and want a consistent UI for those types. For example, you can create a template for typeof PersonViewModel. In /Views/Shared/EditorTemplates/PersonViewModel.cshtml (note that the name of the template must match the name of the type)

@model WebApplication1.Models.PersonViewModel
<div>
  @Html.LabelFor(m => m.FirstName)
  @Html.TextBoxFor(m => m.FirstName)
  @Html.ValidationMessageFor(m => m.FirstName)
</div>
<div>
  @Html.LabelFor(m => m.LastName)
  @Html.TextBoxFor(m => m.LastName)
  @Html.ValidationMessageFor(m => m.LastName)
</div>
.... // ditto for other properties of PersonViewModel

Then in the main view

@model WebApplication1.Models.PersonViewModel
@Html.EditorFor(m => m)

This will also work for a collection of PersonViewModel and generate one editor template for each PersonViewModel in the collection

@model IEnumerable<WebApplication1.Models.PersonViewModel>
@Html.EditorFor(m => m)

Similarly, if you had another model that contained a property public PersonViewModel Person { get; set; }, you can use @Html.EditorFor(m => m.Person)

Side note:

  1. Your claim that "This works great when the form starts out blank" means you not passing a new instance of PersonViewModel to your view in the 'Create' method which is poor practice. Your controller method should pass an instance to the view.
  2. Your current template where you manually construct an input element would not work because you never set the value attribute. In addition you do not add the data-val-* attributes used for client side validation. Always use the strongly typed html helpers to generate your html so you get correct 2-way model binding and take advantage of all the built-in features of MVC.
  • This was extremely helpful. Thank you. My goal is to apply the "form-control" class - for bootstrap to work - to all input boxes. My thinking was that rather than use @Html.EditorFor(model => model.Name, new{@class = 'form-control'}) for every input box, I would use a template. Does this application of a template make sense? In any case, obviously there is a lot of refactoring ahead of me. – Matt Jun 18 '15 at 14:13
  • Well you could do that - in the first snippet it would be `@model String @Html.TextBoxFor(m => m, new { @class = "form-control" })` but that would be very inflexible - every `string` property would be rendered as a textbox with that class, but there would be no flexibility to modify anything (e.g. add another class name). –  Jun 19 '15 at 01:07
  • A better option is to create a html helper extension method - say `BootStrapTextBoxFor()` which generates the html. [This answer](http://stackoverflow.com/questions/26955073/converting-asp-net-mvc-razor-helper-function-into-a-method-of-a-helper-class/26955246#26955246) gives an example of what is possible (in that case its generating the label, textbox and validation message but you could make separate helper methods for each element). One advantage using extension methods is that you can add other overloads and you can compile them in a separate `.dll` fo use across multiple projects. –  Jun 19 '15 at 01:09