Tag Archives: javascript

Nested Collection Models in ASP.NET MVC 3

Scenario: We want to create form where I can save information about a person. That person has zero or more phone numbers and zero or more email addresses. The customer would like to edit everything about a person all at once, on a single form. This is a dynamic nested model problem. Suppose our person object looks like this:

public class Person {
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public IList<PhoneNumber> PhoneNumbers { get; set; }
  public IList<EmailAddress> EmailAddresses { get; set; }
  public IList<Address> Addresses { get; set; }
}

Let’s start with the controller. When new is called, we need to create a new person, and we want to prepopulate the collections with two telephone numbers, one email address, and one address. I’m going to add a few methods to my Person class to help me out. I’m using C#’s new optional parameters feature here.

public class Person {

  /// <summary>

  /// Add a number of empty phone numbers to a person. The

  /// default is 1.

  /// </summary>

  public void BuildPhoneNumbers(int count = 1) {

    for (int i = 0; i < count; i++) {

      PhoneNumbers.Add(new PhoneNumber());

    }

  }

  /* etc */

}



public class PersonController {

  [HttpGet]

  public ActionResult New() {

    var person = new Person();

    person.BuildPhoneNumber(2); // Add 2 phone numbers

    person.BuildEmailAddress(); // Add an email address

    person.BuildAddress(); // Add one address

    return View(person);

  }

}

We’re also going to make use of the EditorFor HTML helper. This helper is smart enough to iterate over a collection, returning a collection of input elements for each object in the collection. From this point on, I’ll only work with phone numbers, but the example is the same. In New.cshtml, we’ll call EditorForModel(). This will look at the type of model (Person), then go looking in your EditorTemplates folder for a file named Person.cshtml. (Yes, this works the same with *.ascx and *.vbhtml.)

Inside of Person.cshtml, we’re going to call EditorFor(Expression). Again, this looks at the return type of the expression and looks for a file name that matches the type. In this case, that will be PhoneNumber.cshtml. Person.cshtml would do the exact same thing for email address and address collections. You would also build editor templates for EmailAddress.cshtml and Address.cshtml and place those in your EditorTemplates folder.

New.cshtml:

@using (Html.BeginForm("New", "Person", FormMethod.Post))

{

  @Html.EditorForModel()

  <p>

    <button type="submit">

      Create Person

    </button>

  </p>

}



Person.cshtml:

@Html.AntiForgeryToken()

@Html.HiddenFor(x => x.Id)



<p>

  <label>First Name</label>

  @Html.TextBoxFor(x => x.FirstName)

</p>

  

<p>

  <label>Last Name</label>

  @Html.TextBoxFor(x => x.LastName)

</p>



<div id="phoneNumbers">

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

</div>



PhoneNumber.cshtml:

<div class="phoneNumber">

  <p>

    <label>Telephone Number</label>

    @Html.TextBoxFor(x => x.Number)

  </p>

  <br/>

</div>

If we were to stop there, we now how a completely dynamic form were the numbers of phone numbers, addresses, and email addresses were set in the controller. That’s a great start! Let’s look a little closer at what just got generated.

  • We have editors for FirstName and LastName properties in Person.cshtml.
  • We created a container div tag with the ID #phoneNumbers.
  • We called EditorFor(x => x.PhoneNumbers), and that looped through our phone numbers and created two nested editors.

Those nested editors are really important. Let’s inspect those input fields. See how MVC tells us their collection, uses a zero-based index, and tells us the property of the object.

<input type="text" id="PhoneNumbers_0__Number" name="PhoneNumbers[0].Number"/>

<input type="text" id="PhoneNumbers_1__Number" name="PhoneNumbers[1].Number"/>

This is the proper way to post collections back to MVC. We can also inspect the model on the submit. If you’ve done everything correctly so far, you should see POST data that looks like this:

Nested Form Post Data

Of course you know where we’re going with this. We want have some sort of user interaction to dynamically scale these values on the screen. This is usually done with links on the page (and then you can get some fancy CSS to make those links look pretty). Right now, forget the pretty.

Nested Form Screen

Removing a Phone Number

As it turns out, the remove button is really easy, but we need to do some work on our model first. We need to add a boolean property called Delete to flag where or not a record has been marked for deletion. We could send DELETE request to the server as soon as the user clicks the Remove link, but that’s a hard problem. If the user cancels out of the form, what do you do? You’ve already sent the DELETE request. Instead, lets add the Delete property to our phone number. We also need to show this on the form as a hidden field.

PhoneNumber.cs

public class PhoneNumber {

  /* etc */

  public bool Delete { get; set; }

  /* etc */

}



PhoneNumber.cshtml

@Html.HiddenFor(x => x.Delete)

