Dealing with complex models in ASP.NET MVC

2019-05-10 17:57发布

问题:

I have a model that looks like this:

Business
 - Branch  
   - Phone(*)
     - Phone Type  
     - Number  
   - Opening hours (*)     
     - Days in week
     - Working period (*)
       - From time
       - To time
 - Custom field (*)
   - Name
   - Value
 - Address
   - Address line
   - City
   - State
   - Zip
 - Yada yada

I created Editor Templates for each of the class types above.

I want to have a common Business editor template with a submit form that posts the entire structure to a single action and saves it, both for an existing or new entity.

  1. Is Editor Templates the right approach? How do I submit the form along its entire downline?
  2. How do I make Add and Remove buttons to add/remove phone numbers within the form?
  3. How do I order items in the collection (i.e. I want to have arrows near each phone number so the user can move it up or down in the client list, then handle the saving on server, for that I already have the solution).

Bottom line, my issue is how to get the right values posted back to the server, and how to modify the inner collections on the client. Once the proper data is on the server in this way or another I'll know how to deal with it. My problem is the client side and the correct way of data submission.

Update

I saw this answer, which basically answers the 1st part of my question, tho the latter two still remain (add-remove-order buttons - manage collections on client).
My problem is not how to add/remove/reorder rows at the client's DOM, but how to modify the client data and then receive it in the server in the action the looks like this:

[HttpPost]
public ActionResult Save(Business business)
{
  /// blah blah
}

Update

Here is how I try to shove in the new data:

View:

@Ajax.ActionLink("Add", "AddCustomField", new AjaxOptions { UpdateTargetId = "customFields", InsertionMode = InsertionMode.InsertAfter })

Action:

public PartialViewResult AddOpeningTimes()
{
  var ot = new OpeningTimes();
  ot.WorkingPeriods.Add(new WorkingPeriod());
  var e = EditorFor(ot);
  //just here for debugging, the values are both empty strings
  e.ViewData.TemplateInfo.HtmlFieldPrefix = ViewData.TemplateInfo.HtmlFieldPrefix;
  return e;
}
//this method is on the base controller:
protected PartialViewResult EditorFor<TModel>(TModel model)
{
  return PartialView("EditorTemplates/" + typeof(TModel).Name, model);
}

The thing is the name for the appropriate fields are not enumerated as needed (Branches[0].CustomField[0].Key), instead, it's just Key.

回答1:

As far as i know, there is no 'simple' way to do this.

Add button - you have to wire javascript that creates a part of form (eg. phone type select and phone text box) and set its id/name. Basically you find the last item in the form, which will have name of Phone[x].PhoneType, and set the values on new part of form to appropriate values with x + 1.

An option to avoid generating the part the form by yourself is to create a hidden 'template' and copy that. Then change id and name.

Remove button - if you simply deleted items from DOM, you would create gaps in the sequence and MVC doesn't know how to deal with that. One possible approach is to mark items in the form as deleted using a hidden field, then handling that on the server.

Reordering - I would add a property called Order to whatever needs this feature, then render it as hidden and change using javascript when reordering. You also have to set it appropriately when adding an item.

Useful properties in these situations are also: IsNew, IsUpdated - along with IsDeleted allow for relatively easy processing on the server.

Of course, if you have nested collections each needing add/remove/reorder functionality it will be kind of difficult to do and debug.

UPDATE

The action rendering the partial view can't know what the html prefix should be, because it doesn't have the context (that the parent is Branch object etc.).

If you want to use AJAX, i would recommend sending the html field prefix as a parameter (public PartialViewResult AddOpeningTimes(string htmlPrefix)). htmlPrefix could be Branches[0].CustomField[last_custom_field + 1].. It's probably the cleanest way to achieve what you want, even if it's in fact not very clean.