I am trying to get up to speed with knockout.js. I am brining it into an MVC application. I am trying to reduce the amount of JavaScript our team has to write, especially in large, complicated screens.
At the same time, I want to leverage the power of MVC views. Our application is using DataAnnotations, and I really love the fact that unobtrusive JavaScript validation is automatically injected into my views. I do not want to give this up to use knockout. I could not find any reasonable solution on the internet, so I decided to create my own.
Let’s first walk through standard approach. I am going to use EntityFramework Code First to create a simple screen with a class that has drop down, some strings and a Boolean field. I am going to build UI then for this class with a standard query (list) screen and an edit screen. My class looks as following.
using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace WebDemo.Data { public class Person { [Key] public int PersonId { get; set; } [Required(ErrorMessage = "First name is required")] [StringLength(30, ErrorMessage = "First name cannot be longer than 30 letters")] [DisplayName("First Name")] public string FirstName { get; set; } [Required(ErrorMessage = "Last name is required")] [StringLength(30, ErrorMessage = "Last name cannot be longer than 30 letters")] [DisplayName("Last Name")] public string LastName { get; set; } [DisplayName("Active")] public bool IsActive { get; set; } public int PersonCategoryId { get; set; } [ForeignKey("PersonCategoryId")] public PersonCategory PersonCategory { get; set; } } }
Now I am going to use standard MVC UI to build a screen that uses entity framework to enter Person data. In Controller wizard I am using the following values.
Note: Ordinarily I would have used repository, but in this demo my focus point is not data.
Now let’s take a look at the Edit view for example (the same issues would exist in Create view as well).
@model WebDemo.Data.Person @using (Html.BeginForm()) { @Html.ValidationSummary(true) <fieldset> <legend>Person</legend> @Html.HiddenFor(model => model.PersonId) <div class="editor-label"> @Html.LabelFor(model => model.FirstName) </div> <div class="editor-field"> @Html.EditorFor(model => model.FirstName) @Html.ValidationMessageFor(model => model.FirstName) </div> <div class="editor-label"> @Html.LabelFor(model => model.LastName) </div> <div class="editor-field"> @Html.EditorFor(model => model.LastName) @Html.ValidationMessageFor(model => model.LastName) </div> <div class="editor-label"> @Html.LabelFor(model => model.IsActive) </div> <div class="editor-field"> @Html.EditorFor(model => model.IsActive) @Html.ValidationMessageFor(model => model.IsActive) </div> <div class="editor-label"> @Html.LabelFor(model => model.PersonCategoryId, "PersonCategory") </div> <div class="editor-field"> @Html.DropDownList("PersonCategoryId", String.Empty) @Html.ValidationMessageFor(model => model.PersonCategoryId) </div> <p> <input type="submit" value="Save" /> </p> </fieldset> } <div> @Html.ActionLink("Back to List", "Index") </div>
The first issue is that each entry field take about 6 lines of html. My first step on my way to deal with knockout is centralize the code for fields. What I am going to use is custom templates capability in ASP.NET MVC. I am going to start by creating a template for a text field. To do that I am going to add EditorTemplates folder under ViewsShared folder in my project first.
Now, I am going to create view as a base for my template. I am going to call it TextBox.cshtml.
To get a head start I am going to copy initial html from the Edit view, but them I have to modify because my Model passed to my edit template is just a single field, FirstName of a person for example. Hence, I cannot use TextBoxFor for example, so I am just going to use TextBox method. The other key to creating a successful editor template is the knowledge about metadata for the field. It is available off ViewData project luckily. Also, I have to do a bit of extra work to inject attributes manually, such as “for” attribute into the label. Here is the final product.
<div class="editor-label"> @Html.Label(ViewData.ModelMetadata.DisplayName, new Dictionary<string, object> { { "for", ViewData.ModelMetadata.PropertyName } }) </div> <div class="editor-field"> @Html.TextBox("", (object)Model, new Dictionary<string, object> { { "id", ViewData.ModelMetadata.PropertyName }, { "name", ViewData.ModelMetadata.PropertyName }, { "class", "text-box single-line"} }) @Html.ValidationMessage(ViewData.ModelMetadata.PropertyName, new Dictionary<string, object> { { "data-valmsg-for", ViewData.ModelMetadata.PropertyName } }) </div>
I can now use this editor in my Edit view. To support first name entry for example I now have just one line of code.
@Html.EditorFor(model=>model.FirstName, "TextBox")
I do not quite like the look of this though – I have to rely on magic strings. So, I am going to define an enumeration for custom edit templates and an extension method for EditorFor.
namespace WebDemo.Helper { public enum EditorTemplate { TextBox, CheckBox } }
namespace System.Web.Mvc.Html { public static class CustomHtmlExtensions { public static MvcHtmlString EditorFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, EditorTemplate editorTemplate) { return html.EditorFor(expression, editorTemplate.ToString()); } } }
Now, I can clean up my Edit view code some more.
@Html.EditorFor(model=>model.FirstName, EditorTemplate.TextBox)
Cool so far. Now, checkbox.
<div class="editor-label"> @Html.Label(ViewData.ModelMetadata.DisplayName, new Dictionary<string, object> { { "for", ViewData.ModelMetadata.PropertyName } }) </div> <div class="editor-field"> @Html.CheckBox("", (bool)Model, new Dictionary<string, object> { { "id", ViewData.ModelMetadata.PropertyName }, { "name", ViewData.ModelMetadata.PropertyName }, { "class", "check-box"} }) @Html.ValidationMessage(ViewData.ModelMetadata.PropertyName, new Dictionary<string, object> { { "data-valmsg-for", ViewData.ModelMetadata.PropertyName } }) </div>
Drop down is a bit complicated and now relies on some conventions, just like the Controller does. For example, the drop down data is added to ViewBag in the controller. I changed it a bit to simplify my Html. My Create method looks as follows.
public ActionResult Create() { ViewData["PersonCategoryId"] = new SelectList(db.PersonCategories, "PersonCategoryId", "Name", 0); return View(new Person()); }
As you can see above, I create a list and add it to ViewData using the property name that the drop down is for. Now my template just needs to pull that data back out and call DropDownList method.
<div class="editor-label"> @Html.Label(ViewData.ModelMetadata.DisplayName, new Dictionary<string, object> { { "for", ViewData.ModelMetadata.PropertyName } }) </div> <div class="editor-field"> @Html.DropDownList("", (IEnumerable<SelectListItem>)ViewData[ViewData.ModelMetadata.PropertyName] , new Dictionary<string, object> { { "id", ViewData.ModelMetadata.PropertyName }, { "name", ViewData.ModelMetadata.PropertyName } }) @Html.ValidationMessage(ViewData.ModelMetadata.PropertyName, new Dictionary<string, object> { { "data-valmsg-for", ViewData.ModelMetadata.PropertyName } }) </div>
Now, the view looks extremely clean.
@using WebDemo.Helper @model WebDemo.Data.Person @using (Html.BeginForm()) { @Html.ValidationSummary(true) <fieldset> <legend>Person</legend> @Html.HiddenFor(model => model.PersonId) @Html.EditorFor(model => model.FirstName, EditorTemplate.TextBox) @Html.EditorFor(model => model.LastName, EditorTemplate.TextBox) @Html.EditorFor(model => model.IsActive, EditorTemplate.CheckBox) @Html.EditorFor(model => model.PersonCategoryId, EditorTemplate.DropDown) <p> <input type="submit" value="Save" /> </p> </fieldset> } <div> @Html.ActionLink("Back to List", "Index") </div>
Very nice, right.
To summarize, I just created a set of custom templates I can use in my views. Now, adding knockout binding cannot be simpler. Here is how I would do it for TextBox by simply adding data-bind attribute with the correct value.
<div class="editor-label"> @Html.Label(ViewData.ModelMetadata.DisplayName, new Dictionary<string, object> { { "for", ViewData.ModelMetadata.PropertyName } }) </div> <div class="editor-field"> @Html.TextBox("", (object)Model, new Dictionary<string, object> { { "id", ViewData.ModelMetadata.PropertyName }, { "name", ViewData.ModelMetadata.PropertyName }, { "class", "text-box single-line"}, { "data-bind", "value: " + ViewData.ModelMetadata.PropertyName }, }) @Html.ValidationMessage(ViewData.ModelMetadata.PropertyName, new Dictionary<string, object> { { "data-valmsg-for", ViewData.ModelMetadata.PropertyName } }) </div>
Voila! The code for the dropdown and checkbox is identical. I used some conventions of course, and relied on the fact that DisplayName attribute was defined. You can always customize to suit your needs.
Please let me know what you think about this approach. Feel free to download the demo as it stands today. I will work on adding knockout view model functionality with save in the next post.
Thanks.
Hi Sergey,
Very nice! Do you know if this approach works in ASP.NET MVC4 also?
I’ve been struggling the whole day today with this layout http://forum.jquery.com/topic/why-one-code-always-firing-and-the-other-one-never#14737000003630988
I am wondering if using your approach I can also do something similar for the Phone + Ext together?
Also, do you know where this etc comes from? Is it something defined internally?
Thanks again for your blog and I am going to try to implement it as is in my application also.
Sure, you can use this with MVC 4, my demo an MVC 4 demo anyway. I am not sure about your other questions, I do not think I follow what you are asking..
Thanks.
Yes, I downloaded the demo and looking at it right now.
My question – sort of – say you have Phone and Ext fields in your model and you want to show them as one nice control in UI (e.g.
Phone [ ] Ext [ ]
It is a bit off-topic, but this is what I may need for the project I am working on right now.
Also, part of my original comment got eaten since I used div tag. I was wondering where editor-label and editor-field classes are defied – is it in the core MVC code?
—————————
Microsoft Visual Studio
—————————
Unable to launch the IIS Express Web server.
Sergey,
When I am attempting to run your application by pressing F5 I am getting this error
Failed to register URL “http://localhost:33167/” for site “WebDemo” application “/”. Error description: The process cannot access the file because it is being used by another process. (0x80070020)
I am going to research later on, but may be you know an easy fix?
Never mind the very last comment, I sort of solved it.
You can certainly create custom reusable templates or partial views. Other approach will work for phone/extension. As far as the error goes, I’d restart the studio and/or computer.
Is it OK to have one partial view invoke another? Say, if I create my phone + ext as a partial view, can I insert it into another already partial view (_ClientForm)?
I am also going to try using your editors – I like much cleaner view. One quick question, though:
In the model I used the following annotation:
[Email]
[StringLength(100)]
[Column(“C2_Email”, TypeName = “varchar”)]
public virtual string Email2 { get; set; }
(This is from DataAnnotationsExtensions library). It automatically handles e-mail validation and shows it right away when I navigate to another control. Will your new editors also handle that automatically?
For the labels, I guess we need to adjust this
@Html.Label(ViewData.ModelMetadata.DisplayName,
What if the DisplayName not defined – in this case we need to go with the PropertyName.
I have tested email attribute in your WebDemo and it didn’t work:( How can we incorporate these annotations into using that editor?
BTW, I did fix this for the label
@Html.Label((ViewData.ModelMetadata.DisplayName??ViewData.ModelMetadata.PropertyName),
Sorry, I take it back. It does work correctly, I just installed the wrong package (I needed to install the one with MVC3 at the end). I read it in this Scott Gu blog
http://weblogs.asp.net/srkirkland/archive/2011/02/23/introducing-data-annotations-extensions.aspx
So, this is very nice to have such editors to clean the view code and easily introduce knockout.js
How would we add “editbox” (multi-line) as an editor?
I am having a bit of trouble implementing textarea helper. Its parameters are different than for the TextBox and I am not sure how to implement it.
It is practically the same as textbox
new Dictionary
{
{ “for”, ViewData.ModelMetadata.PropertyName }
})
new Dictionary
{
{ “id”, ViewData.ModelMetadata.PropertyName },
{ “name”, ViewData.ModelMetadata.PropertyName },
{ “class”, “text-box multi-line”},
{ “data-bind”, “value: ” + ViewData.ModelMetadata.PropertyName },
})
@Html.ValidationMessage(ViewData.ModelMetadata.PropertyName,
new Dictionary
{
{ “data-valmsg-for”, ViewData.ModelMetadata.PropertyName }
})
Thanks, it’s working except that I also tried to add
{“cols”,”20″},
{“rows”,”5″},
{“wrap”,”virtual”}
and this didn’t work. Where there attributes should go?
BTW, while researching on ‘template view ASP.NET MVC’ I found this http://www.codeproject.com/Articles/188467/jQuery-Templates-View-Engines-in-ASP-NET-MVC that can help understand the knockout.js concept.
Nice! What about a radio button list?
I tried to go one step farther and somehow validations no longer work. I am now sure what got broken.
I documented this here http://stackoverflow.com/questions/13016674/validations-not-show-up-using-ef-code-first-with-complex-types
@Beezo
Depends on your view model. You can use Html.RadioButton and pass in the name you would like to use for all bool properties that are grouped together. If so, you can always set name for Radio button group to the value you can pass in when calling the EditorFor as dictionary. This will translated into ViewData. So your code will look something like
@Html.RadioButton(
ViewData[“GroupName”],
(bool)Model,
(bool)Model,
new Dictionary
{
{ “id”, ViewData.ModelMetadata.PropertyName },
{“data-bind”, “checked: ” + ViewData.ModelMetadata.PropertyName },
{ “class”, “radio-button”}
})
Do you know if it’s possible to somehow access the column name assigned through the mapping and set Id to be the column’s name instead of the property name?
Say, I have Client class that consists of 2 complex types. If I use the editor as is, the validation messages are not shown.
This is what I have in the mapping
this.Property(t => t.Contact1.Email).IsUnicode(false).HasColumnName(“C1_Email”);
So, in the EditorFor I want to access that column name and use it for Id instead of the EMail property. Do you know if this is possible and how?
What I found is that I can not use these EditorFor for complex types. They work OK for a simple property, but for Complex Types I need to use more verbose syntax otherwise validation messages do not show up. See this thread http://forums.asp.net/t/1855963.aspx/1?Validation+messages+don+t+show+up+what+is+missing+
NAIOMI … “Unable to launch the IIS Express Web server.”
How did you fix it?
I typically just use IIS locally, not express. I try to develop on environment as close to production as I can. I have never seen this error using iIS. Just go to project properties web tab and create a virtual directory, then you can just run it. I am not sure about IIS Express error, but this should get you going.
Great tutorial. Do you have any plans to release this as a nuget package?
So, it appears that the id attribute plays a significant role in this pattern. The behavior I’m seeing is this:
1. when the property being bound does not have an associated Knockout observable, the field is bound to itself resulting in a string value similar of [object HTMLInputElement] or [object HTMLCollection] if there are more than one element on the page with the same id.
2. the assignment of the id attribute in the template does not account for nested view model properties. So, if my vm has two Address properties, the fields within the Address properties will be assigned duplicate id values. Additionally, if the KO vm doesn’t have an associated property, the ApplyBindings command will now fail since KO can’t even self-reference anymore.
Have you come across these issues? If so, have you come up with an elegant solution?
Thanks,
Vinney
Hi Paul,
Sorry, I haven’t visited this blog and didn’t see your question till today. I think I found at that time some solution through internet and also I assigned a specific port to my application. I forgot right now the exact steps of how I did it, but hopefully you can google as how to assign the port.
Hi Sergey,
I want my textbox class to become “smarter”. Can you please see this thread http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/988ddb77-9516-4a22-be03-7b26bf24e32c and perhaps you can help me?
Thanks a lot in advance.
You can always use ViewData.ModelMetadata.ModelType to get conditional code based on property type.
I guess being new to ASP.NET MVC development (and lazy) I hoped to take a shortcut and find a sample. Since I could not find good sample, I’ll try trial and error approach now.
I’ve been looking into this now http://mvchtml5.codeplex.com/ but it’s not clear to me as how to use this and your editors. Say, I added UIHint(“Number”) to my model’s property but I do not see any changes and I still can type anything in the number field. I thought this hint should “automagically” work.
In other words, my new question now is this – how did you create your “EditorFor” (starting from “this is the final product”) and what is the code in the core ASP.NET MVC for this?
I think your own EditorFor (which I’ve been using) is good, but it has certain limitations compared with the “native” EditorFor which can figure out more information from the model. I think it can automatically handle UIHint and DataType. Have you tried taking all the best from the native ASP.NET editor and incorporate into yours?
Do you have an update to that blog post?
I’ve looked into this http://aspnetwebstack.codeplex.com/SourceControl/changeset/view/5d4159c85ff6#src/System.Web.Mvc/Html/TemplateHelpers.cs (and checked other source code) This is quite complicated, but I think that code means that the ASP.NET MVC “native” editor can handle different types and produce the correct HTML, but it will not be lean…
Pingback: KnockoutMVC Extension Support for DataAnnotations C# | El Yacare
What if you want to add a subscribe event to a dropdown using knockout model, how is the registering of the model works?
You can always customize your observable model or add additional attributes to pass to the template or enhance the template to pay attention to new attributes if you implement events via attributes.
Do you have a sample of this?
Not off top of my head. Sorry.
Pingback: Validation in Angular forms – Part 2 | Sergey Barskiy's Blog