Your database/repository code will need to be smart enough to find the phone numbers marked as Delete == true and either not save (new records) or delete (existing records) when the flag has been set.

Next, we’re going to write an HTML helper for this link. First, here’s the helper.

public static class HtmlHelpers {

  public static IHtmlString LinkToRemoveNestedForm(this HtmlHelper htmlHelper, string linkText, string container, string deleteElement) {

      var js = string.Format("javascript:removeNestedForm(this,'{0}','{1}');return false;", container, deleteElement);

      TagBuilder tb = new TagBuilder("a");

      tb.Attributes.Add("href", "#");

      tb.Attributes.Add("onclick", js);

      tb.InnerHtml = linkText;

      var tag = tb.ToString(TagRenderMode.Normal);

      return MvcHtmlString.Create(tag);

  }

}  

This is what PhoneNumber.cshtml will now look like.

<div class="phoneNumber">

  <p>

    <label>Phone Number</label>

    @Html.TextBoxFor(x => x.Number) 

    @Html.HiddenFor(x => x.Delete, new { @class = "mark-for-delete" })

    @Html.LinkToRemoveNestedForm("Remove", "div.phoneNumber", "input.mark-for-delete")

  </p>

  <hr />

</div>

It looks like we need a JavaScript method called removeNestedForm() here. I usually create an application.js file where my site-wide JavaScript goes.

function removeNestedForm(element, container, deleteElement) {

  $container = $(element).parents(container);

  $container.find(deleteElement).val('True');

  $container.hide();

}

The code here is pretty simple. First, find the parent container of the element. That’s why we had to wrap our phone number with <div class="phoneNumber"></div>. We’re going to find the delete element inside that container and set it’s value to ‘True’. Finally, we’re going to hide the container. After clicking this link, we should see some POST data that demonstrates that this phone number has been marked for deletion. It’s important that we .hide() the block, and not .remove(). If we remove it, it’s gone, and it won’t get POSTed with the form.

Good, we’re making progress. Just one more piece, and that’s the add link. It’s not quite as easy as the remove link, and there’s a little bit of work to do here. I’m going to store the template content in the add link. There are other ways I could do this. I could send a GET request to the server, but this works and allows for maximum code reuse. First, I need to be able to encode my a string to make it JavaScript safe. Not HTML safe – we already have Html.Encode() for that. I want something similar to what Rick Strahl posted back in 2007. Here’s my version.

public static class HtmlHelpers {

  private static string JsEncode(this string s)

  {

    if (string.IsNullOrEmpty(s)) return "";

    int i;

    int len = s.Length;

    StringBuilder sb = new StringBuilder(len + 4);

    string t;



    for (i = 0; i < len; i += 1)

    {

      char c = s[i];

      switch (c)

      {

        case '>':

        case '"':

        case '\\':

          sb.Append('\\');

          sb.Append(c);

          break;

        case '\b':

          sb.Append("\\b");

          break;

        case '\t':

          sb.Append("\\t");

          break;

        case '\n':

          //sb.Append("\\n");

          break;

        case '\f':

          sb.Append("\\f");

          break;

        case '\r':

          //sb.Append("\\r");

          break;

        default:

          if (c < ' ')

          {

            //t = "000" + Integer.toHexString(c); 

            string tmp = new string(c, 1);

            t = "000" + int.Parse(tmp, System.Globalization.NumberStyles.HexNumber);

            sb.Append("\\u" + t.Substring(t.Length - 4));

          }

          else

          {

            sb.Append(c);

          }

          break;

      }

    }    

    return sb.ToString();

  } 

}

I don’t do too much differently. I ignore CRLF, because HTML doesn’t care about newlines. If you care about newlines, then you’ll need to uncomment the stuff at \n and \r. I’ve also compacted some of the redundant operators and removed the leading and trailing quotes. I guess (for now), you’ll have to trust me that my code works. Still, if you want other JavaScript encoding methods, that’s what search engines are for.

Adding a New Phone Number

OK, moving on. We also need a link and a helper for adding a new row. Here’s what Person.cshtml should look like now. Check out the last HTML helper.

@Html.AntiForgeryToken()

@Html.HiddenFor(x => x.Id)

<p>

  <label>First Name</label>

  @Html.TextBoxFor(x => x.FirstName)

</p>  

<p>

  <label>Last Name</label>

  @Html.TextBoxFor(x => x.LastName)

</p>

<hr />

<div id="phoneNumbers">

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

</div>

<p>

  @Html.LinkToAddNestedForm("Add Phone Number", "#phoneNumbers", ".phoneNumber", "PhoneNumbers", typeof(PhoneNumber))

</p>

