Sunday, July 3, 2011

AjaxOnly Attribute: ActionFilter vs ActionMethodSelector

Today when finding answer to this question, i came across Mike's blog post. In this post Mike inherited the filter from ActionFilterAttribute and checked request status in onActionExecuting method.
I was about to paste the link then i decided to investigate the mechanism of other attributes like AcceptVerb, HttpPost etc. It turned out that they inherit from ActionMethodSelectorAttribute which is an abstarct class and implement its abtsract method IsValidForRequest

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public sealed class HttpPostAttribute : ActionMethodSelectorAttribute {

        private static readonly AcceptVerbsAttribute _innerAttribute = new AcceptVerbsAttribute(HttpVerbs.Post);

        public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) {
            return _innerAttribute.IsValidForRequest(controllerContext, methodInfo);
        }
    }
Listing 1: Mvc's HttpPostAttribute
After this finding i decided to make a new filter and inherit it from ActionMethodSelectorAttribute so i could implement IsValidForRequest method and it all ended up like
public class AjaxOnlyAttribute : ActionMethodSelectorAttribute 
    {
        public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
        {
            return controllerContext.RequestContext.HttpContext.Request.IsAjaxRequest();
        }
    }
Listing 2: AjaxOnlyAttribute
The end result of Mike's AjaxOnlyAttribute and this one is same but what is the result of using either approach in mvc request pipeline. An excerpt  from InvokeAction method of ControllerActionInvoker class could help us here.
ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
            ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
            if (actionDescriptor != null) {
                FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);

                try {
                    AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
                    if (authContext.Result != null) {
                        // the auth filter signaled that we should let it short-circuit the request
                        InvokeActionResult(controllerContext, authContext.Result);
                    }
                    else {
                        if (controllerContext.Controller.ValidateRequest) {
                            ValidateRequest(controllerContext);
                        }

                        IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
                        ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);
                        InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);
                    }
                }
Listing 3: Excerpt from InvokeAction method
on line 2 of Listing 3 FindAction method of ControllerActionInvoker class is called. This method through series of method calls, calls FindAction method of ActionMethodSelector class. FindAction method in turn runs all the ActionMethodSelecorAttributes on the action method to find out if method is valid for this request.  
It necessarily means that if IsValidForRequest method returns false for any action method it will not be processed further in the request and none of the filters applied to this action method will be executed. 
If AjaxOnly (or any other filter for that matter) is created by inheriting from ActionFilter, AuthorizeFilter, ExceptionFilter or ResultFilter attributes it will be executed later in the request pipeline. This can be observed from code in Listing 3. Information of these filters is acquired on line 4 and until this point all ActionMethodSelector attributes would be executed. 
In the end it is probably safe to state that if AjaxOnlyAttribute inherits from ActionMethodSelectorAttribute mvc will behave as if action method does not exist in case of non-ajax calls. Conversely, if AjaxOnly attribute is implemented by inheriting from ActionFilterAttribute mvc will not allow the action method to execute in case of non-ajax calls. Apparently, there seems to be no consequences of using either approach but i personally feel that this logic belongs to ActionMethodSelectorAttribute

Friday, July 1, 2011

Server side Validation with asp.net mvc ajax and Jquery

These days ajax is extensively used to provide good user experience, especially when there is small amount of data exchange. In asp.net mvc when we post a form using ajax we normally put validation at two places
  1. Client side (for user experience)
  2. Server side (for validation)
Client validation servers the purpose well in most of the cases but its not reliable validation and one can not trust the input coming from the end user that can potentially be tempered by end user.
Suppose we have a small comment form with three input fields Name, Email and CommentText and we want to post it using ajax to asp.net mvc controller action. Furthermore, we have two divs on the page with ids comments and commentform respectively. When this form is post using ajax there are two possibilities.
  • Form is valid: in this case save the comment and return the same comment to client so it could be appended to comments div
  • Form is Invalid: in this case return the form with populated errors and put this response in commentform div

To implement the above mentioned functionality let's have a look at jquery ajax function
<script type="text/javascript">
    $('form').live('submit', function () {
        $.ajax({
            url: this.action,
            type: "post",
            data: $(this).serialize(),
            success: function (data) {
                $("#comments").append(data);
                $('#Name').val('');
                $('#Email').val('');
                $('#CommentText').val('');
            }
        });
        return false;
    });
