📜 ⬆️ ⬇️

Dynamic Forms in ASP.NET MVC

Often, the user is required to find out information about several additional objects, the number of which is not known in advance. For this they use dynamic forms, the fields of which are created by javascript code on the client machine. In asp.net mvc, working in the Controller-View bundle, we work with typed objects. The conversion of the values ​​from the request that came from the client into the typed object is handled by the ModelBinder class. For simple objects, this is a rather trivial task. But how to correctly handle dynamic data, the names of the parameters that are unknown in advance. This post is dedicated to solving this problem.



In asp.net, the MVC controller (Controller) notifies the view (View) about changes in the model by passing it a typed data object:
')
public class Customer { public int CustomerID { get; set; } // Unique key public string Name { get; set; } } 


Data transfer to View:
 [AcceptVerbs(HttpVerbs.Get)] public ViewResult CustomerView() { return View(new Customer { CustomerID = 14, Name = "Papa Carlo"}); } 


In the GiftView.aspx view, we display the values ​​of this object:
 <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MyNamespace.Customer>" %> <asp:Content ID="mainContent" ContentPlaceHolderID="MainContent" runat="server"> ID : <%= Html.Encode("CustomerID") %><br/> Name : <%= Html.Encode("Name") %><br/> </asp:Content> 


Also in asp.net MVC there is a separate use of GET and POST request using the AcceptVerbs attribute before the controller's data processing method. The data that arrives at the POST request is processed by the ModelBinder and initializes the object that is passed as a parameter to the method for processing the data:

 [AcceptVerbs(HttpVerbs.Get)] public ActionResult EditCustomer(int idCustomer) { //   Customer     idCustomer return View(Customer); } [AcceptVerbs(HttpVerbs.Post)] public ActionResult EditCustomer(Customer customer) { if (ModelState.IsValid) { //     } return View(customer); } 

Where View for this controller method:

 <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MyNamespace.Customer>" %> <asp:Content ID="mainContent" ContentPlaceHolderID="MainContent" runat="server"> <% using (Html.BeginForm()) { %> <%= Html.Hidden("CustomerID") %><br/> Name : <%= Html.TextBox("Name") %><br/> <input type="submit" value="Save" /> <% } %> </asp:Content> 


It is very convenient to use, but there is one problem when we are faced with filling in dynamic forms. To add additional fields, use javascript, which will generate them. But then how to transfer them to the server correctly in order to get a typed object? There are several solutions to this situation. But first, let's expand our object, with which we work in the form of:

 public class Ownership { public string Name { get; set; } public int Price {get; set; } } public class Customer { ... public List<Ownership> Ownerships { get; set; } } 


First method:
In the POST request handler method, we will not accept any parameters, but manually process the Request.Params property (NameValueCollection).
The second method:
In the handler method of the POST request, we accept the FormCollection parameter, and again we manually process this parameter.
The third method:
We write our version of data processing (ModelBinder) and add a certain type of data to it.

But still there is a fourth way - to provide the correct data names in the view, correctly write the creation of new fields and provide the job of processing them to the standard ModelBinder, and at the entrance to get the already filled object of the type Customer with data. So our EditCustomer View:

 <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MyNamespace.Customer>" %> <asp:Content ID="mainContent" ContentPlaceHolderID="MainContent" runat="server"> <% using (Html.BeginForm()) { %> <%= Html.Hidden("CustomerID") %><br/> Name : <%= Html.TextBox("Name") %><br/> <div class="add-ownership" id="AddOwnership">Add</div> <div id="Ownerships"> <% int index = 0; foreach (var ownership in Model.Ownerships) { %> <div class="ownership-container" id="OwnershipContainer<%= Html.Encode(index) %>"> Name : <%= Html.TextBox("Ownerships[" + index.ToString() + "].Name")%> Price : <%= Html.TextBox("Ownerships[" + index.ToString() + "].Price")%> <div class="remove-ownership">Remove</div> <%} %> </div> <% index++; } %> </div> <input type="submit" value="Save" /> <% } %> // javascript      ( . ) <script type="text/javascript"> var OwnershipsCount = <%= Model.Ownerships.Count.ToString() %>; </script> </asp:Content> 


Now we describe jquery correctly to add and remove additional fields:
 $().ready(function() { ///Add/Remove $("#AddOwnership").click(AddOwnership); $(".remove-ownership").click(RemoveOwnership); }); 


Now we will describe the function to add:
 function AddOwnership() { //  var OwnershipContainer = $("<div/>").attr("class", "ownership-container").attr("id", "OwnershipContainer" + OwnershipsCount).appendTo("#Ownerships"); OwnershipContainer.text(" Name : "); $("<input/>").attr("type", "text").attr("name", "Ownerships[" + OwnershipsCount + "].Name").attr("id", "Ownerships[" + OwnershipsCount + "]_Name").appendTo(OwnershipContainer); OwnershipContainer.text(" Price : "); $("<input/>").attr("type", "text").attr("name", "Ownerships[" + OwnershipsCount + "].Price").attr("id", "Ownerships[" + OwnershipsCount + "]_Price").appendTo(OwnershipContainer); var RemoveButton = $("<div/>").attr("class", "remove-ownership").text("Remove").appendTo(OwnershipContainer); //      -   RemoveButton.click(RemoveOwnership); OwnershipsCount++; } 


In order for the standard ModelBinder to correctly process our array, the array index must go inseparably, and therefore, when we remove the intermediate field, for example, with number 4, then all the subsequent ones must be recalculated.

 function RemoveOwnership() { var RecalculateStartNum = parseInt($(this).parent().attr("id").substring("OwnershipContainer".length)); //  div $(this).parent().remove(); for (var i = RecalculateStartNum+1; i < OwnershipsCount; i++) { //   name  id RecalculateNamesAndIds(i); } OwnershipsCount--; } function RecalculateNamesAndIds(number) { var prevNumber = number - 1; $("#OwnershipContainer" + number).attr("id", "OwnershipContainer" + prevNumber); // "["  "]"    id DOM-  jquery       \\ $("#Ownerships\\[" + number + "\\]_Name").attr("id", "Ownerships[" + prevNumber + "]_Name").attr("name", "Ownerships[" + prevNumber + "].Name"); $("#Ownerships\\[" + number + "\\]_Price").attr("id", "Ownerships[" + prevNumber + "]_Price").attr("name", "Ownerships[" + prevNumber + "].Price"); } 


That's all. Thus, a standard ModelBinder can correctly handle the form fields.

Source: https://habr.com/ru/post/88766/


All Articles