Here’s what that HTML helper will look like. Yes, it’s got a few more input parameters. Let’s go through them. linkText is obvious. It’s the text that needs to appear on the link. The containerElement is where this block will be inserted in the HTML using a jQuery append method. The counterElement is how we’re going to count the number of items on the form. (Remember that we need to 0-index our properties.) collectionProperty tells us the prefix that needs to be added to the generated HTML elements. We didn’t need to worry about the prefix when calling the EditorFor method from the HTML source. Here, though, we’re not as lucky and need to do a little bit of MVC’s work ourselves. The helper method creates an instance of the object, generates the form using EditorFor.

public static IHtmlString LinkToAddNestedForm<TModel>(this HtmlHelper<TModel> htmlHelper, string linkText, string containerElement, string counterElement, string collectionProperty, Type nestedType) {

  var ticks = DateTime.UtcNow.Ticks;

  var nestedObject = Activator.CreateInstance(nestedType);

  var partial = htmlHelper.EditorFor(x => nestedObject).ToHtmlString().JsEncode();

  partial = partial.Replace("id=\\\"nestedObject", "id=\\\"" + collectionProperty + "_" + ticks + "_");

  partial = partial.Replace("name=\\\"nestedObject", "name=\\\"" + collectionProperty + "[" + ticks + "]");

  var js = string.Format("javascript:addNestedForm('{0}','{1}','{2}','{3}');return false;", containerElement, counterElement, ticks, partial);

  TagBuilder tb = new TagBuilder("a");

  tb.Attributes.Add("href", "#");

  tb.Attributes.Add("onclick", js);

  tb.InnerHtml = linkText;

  var tag = tb.ToString(TagRenderMode.Normal);

  return MvcHtmlString.Create(tag);

}

So now we’ve got our link that says “Add Phone Number” on our screen. Here’s the HTML link that gets rendered by the above code. We do need to do a little bit of string magic. The elements generated by the EditorFor will be prefixed with nestedObject, and not PhoneNumbers[index] like we want. Our helper can change the text, but not the index. The lines with the “replace” method take care of making sure that our collection properties are correctly named. Instead of an index, though, I’m putting the system clock time in there. We’ll let JavaScript take care of figuring out what the real index should be. That’s where the addNestedForm() method comes in.

function addNestedForm(container, counter, ticks, content) {

  var nextIndex = $(counter).length;

  var pattern = new RegExp(ticks, "gi");

  content = content.replace(pattern, nextIndex);

  $(container).append(content);

} 

Refrence:-

http://jarrettmeyer.com/post/2995732471/nested-collection-models-in-asp-net-mvc-3

ASP.NET MVC: Using Ajax, Json and PartialViews

While working on a new ASP.NET MVC project, I had a simple objective: add a new record and refresh my View. After sifting through several sites I found several resources that lead me to accomplish my goal.

I’ve compiled my result into a new sample MVC Application that I created in Visual Studio that you can download here. I’ll explain what I did using that as my reference. It’s a trimmed down version of what I did on the actual project, but it will get the point across.

Let’s assume we want to have a View that lists some People from our data source. I started out by creating a Person data model:

public class Person
{
  public Guid Id { get; set; }
  public String FirstName { get; set; }
  public String LastName { get; set; }

  public Person()
  {
    Id = Guid.NewGuid();
  }
}

Next, I created some ViewModels so that I can work with strongly typed Views:

public class PersonIndexViewModel
  {
    public PersonListViewModel PersonListModel { get; set; }

    public AddPersonViewModel AddPersonModel { get; set; }
  }

  public class PersonListViewModel
  {
    public List PersonList { get; set; }
  }

  public class AddPersonViewModel
  {
    public String FirstName { get; set; }
    public String LastName { get; set; }
}

Next, I added a People folder in my Views folder and created a strongly typed Index View on my PersonIndexViewModel. I started out with building a table and doing a foreach to display each Person object. I moved that into a PartialView (.ascx) by creating a ParitialView in my Views/Shared folder (This blog post is very helpful for showing you how to use PartialViews). I called that PersonList.ascx and that is a strongly typed partial view on my PersonListViewModel.

Now, I can update my View to display that PartialView with this code:

<% Html.RenderPartial("PersonList", Model.PersonListModel); %>

Next, I want to be able to perform a delete action to remove a Person from the DB. You’ll notice I’m using an Ajax.ActionLink in my PersonList PartialView so that I can perform the delete with Ajax.

<%= Ajax.ActionLink("delete", "JsonDelete", "People",
new { Id = person.Id },
new AjaxOptions { Confirm = "Are you sure you want to Delete this Person? This action cannot be undone.",
HttpMethod = "Delete",
OnComplete = "JsonDelete_OnComplete" })%>

In the ActionLink, I specify the Action I want to call, pass the Person.Id and in the AjaxOptions I defined a JavaScript method that should be called on complete. In my People Controller I can now add the JsonDelete method:

[AcceptVerbs(HttpVerbs.Delete)]
public JsonResult JsonDelete(Guid Id)
{
  // call your Repository to delete the Person
  bool result = _personList.Remove(toDelete);

  // return a Json Object, you could define a new class
  return Json(new
  {
    Success = result,
    Message = result ? "The person has been deleted!" : "Error!"
  });
}

You would call your Repository to delete that Person and then return a new Json Object. What I did was define a couple of properties that I will reference from the JavaScript function to give feedback to the user. Here is the JavaScript function:

function JsonDelete_OnComplete(context) {

  var JsonDelete = context.get_response().get_object();

  if (JsonDelete.Success) {
    $(this).parents('tr.item').remove();
  }

  $("#message").html(JsonDelete.Message);
}

I found this link that showed me how to use “context.get_response().get_object();” to get the Json Object in JavaScript.

Now that I can delete, the next logical step would be the ability to add a new Person. I’ll start out by creating a new form that uses my AddPersonViewModel Model:

<% using (Ajax.BeginForm("JsonAdd", "People", new AjaxOptions { OnComplete = "JsonAdd_OnComplete" }))
 {%>
<fieldset>
  <legend>Add a Person</legend>
  <%= Html.LabelFor(model => model.AddPersonModel.FirstName)%>:
  <%= Html.TextBoxFor(model => model.AddPersonModel.FirstName, new { @class = "firstname" })%>
  <%= Html.ValidationMessageFor(model => model.AddPersonModel.FirstName)%>

  <%= Html.LabelFor(model => model.AddPersonModel.LastName)%>:
  <%= Html.TextBoxFor(model => model.AddPersonModel.LastName, new { @class = "lastname" })%>
  <%= Html.ValidationMessageFor(model => model.AddPersonModel.LastName)%>

  <input id="AddBtn" name="AddBtn" type="submit" value="Add" />
</fieldset>
<% } %>

Again, I use the Ajax.BeginForm to set the Action to call and define a JavaScript function to call on complete. To my Controller I add:

public JsonResult JsonAdd(AddPersonViewModel AddPersonModel)
{
  ...

  Person newPerson = new Person
  {
    FirstName = AddPersonModel.FirstName,
    LastName = AddPersonModel.LastName
  };

  // call your Repository to add the new Person
  _personList.Add(newPerson);

  // return a Json Object, you could define a new class
  return Json(new
  {
    Success = true,
    Message = "The person has been added!",
    PartialViewHtml = RenderPartialViewToString("PersonList", new PersonListViewModel {PersonList = _personList})
  });
}

One important thing here is the method “RenderPartialViewToString”. I ran across this which was a tremendous resource in solving my problem here, which was returning a Json Object with a rendered PartialView so that I could use JavaScript/jQuery to update the page.

The post I referenced above showed where you needed to create a base Controller to inherit from and that Controller defines the methods which will return a PartialView as an HTML string:

public abstract class BaseController : Controller
  {
    protected string RenderPartialViewToString()
    {
      return RenderPartialViewToString(null, null);
    }

    protected string RenderPartialViewToString(string viewName)
    {
      return RenderPartialViewToString(viewName, null);
    }

    protected string RenderPartialViewToString(object model)
    {
      return RenderPartialViewToString(null, model);
    }

    protected string RenderPartialViewToString(string viewName, object model)
    {
      if (string.IsNullOrEmpty(viewName))
        viewName = ControllerContext.RouteData.GetRequiredString("action");

      ViewData.Model = model;

      using (StringWriter sw = new StringWriter())
      {
        ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
        ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
        viewResult.View.Render(viewContext, sw);

        return sw.GetStringBuilder().ToString();
      }
    }
}

Now with my JavaScript function is called, I can again reference my Json Object and then update the page:

function JsonAdd_OnComplete(context) {

  var JsonAdd = context.get_response().get_object();

  if (JsonAdd.Success) {
    $("#PersonList").html(JsonAdd.PartialViewHtml);
  }

  $("#message").html(JsonAdd.Message);
}

With this line:

$("#PersonList").html(JsonAdd.PartialViewHtml);

I have a div tag that surrounds my Html.RenderPartial call and I can use jQuery to just replace the HTML. Remember JsonAdd.PartialViewHtml contains the entire HTML of the newly rendered PartialView that we called from the Controller:

return Json(new
{
  Success = true,
  Message = "The person has been added!",
  PartialViewHtml = RenderPartialViewToString("PersonList", new PersonListViewModel {PersonList = _personList})
});

That just about sums it up how to use Ajax, jQuery, Json, and PartialViews in an effective manor in an ASP.NET MVC application.

Refrence:-

http://www.atlanticbt.com/blog/asp-net-mvc-using-ajax-json-and-partialviews/