</script>
Listing 1: Jquery Ajax function
Below is controller action method that will handle the posted form data
[HttpPost]
        public ActionResult comment(Comment _Comment) 
        {
            if (!ModelState.IsValid)
            {
                PartialView("RenderForm", _Comment);
            }
            var comments = Session[_CommentKey] as List<Comment>;
            comments.Add(_Comment);
            Session[_CommentKey] = comments;
            return PartialView("DisplayComment", _Comment);
        }
Listing 2: Controller Action Method
Ajax function in listing 1 presumes that we always have success in controller action method and it will always receive comment that will be appended to the comments div. But, what if modelstate is not valid in controller action and returned response is comment form populated with posted values and error messages. In that case we have no way of knowing which div to populate (append to comments div or repopulate commentform div)
One way of addressing this issue is to create a json object with isSuccess flag set to true or false depending upon the value of modelstate in the controller and second property of this json object will contain the actual response text. Please read stackoverflow question for details. Another way is to set response code other than 200 in controller action method when modelstate is invalid. This will allow us to receive the response text in error callback rather than success callback. Listing 3 and 4 shows revised jquery ajax function and controller action method.
<script type="text/javascript">
    $('form').live('submit', function () {
        $.ajax({
            url: this.action,
            type: "post",
            data: $(this).serialize(),
            success: function (data) {
                $("#comments").append(data);
                $('#Name').val('');
                $('#Email').val('');
                $('#CommentText').val('');
            },
            error: function (xhr, status) {
                $("#commentform").html(xhr.responseText);
            }
        });
        return false;
    });
</script>
Listing 3: Jquery ajax function with error callback
[HttpPost]
        public ActionResult comment(Comment _Comment) 
        {
            if (!ModelState.IsValid)
            {
                Response.StatusCode = 500;
                return PartialView("RenderForm", _Comment);
            }
            var comments = Session[_CommentKey] as List<Comment>;
            comments.Add(_Comment);
            Session[_CommentKey] = comments;
            return PartialView("DisplayComment", _Comment);
        }
Listing 4: Revised ActionMethod
This approach seems cleaner and more elegant to me than creating and sending the json. However, other thoughts, ideas are very much appreciated. Don't forget to download the demo code. In demo project client validation is disabled in web.config for testing purpose
<add key="UnobtrusiveJavaScriptEnabled" value="false"/> 
Listing 5: web.config setting

Thursday, June 30, 2011

Master detail form in aspnet mvc-3 - III - Remove Duplicate IDs

This post is to address a mistake in previous post. In previous post, when adding new rows for detail portion on client side, duplicate id's were generated.

<select name="OrderLines[9ecd00fe-b5cd-420a-9140-f5c35fad9f3c].ProductID" class="input-validation-error" id="OrderLines___randomNumber___ProductID" data-val-required="The ProductID field is required." data-val-number="The field ProductID must be a number." data-val="true">

Listing 1: Select List for Detail Portion
Listing 1 shows that randomNumber is not replaced by generated random number in id attribute of select list which is generated for detail portion of master detail form so it will result in same id for productID select list in each generated row. Let's have a look at jquery template that is rendered when we call RenderClientTemplate method inside jquery template
<SCRIPT id=OrderLine type=text/x-jQuery-tmpl jQuery15103699563721650198="3">
    <div class="editorRow">
    <input type="hidden" name="OrderLines.index" autocomplete="off" value="${randomNumber}" />

Item: <select data-val="true" data-val-number="The field ProductID must be a number." data-val-required="The ProductID field is required." id="OrderLines___randomNumber___ProductID" name="OrderLines[${randomNumber}].ProductID"><option value="">--Select Product--</option>
<option value="1">Printer</option>
<option value="2">Camera</option>
<option value="3">Monitor</option>
</select>

        Quantity: <input data-val="true" data-val-number="The field Quantity must be a number." data-val-required="The Quantity field is required." id="OrderLines___randomNumber___Quantity" name="OrderLines[${randomNumber}].Quantity" type="text" value="0" /> 
        <a href="#" class="deleteRow">delete</a>
        
        <span class="field-validation-valid" data-valmsg-for="OrderLines[${randomNumber}].ProductID" data-valmsg-replace="true"></span>
        <span class="field-validation-valid" data-valmsg-for="OrderLines[${randomNumber}].Quantity" data-valmsg-replace="true"></span>
    </div>
    </SCRIPT>

