0

I'm creating a form for register new users, each user is allowed to have many address. (many address to one user). I found this article but doesn't look right for me, because I have a main property which is user and the address has to be related with it. I would like to create something similar for add many address even in the create screen for new user, which doesn't exist the user primary key for these address be related.

http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/

Could someone send an example how is the better way to do it?

this is something very close to my code:

public class Person{
   public int PersonId { get; set; }
   public string Name { get; set; }      
   public virtual ICollection<Address> Addresses { get; set; }
}    
public class Address{
   public int AddressId { get; set; }
   public string Street { get; set; }      

   [ScriptIgnore]
   public virtual Person Person { get; set; }
}
doniyor
  • 36,596
  • 57
  • 175
  • 260
CidaoPapito
  • 572
  • 1
  • 6
  • 21
  • That's also probably not the best example to be using if you're using MVC4 as that's for MVC2 using the web forms style. The Razor engine is much nicer to work with and I would imagine the scaffolding has come on a bit since then. There are plenty of tutorials out there, look for one about strongly typed views in MVC4. – Klors Aug 06 '14 at 08:54
  • @Klors That's another thing that I noted. So, could you suggest me something in razor with deal with this many to one relation? – CidaoPapito Aug 06 '14 at 08:59
  • Is your question about saving a new `person` and their collection of `addresses`, or about generating the view to create new `person`, or about the javascript needed to dynamically add `addresses` –  Aug 06 '14 at 10:50
  • @StephenMuecke Create a view to new person been able to add many address. I think I should use edit templates but I don't know how to use it properly. – CidaoPapito Aug 06 '14 at 11:23

2 Answers2

2

As Klors indicated, create a view models for Address and Person (suggest List<AddressVM> is initialized in PersonVM constructor). In your PersonController

[HttpGet]
public ActionResult Create()
{
  PersonVM model = new PersonVM();
  model.Addresses.Add(new AddressVM()); // add an empty address assuming at least one required
  return View(model, "Edit")
}

[HttpGet]
public ActionResult Edit(int ID)
{
  PersonVM model = // get from database/repository
  // If Address.Count == 0, add new Address
  return View(model);
}

[HttpPost]
public ActionResult Edit(PersonVM model)
{
  ...

In your view

@model PersonVM
@using (Html.BeginForm("Edit", "Person") {
  ....
  @Html.HiddenFor(m => m.ID)
  @Html.TextBoxFor(m => m.Name)
  ... 
  @Html.EditorFor(m => m.Addresses) // you may want to create an editor template for AddressVM
  <button type="button" id="AddAddress">Add address</button>

The EditorFor() will generate HTML similar to this (note the indexer)

<input type="text" name="Address[0].Street" id="Address_0__Street" ..../>
<input type="text" name="Address[0].Suburb" id="Address_0__Suburb" ..../>

To dynamically add new addresses, you need to use JavaScript to generate similar HTML, but increment the indexer. If you use an editor template, you can wrap the Address controls in a container (say <div class="address">) which make them easy to select as per the script below

Script to dynamically add a new address

$('#AddAddress').click(function() {
  var addresses = $('.address');
  // Get the number of existing address
  var count = addresses.length;
  // Get the first address and clone it
  var clone = addresses.first().clone();
  // Update the index of the clone
  clone.html($(clone).html().replace(/\[0\]/g, '[' + count + ']'));
  clone.html($(clone).html().replace(/"_0__"/g, '_' + count + '__'));
  // Add to the DOM
  addresses.last().after(clone);
}

Note this will also clone the values from the first address, so you may want to reset some or all of them, for example (after its been added)

clone.find('input').val('');

If you're using @Html.ValidationMessageFor() methods, note that dynamically added elements will not be validated in the browser unless you parse the form. An example of how to do that can be found here: jquery.validate.unobtrusive not working with dynamic injected elements

Tieson T.
  • 20,774
  • 6
  • 77
  • 92
  • Stephen, I think that I need an Editor Template, I didn't get properly how would work with the Editor Control? Could edit it including the editor template code? Thank you. – CidaoPapito Aug 06 '14 at 18:44
  • 1
    Note 2nd last paragraph. So if you wanted to clear all inputs, then after `addresses.last().after(clone);` add the line `clone.find('input').val('');` –  Aug 06 '14 at 22:40
  • How would I include a remove button (or link) per which address in the view? Any examples? Thank you – CidaoPapito Aug 07 '14 at 13:02
  • You could define a Delete button in your editor template for `AddressVM`, bind its `click` event to a function that finds the closest `div.address` and removes it from the DOM. But now things get harder because indexes are zero based and must be sequential, so if you deleted the 2nd of 5 addresses, you would need to update the html of all subsequent addresses. An alternative would be to just clear all values and hide them, and then when posting back, ignore all `AddressVM` where its properties are null. –  Aug 07 '14 at 13:13
  • I got this issue of indexes... Could give an example of how to solve it? Thank you very much for your time. – CidaoPapito Aug 07 '14 at 17:02
  • The best solution is probably to include an `.Index` property in your editor template, but too much to respond here. Suggest you ask a new question showing what you have done so far. I'll keep an eye out for it. –  Aug 07 '14 at 23:33
1

Something like this might be closer to what you need. You'll need to imagine the checkboxes are collections of fields for entering your address.

However, if you create a ViewModel for Person that contains a list of ViewModels for Address, you can create strongly typed editor templates and display templates named the same as the ViewModels and they'll automatically be picked up if you use @Html.EditorFor and @Html.DisplayFor which makes working with one to many's easier.

If you had files like so -

~/Models/PersonViewModel.cs
~/Models/AddressViewModel.cs
~/Views/Person/Edit.cshtml
~/Views/Shared/DisplayTemplates/AddressViewModel.cshtml
~/Views/Shared/EditorTemplates/AddressViewModel.cshtml

and a person ViewModel a bit like

public class PersonViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    ...
    public List<AddressViewModel> Addresses { get; set; }
}

If you then have an edit view for person like

@model PersonViewModel

<div>
@using (Html.BeginForm("Edit", "Person", new {id = Model.Id}, FormMethod.Post))
{
    <div>
        @Html.EditorForModel()
    </div>
    <div>
        <p>@Html.DisplayNameFor(p => p.Addresses)</p>

        @Html.EditorFor(p => p.Addresses)
    </div>

    <p>
        <input type="submit" value="Save"/>
    </p>
}
</div>

then the editor template should get picked up for the AddressViewModel once for each entry in the list. You'll have to add in some Ajax to allow new addresses to be created like in your example link. As long as the template contains all the fields for the AddressViewModel to work, then your Edit POST controller should just receive a PersonViewModel back as it's parameter.

There are some parts missing from my example, but you should be able to fill in the blanks from tutorials.

Klors
  • 2,665
  • 2
  • 25
  • 42