Listing 2: Template for Detail Portion
in Listing 2 we can see that "randomNumber" is enclosed in ${} symbol in name attribute thus allowing us to use it as jquery template variable and provide this value when materializing the template into html. But, when we have a look at id attribute, "randomNumber" is output in the template as it is. So, it can not be used as template variable and it produces same ids for every field in each detail row when added on client side. For this reason, xhalent's extension method did not work properly. To address this issue, i wrote a helper method to keep id's unique across all rows of detail method
public static string IDFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expr) 
        {
            string prefix = helper.ViewData.TemplateInfo.HtmlFieldPrefix;
            string name = ExpressionHelper.GetExpressionText(expr);
            return (prefix + "__" + name).Replace("[", "__").Replace("]", "__");
        }
Listing 3: Helper Method for ID generation
helper method is quite simple. It is just replacing "[" and "]" symbols with couple of underscores each. The main purpose of this method is to output "randomNumber" as ${randomNumber} so it could be used when materializing template. There are also some changes in orderLine.asx partial view and Product.ascx editor template
Item: <%= Html.EditorFor(x => x.ProductID, new { id = Html.IDFor(x => x.ProductID) })%> 
        Quantity: <%= Html.TextBoxFor(x => x.Quantity, new { size = 4, id = Html.IDFor(x=>x.Quantity) })%> 
        <a href="#" class="deleteRow">delete</a>
        
        <%= Html.ValidationMessageFor(x => x.ProductID) %>
        <%= Html.ValidationMessageFor(x => x.Quantity) %>
Listing 4: OrderLine.ascx Partial View
In partial view, i am explicitly setting ID attribute of html elements by calling Html.IDFor helper method for Quantity field and for ProductID field this value is passed through ViewData which is used in Product.ascx Editor template
<%:Html.DropDownList("", new SelectList((List<MasterDetail.Models.Product>)ViewBag.Products, "ProductID", "ProductName", Model), "--Select Product--", new { id = ViewData["id"] })%>
Listing 5: Product Editor Template
This setup insures that unique ids will be generated for each element of detail part of the form thus avoiding any potential DOM malfunctioning. 

Sunday, May 22, 2011

Master detail form in aspnet mvc-3 - II

In my previous post i discussed how we can add and delete rows in detail portion of master detail form without having to go to server side when new rows are inserted. In this post i will discuss adding new rows on client side for detail portion of the form, using select lists ( or  other editor elements) to insert a new row and client validate dynamically added rows. For this purpose, i will be using unobtrusive js that comes with asp.net mvc-3 and jquery templating engine. I will not reproduce the code from last entry if there is no change so, please go through the post if you haven't already done it.
So, first change is in our OrderLine view model where we want ProductID quantity to be selected through a select list. For this, its been decorated with UIHint attribute with name of the corresponding editor template.
public class OrderLine
{
    [UIHint("Product")]
     public int ProductID { get; set; }
     public int Quantity { get; set; }

}
Listing 1: OrderLine ViewModel
To make Steve's collection helper work with jquery templating engine , i have  made few changes in BeginCollectionItem method
public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName, bool isTemplate = false)
{
    if (isTemplate)
    {
        var randomNumber = "${randomNumber}";
        html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, randomNumber));
        return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, randomNumber));
    }
    else
    {
        var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
        string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();

        // autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
        html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));

        return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
    }
}
Listing 2: Collection Helper
one optional parameter "isTemplate" is added to method's parameter list with default value set to false so it does not affect the code already using this method. when isTemplate is true ${randomNumber} string is used as index of collection item in place of GUID and will produce the output like
<input type= "text" name = "OrderLines[${randomNumber}]" id = "some_Generated_ID" data-*/>
it will help jquery insert the random number value on client side when new row is added. Please visit this article to learn how jquery templating engine works. If value of isTemplate is false or unspecified the ids are generated as usual.
Next, there is only one change in index ActionResult that renders the form
public ActionResult Index()
{
     ViewBag.Products = Session[_productKey] as List<Product>;
     Order _order = new Order { CustomerID = 1, OrderDate = DateTime.Now, OrderID = 1 };
     _order.OrderLines = new List<OrderLine> { new OrderLine { ProductID = 1, Quantity = 3 }, new OrderLine { ProductID = 2, Quantity = 56 } };
     _order.Customers = Session[_customerKey] as List<Customer>; 
     return View(_order);
}
Listing 3: Index ActionResult (HttpGet)
in this method ViewBag.Products property is set to list of Products. This will be used by Product Editor templage that we annotated ProductID property with. Some changes have also been made in index view and OrderLine partial view.
<%Html.BeginForm(); %>
    <div id="orderMaster">
        <div>
            <%:Html.LabelFor(x =>x.OrderID)%>
            <%:Html.TextBoxFor(x=>x.OrderID)%>
        </div>
        <div>
            <%:Html.LabelFor(x =>x.CustomerID)%>
            <%:Html.DropDownListFor(x => x.CustomerID, new SelectList(Model.Customers, "CustomerID", "CustomerName"), "Select Customer", new { @class = "xyz" })%>
        </div>
        <div>
            <%:Html.LabelFor(x =>x.OrderDate)%>
            <%:Html.TextBoxFor(x=>x.OrderDate)%>
        </div>
    </div>
    <div id="orderDetail">
        <%foreach (var x in Model.OrderLines)
          {%> 
             <div class="editorRow">
               <%using(Html.BeginCollectionItem("OrderLines")) {
                   Html.RenderPartial("OrderLine",x);
               }%>
               </div> 
        <%} %>
       
    </div>
    <a href="#" id="add">Add Another</a>
    <input type="submit" value="save" />
    <%Html.EndForm(); %>
    <script id="OrderLine" type="text/x-jQuery-tmpl">
    <div class="editorRow">
    <%using(Html.BeginCollectionItem("OrderLines",true)) { %>
        <%Html.RenderClientTemplate(typeof(MasterDetail.Models.OrderLine), "OrderLine"); %>
        <%} %>
    </div>
    </script>
Listing 4: Index View
First change in index view is at line 19 and 20. I have moved editor row div and BeginCollectionItem method call from partial view to the index view so we can set the value of isTemplate parameter of the method. At line 30 jquery client template that will be used to add rows dynamically on the detail portion of the form. Inside this template same BeginCollectionItem is called but this time with isTemplate value set to true. This will enable us to set values of index on client side. On line 33 RenderClientTemplate helper is called which produces all the inputs (selectlists etc.) required to add an OrderLine type object. Listing 5 shows the code of  RenderClientTemplate helper
public static void RenderClientTemplate(this HtmlHelper helper, Type _type, string _partialViewName) 
{
    object model = Activator.CreateInstance(_type);
    helper.RenderPartial(_partialViewName, model);
}
Listing 5: Client Template Helper
ClientTemplateHelper accepts two inputs (except html helper itself): first is the type of object that we want to render a template for and second is partial view that will be used to render the template. Inside the helper model object is created using reflection and passed to the partial view to render. When this method is called from index view it will render the same partial view that was used for the OrderLine objects present in the Order object. The only difference this time is that the value of index this time will be set to ${randomNumber} allowing us to use this template multiple times on client side.
There are of course some changes in click event handler of anchor tag used to render the new row.
$('#add').live('click', function () {
    obj = { randomNumber: GetRandomGUI() }
    $("#OrderLine").tmpl(obj).appendTo("#orderDetail");
    $.validator.unobtrusive.parseDynamicContent('div.editorRow:last');
    return false;
});
Listing 6: Adding New Row in Javascript
Just line 2 creates an object with property randomNumber that is acquired by a javascript function. Line 3 is generating actual html content by providing the value of obj and appending it to the end of orderDetail div element. Line 4 calls a function that parses newly added html to turn on client validation for them. Listing 7 shows pareseDynamicContent function

(function ($) {
    $.validator.unobtrusive.parseDynamicContent = function (selector) {
        //use the normal unobstrusive.parse method

        //$.validator.unobtrusive.parse(selector);
        $(selector).find('*[data-val = true]').each(function(){
            $.validator.unobtrusive.parseElement(this,false);
        });

        //get the relevant form
        var form = $(selector).first().closest('form');
        
        //get the collections of unobstrusive validators, and jquery validators
        //and compare the two
        var unobtrusiveValidation = form.data('unobtrusiveValidation');
        var validator = form.validate();
        $.each(unobtrusiveValidation.options.rules, function (elname, elrules) {
            if (validator.settings.rules[elname] == undefined) {
                var args = {};
                $.extend(args, elrules);
                args.messages = unobtrusiveValidation.options.messages[elname];
                //alert('here1');
                
                $('[name="' + elname + '"]').rules("add", args);
            } else {
                $.each(elrules, function (rulename, data) {
                    rulename = rulename;
                    data = data;
                    if (validator.settings.rules[elname][rulename] == undefined) {
                        var args = {};
                        args[rulename] = data;
                        args.messages = unobtrusiveValidation.options.messages[elname][rulename];
                        alert('here');
                        
                        $('[name="' + elname + '"]').rules("add", args);
                    }
                });
            }
        });
    }
})($);
Listing 7: JS Function for Switching on Client Validation
This function is taken from xhalent's blog and there you can find more explanation on switching on client validation on dynamically added fields. There is one change that has been made in his function. Line 5 was replaced by Line 6,7 and 8.
Note: I have found conflicting material on turning on client side validation for newly added fields. Brad Wilson says that calling Jquery.validator.unobtrusive.parse() will turn on client validation on newly added html but it did not work for me and i had to use xhalent's function  but it didn't work for me either until i changed line 5 with 6, 7 and 8 in listing 7
Pleas find Demo Code Here
Note: Duplicate ids are generated for similar elements of each row in detail portion of the form resulting in some DOM malfunctions. To address the issue pleas follow this link

Master Detail Form in asp.net Mvc 3 - I

This blog post is basically inspired by steve senderson's blog post on editing variable length list in asp.net mvc and this blog entry is going to be on the same topic as well. in his post he introduced a collection helper that i had been using for long time but it had taken me long time before  i realized the power of this collection helper and things that it can do. we will discuss it later on but first let's have a look at our view model that we want to create in a master-detail format

public class Order
    {
        public int OrderID { get; set; }
        public DateTime OrderDate { get; set; }
        public int CustomerID { get; set; }
        public IEnumerable<customer> Customers { get; set; }
        public IEnumerable<orderline> OrderLines { get; set; }
    }
    public class OrderLine
    {
        public int ProductID { get; set; }
        public int Quantity { get; set; }

    }

    public class Customer 
    {
        public int CustomerID { get; set; }
        public string CustomerName { get; set; }
    }
Listing 1: View Models
Order class has two IEnumerable properties (Customers and OrderLines). Customer property is here to just help render the drop down list so customer ID property can be conveniently filled by the user. Other property (OrderLines) forms the detail portion and contain n entries each for one product. The problem with master detail (or even List binding scenarios explained by steve sanderson) the user can dynamically demand to add or remove rows from detail portion. Now, with the Order viewmodel rendering from for master portion is straight forward but rendring orderLines properties on the same page can get bit tricky. one way of doing this is to use Steve's Collection Helper. In this technique an ajax request is sent to the server to bring an empty form row whenever user wants another row of same type (OrderLine type here) to be filled.
In this post i will analyze how we can do the same task on client side without having to go to server for getting blank inputs (select lists, checkboxes etc.). one way of letting our form inputs to bind with list parameter of ActionResult (or list property of our mode e.g OrderLiens) is to use zero based index with no discontinuity. Using this scheme, if we delete rows from the middle of the form we will have to take care of broken idexes using javascript. Other way of doing this to use a hidden input with block of inputs that belong to the one row. For example
<input type="hidden" name="OrderLines.index"  value="ahdi2391-xyza-bcd0-ski540189xib" />
<input type="text" name = "OrderLines[ahdi2391-xyza-bcd0-ski540189xib].ProductID" id = "ProductID_sdlfk_1244">
<input type="text" name = "OrderLines[ahdi2391-xyza-bcd0-ski540189xib].Quantity" id = "Quantity_122343_skdsd">
Listing 2: Target Html
if html is generated in this way we don't have to bother about managing the index values when rows are deleted from the middle of the collection and i will be following this approach in rest of this writing. First let me reproduce the part HtmlCollection Helper that interests me the most
public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
{
    var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
    string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();

    // autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
       html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));

       return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
}
Listing 3: Collection Helper
Line 3 and 4 in above listing decide which value will be used as index in current item of the list.  Line 3 comes into play when form is rendered at least once and is posted to the controller but we have to re-render the form because of invalid inputs. without this line we will miss track of validtion errors that are added to the modelstate on server side (plz see steve's comments in the code).  Line 4 creates an new GUID to be used as index if the form is being rendered for the first time.
Note: we don't need GUID as index for model binding to work. Any random string that is unique within page's context will server the purpose.
Ok, lets move to the work we have to do to render empty rows on the page using javascript (without ajax calls). First, we'll have a look at Index ActionResult method that accepts Get verbs and is responsible for rendering the form
public ActionResult Index()
{
     Order _order = new Order { CustomerID = 1, OrderDate = DateTime.Now, OrderID = 1 };
     _order.OrderLines = new List<OrderLine> { new OrderLine { ProductID = 1, Quantity = 3 }, new OrderLine { ProductID = 2, Quantity = 56 } };
     _order.Customers = Session[_customerKey] as List<Customer>; 
     return View(_order);
}
Listing 4: Controller's Action Method (HttpGet)
In this method we are just filling couple of entries in OrderLines property and Customer list is retrieved from the Session that was stored there earlier on. Index view in listing bleow is accepting the Order object and rendering the form
<%Html.EnableClientValidation(); %>
    <%Html.BeginForm(); %>
    <div id="orderMaster">
        <div>
            <%:Html.LabelFor(x =>x.OrderID)%>
            <%:Html.TextBoxFor(x=>x.OrderID)%>
        </div>
        <div>
            <%:Html.LabelFor(x =>x.CustomerID)%>
            <%:Html.DropDownListFor(x => x.CustomerID, new SelectList(Model.Customers, "CustomerID", "CustomerName"), "Select Customer", new { @class = "xyz" })%>
        </div>
        <div>
            <%:Html.LabelFor(x =>x.OrderDate)%>
            <%:Html.TextBoxFor(x=>x.OrderDate)%>
        </div>
    </div>
    <div id="orderDetail">
        <%foreach (var x in Model.OrderLines)
          { 
              Html.RenderPartial("OrderLine",x);
        } %>
    </div>
    <a href="#" id="add">Add Another</a>
    <input type="submit" value="save" />
<%Html.EndForm(); %>
Listing 5: Index View
In detail portion of the form we are just iterating over the OrderLines property and rendering it using OrderLines partial view. Here is the code of OrderLines partial view
<div class="editorRow">
    <% using(Html.BeginCollectionItem("OrderLines")) { %>
        Item: <%= Html.TextBoxFor(x => x.ProductID) %> 
        Quantity: <%= Html.TextBoxFor(x => x.Quantity, new { size = 4 }) %> 
        <a href="#" class="deleteRow">delete</a>
        
        <%= Html.ValidationMessageFor(x => x.ProductID) %>
        <%= Html.ValidationMessageFor(x => x.Quantity) %>
    <% } %>
</div>
Listing 6: OrderLine Partial View
Textboxes are rendered for each property of the OrderLine object with their associated vlidationmessage containers and a delete link that will be used to remove the entire row from the form if needed.
In listing 5 we can see an anchor tag towards the end of the page which renders empty row for entering another OrderLine on click event. Listing 7 shows the js code associated with the click event of the anchor tag.
$("#add").live('click', function () {
    var gui = GetRandomGUI();
    var htm = '<div class="editorRow">';
    htm += '<input type="hidden" autoComplete = "off" name = "OrderLines.index" value = "' + gui + '"/>';
    htm += 'Item: <input type = "text" name = "OrderLines[' + gui + '].ProductID" id = "xyzy" />';
    htm += ' Quantity: <input type = "text" name = "OrderLines[' + gui + '].Quantity" size="4" id = "xyzy1" /> <a href="#" class="deleteRow">delete</a></div>';
    $('#orderDetail').append(htm);
    return false;
});
Listing 7: js function for rendering new row
This function is doing nothing special. its just calling a function to get a random number in js and using it to  render html  similar to what was generated on server side. New order lines that are created using this function are bound to the model when we post it back to the controller thus avoiding the need to go to server just to render empty inputs.
Conclusion: we have just discussed the way of creating master-detail form where rows belonging to the detail portion can automatically be added and deleted on client side. This works great for simple scenarios but what if we want ProductID to be selected through a dropdown list. Currently this is not possible with current approach. Moreover, newly added rows to the form do not get client validated.
I will write about these issues in my next post. Meanwhile, you can download the source code from esnips