ASP.NET MVC 3 - edit items dynamically added to model collection in jquery dialog - asp.net-mvc-3

I'm new to MVC, so I wasn't sure what the best approach would be here.
I have a view model that contains several collections like this:
public class MainViewModel{
public List<AViewModel> A { get; set; }
public List<BViewModel> B {get; set; }
...}
I'm using Steve Sanderson's approach here to dynamically add items to a collection, and it's working fine as long as the child items are editable on the main view.
The problem I'm having is returning a read only list with an edit link that will open the details to edit in a popup dialog.
Since these items may be newly added, I can't use the ID property to return a partial view from the controller. It seems like I'll have to render the editors in a hidden div like this:
<div class="AEditorRow">
#using (Html.BeginCollectionItem("A"))
{
#Html.DisplayFor(l => l.ID)
#Html.DisplayFor(l => l.Name)
#Html.DisplayFor(l => l.Code)
edit <text>|</text>
delete
<div class="ADetails" style="display: none">
#using (Html.BeginForm("EditA", "Controller"))
{<fieldset>
<legend>Location</legend>
#Html.HiddenFor(model => model.ID)
<div class="editor-label">
#Html.LabelFor(model => model.Code)
</div>
Does anyone know of a better way to do this?

After working on this issue for a while now I was able to find a walk-through that worked for me.
http://jarrettmeyer.com/post/2995732471/nested-collection-models-in-asp-net-mvc-3
I think this is the most applicable technique for accomplishing dynamically added nested collection objects for MVC3. Most of the other suggestions I've found were meant for MVC2 or MVC1, and it seems that every iteration of MVC the best way to accomplish this changes slightly.
Hopefully this works for you.

I have the same question. Now looking for solution.
Seems like this resources can help:
http://www.joe-stevens.com/2011/06/06/editing-and-binding-nested-lists-with-asp-net-mvc-2/
http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
Model binding nested collections in ASP.NET MVC

Related

Dynamically Add Rows when using Editor Templates and EditorFor in MVC 3

Expanding on a previous question, I'm using EditorFor to edit a list of objects of type (let's say) Hobby.
Now, I need to implement functionality to add editors for individual Hobby objects, so that the user can add additional hobbies.
I read Steven Anderson's blog about how to do that in MVC 2. However, he's rendering the individual Hobby instances using a loop and RenderPartial, rather than using EditorFor. I understand his approach of making Ajax calls to render another partial view and inserting the view result into the DOM, but don't know how to apply the Ajax add in my case, with EditorFor.
His code:
<% foreach (var item in Model)
Html.RenderPartial("GiftEditorRow", item);
%>
My code is using EditorFor like this:
// Model is a List<Hobby>
#Html.EditorFor(model => model.AllowedSurveys)
How do I add an additional Hobby editor, given that the editor is implemented as an Editor Template rather than as a Partial View?
You could replace your editor template with a partial:
#foreach (var item in Model.AllowedSurveys)
{
Html.RenderPartial("_Hobby", item);
}
and inside the partial (~/Views/controllerName/_Hobby.cshtml):
#model Hobby
<div class="editorRow">
#using(Html.BeginCollectionItem("AllowedSurveys"))
{
#Html.EditorFor(x => x.Name)
...
}
</div>
The Html.BeginCollectionItem custom helper is used to generate names with Guids and allow for reordering and adding new items to the collection using javascript. You can download it from the updated article. The one you were reading was for ASP.NET MVC 1 and is obsolete.

What's the proper way to add a form to the Index page that posts to the Create action, including validation?

I have an Index view in my application that shows a list of vendors. I also want to add a small form to add new items right on that page. The create form will post to the Create action. My model class contains a list of vendors, plus one property for a single vendor named NewVendor.
public IEnumerable<SoftwareVendor> Vendors { get; set; }
public SoftwareVendor NewVendor { get; set; }
The SoftwareVendor class has validation attributes. It's an Entity Framework class.
Making a form that posts to the Create action is easy:
#using (Html.BeginForm( "Create", "Vendor" )) {
#Html.ValidationSummary(true)
<fieldset>
<legend>New Software Vendor</legend>
<div class="editor-label">
#Html.LabelFor(model => model.NewVendor.Name)
</div>
<div class="editor-field">
#Html.EditorFor( model => model.NewVendor.Name )
#Html.ValidationMessageFor( model => model.NewVendor.Name )
</div>
<br />
<input type="submit" value="Create" />
</fieldset>
}
This posts just fine, and client-side validation also works. However, the default Create action takes an instance of SoftwareVendor and is looking for a key in the form collection called "Name". Instead, the above form posts "NewVendor.Name".
I can remedy this by specifying a template and field name in #Html.EditorFor.
#Html.EditorFor( model => model.NewVendor.Name, "string", "Name" )
Now the Create action is happy because the "Name" value is being received. However, the validation message is broken because it is still looking for a field named "NewVendor.Name", and there seems to be no way to override this.
<span class="field-validation-valid" data-valmsg-for="NewVendor.Name" data-valmsg-replace="true"></span>
Is there something simple I'm missing to make this work?
Here is a list of things I can do to solve this:
Have my Create action take an instance of my Index model instead of a SoftwareVendor. I still have a traditional Create view, though, and I don't want to do this.
Don't have my Create action take any parameters. Instead, manually look at the form keys and pull the name from either "Name" or "NewVendor.Name", whichever is there.
Have the Create action take both model classes and detect which one got populated properly. This is a lot like #2 but I'm checking properties for non-null values instead of checking the form collection.
Figure out how to make a model binder that will perform what #2 is doing. This seems overly complicated, and I'm going to have this problem in a number of pages, so I'm hoping for an easier way.
Use javascript to make the post instead of a form submit, so I can control the exact field names I'm posting. This works, but I'd prefer to leverage an HTML form, since that's what it's for.
Use the overload of EditorFor to specify the field name, and create the validation message manually.
Write my own extension method on HtmlHelper for a new ValidationMessageFor that can override the field name.
Of these options, #2 or #5 are the ones I think I'd choose from unless there's a better way.
Well, this worked:
#Html.EditorFor( model => model.NewVendor.Name, "string", "Name" )
#Html.ValidationMessage( "Name" )
Since my only real problem with my above code was a broken validation message, this seems to solve my problem. I'm still curious if there is a better solution overall.

My controller viewmodel isn't been populated with my dynamic views model

Im creating an application that allows me to record recipes. Im trying to create a view that allows me to add the basics of a recipe e.g. recipe name,date of recipe, temp cooked at & ingredients used.
I am creating a view that contains some jquery to load a partial view clientside.
On post im having a few troubles trying to get the values from the partial view that has been loaded using jquery.
A cut down version of my main view looks like (I initially want 1 partial view loaded)
<div id="ingredients">
#{ Html.RenderPartial("_AddIngredient", new IngredientViewModel()); }
</div>
<script type="text/javascript">
$(document).ready(function () {
var dest = $("#ingredients");
$("#add-ingredient").click(function () {
loadPartial();
});
function loadPartial() {
$.get("/Recipe/AddIngredient", {}, function (data) { $('#ingredients').append(data); }, "html");
};
});
</script>
My partial view looks like
<div class="ingredient-name">
#Html.LabelFor(x => Model.IngredientModel.IngredientName)
#Html.TextBoxFor(x => Model.IngredientModel.IngredientName)
</div>
<div class="ingredient-measurementamount">
#Html.LabelFor(x => Model.MeasurementAmount)
#Html.TextBoxFor(x => Model.MeasurementAmount)
</div>
<div class="ingredient-measurementtype">
#Html.LabelFor(x => Model.MeasurementType)
#Html.TextBoxFor(x => Model.MeasurementType)
</div>
Controller Post
[HttpPost]
public ActionResult Create(RecipeViewModel vm,IEnumerable<string>IngredientName, IEnumerable<string> MeasurementAmount, IEnumerable<string> MeasurementType)
{
Finally my viewmodel looks like
public class IngredientViewModel
{
public RecipeModel RecipeModel { get; set; }
public IEnumerable<IngredientModel> Ingredients { get; set; }
}
My controller is pretty ugly......im using Inumerble to get the values for MeasurementAmount & MeasurementType (IngredientName always returns null), Ideally I thought on the httppost Ingredients would be populated with all of the on I would be able Ingredients populated
What do I need to do to get the values from my partial view into my controller?
Why don't you take a look at the MVC Controlstoolkit
I think they would do what you want.
Without getting in too much detail. Can you change the public ActionResult Create to use FormCollection instead of a view model? This will allow you to see what data is coming through if any. It would help if you could post it then.
Your view model gets populated by using Binding - if you haven't read about it, it might be a good idea to do that. Finally I would consider wrapping your lists or enums into a single view model.
Possible Problem
The problem could lay with the fact that the new Partial you just rendered isn't correctly binded with your ViewModel that you post later on.
If you inspect the elements with firebug then the elements in the Partial should be named/Id'ed something like this: Ingredients[x].Property1,Ingredients[x].Property2 etc.
In your situation when you add a partial they are probably just called Property1,Property2.
Possible Solution
Give your properties in your partial the correct name that corresponds with your List of Ingredients. Something like this:
#Html.TextBox("Ingredients[x].Property1","")
Of, after rendering your partial just change all the names en ID's with jquery to the correct value.
It happens because fields' names from partial view do not fit in default ModelBinder convention. You should analyze what names fields have in your partial view.
Also you should implement correct way of binding collections to MVC controller. You could find example in Phil's Haack post
Assuming RecipeViewModel is the model being supplied to the partial view, try just accepting that back in your POST controller like this:
[HttpPost]
public ActionResult Create(RecipeViewModel vm)
{
//
}
You should get the model populated with all the values supplied in the form.

ASP.NET MVC 3 - Validation Question

Good evening everyone I have a question regarding validation of drop-down list values. I have a view that is bound to a view model type called ReservationData.
This object contains a property CustomerVehicles of type List<VehicleData>. VehicleData has two int properties VehicleMakeId and VehicleModelId.
On my view I am trying to loop over the number of items in the CustomerVehicles collection and displaying two dropdowns for each, a vehicle make dropdown and a vehicle model dropdown using DropDownListFor.
When I try to submit and validate I do not see any validation errors displayed on the screen.
Just in case you are wondering I have added a ValidationMessageFor for each dropdown as well. I am not sure if this is an issue with the structure of my view model and its complexity and how the controls need to be named or how the ids need to be set. Any help would be greatly appreciated.
Here is the code for the looping over the collection:
#for (var i = 0; i < Model.CustomerVehicles.Count(); i++)
{
var vehicleNumber = i + 1;
<div class="vehicle-selection-wrapper">
<div class="content-container">
<h3>
Vehicle #vehicleNumber</h3>
<img class="vehicle-image" alt="manufacturer image" src="#Url.Content("~/Content/images/default-vehicle.gif")" /><br />
#Html.LabelFor(m => m.CustomerVehicles[i].VehicleMakeId)
#Html.DropDownListFor(m => m.CustomerVehicles[i].VehicleMakeId
, new SelectList(Model.VehicleMakes, "Id", "Name")
, #UIDisplay.Dropdown_DefaultOption, new { #class = "long-field" })<br />
#Html.ValidationMessageFor(m => m.CustomerVehicles[i].VehicleMakeId)<br />
#Html.LabelFor(m => m.CustomerVehicles[i].VehicleModelId)
#Html.DropDownListFor(m => m.CustomerVehicles[i].VehicleModelId
, new SelectList(new List<CWR.Domain.VehicleModel>(), "Id", "Name")
, #UIDisplay.Dropdown_DefaultOption, new { #class = "long-field" })
#Html.ValidationMessageFor(m => m.CustomerVehicles[i].VehicleModelId)
</div>
</div>
}
Ok so I also noticed that in the generated HTML the selects that are generated are missing the HTML5 data-val attributes that are associated to elements to handle validation. Here is the generated HTML
<select class="long-field" id="CustomerVehicles_0__VehicleMakeId" name="CustomerVehicles[0].VehicleMakeId"><option value="">-- Select --</option>
</select><br />
<span class="field-validation-valid" data-valmsg- for="CustomerVehicles[0].VehicleMakeId" data-valmsg-replace="true"></span><br />
<label for="CustomerVehicles_0__VehicleModelId">Model</label>
<select class="long-field" id="CustomerVehicles_0__VehicleModelId" name="CustomerVehicles[0].VehicleModelId"><option value="">-- Select --</option>
</select>
<span class="field-validation-valid" data-valmsg-for="CustomerVehicles[0].VehicleModelId" data-valmsg-replace="true"></span>
Additionally in my VehicleData class the VehicleMakeId and VehicleModelId properties are decorated with a Required attribute.
UPDATE:
Ok so I was testing and noticed that if I keep my code identical except I swap the Html.DropdownListFor calls with Html.TextboxFor calls then the validation works. What could be causing this? Could it be a framework bug with the unobtrusive validation?
UPDATE: Contains Fix
So after posting this same question on the ASP.NET Forums, I was able to get a solution. In the post you will be able to see that there is a bug in the unobtrusive validation framework and how it handles validation of dropdownlists. The user counsellorben does a good job in explaining the problem as well as a solution (including sample code) that will assist others in avoiding this issue in the future, or at least until Microsoft builds in a fix in to the framework.
Thank you everyone for your assistance.
I too have come across this obviously massive oversight regarding client side validation with dropdownlists in MVC 3 and the best solution I can offer is to put the missing HMTL attributes in yourself.
In your view model create a property like this.
public Dictionary<string, object> CustomerVechicleAttributes
{
get
{
Dictionary<string, object> d = new Dictionary<string, object>();
d.Add("data-val", "true");
d.Add("data-val-required", "Please select a Vechicle.");
return d;
}
}
Then in your code, enter
#Html.DropDownListFor(m => m.CustomerVehicles[i].VehicleMakeId
, new SelectList(Model.VehicleMakes, "Id", "Name")
, #UIDisplay.Dropdown_DefaultOption,
**Model.CustomerVechicleAttributes** })
Just add the Model.CustomerVechicleAttributes as htmlAttributes to your dropdownlist.
This will inject the necessary attributes that are missing. You will of course need to add any other attributes you may need like your class attribute.
Hope this helps.
This is the simpliest way I found to do it, just adding data-val-*-* attributes in HtmlAttributes of DropDownListFor, inside the view. The following method works with RemoteValidation too, if you do not need remote validation, simply remove the elements containing data-val-remote-*:
#Html.DropDownListFor(m => m.yourlistID, (IEnumerable<SelectListItem>)ViewBag.YourListID, String.Empty,
new Dictionary<string, object>() { { "data-val", "true" },
{ "data-val-remote-url", "/Validation/yourremoteval" },
{ "data-val-remote-type", "POST" }, { "data-val-remote-additionalfield", "youradditionalfieldtovalidate" } })
I hope it may help. Best Regards!
you should try to add data annotations on your view model properties first so you could see the validation messages.
you might find what you need here
http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx
or create custom ones if needed.
what exactly do you need to validate?
I had exactly the same problem with the field getting correctly validated in TextBoxFor but not in DropDownListFor.
#Html.DropDownListFor(m => m.PaymentTO.CreditCardType, Model.CreditCardTypeList, "Select Card Type", new { style = "width:150px;" })
Since I had another DropDownListFor working on the same page, I knew that it wasn’t a generic DropDownListFor problem. I also have a complex model and parent object PaymentTO wasn’t initialized. When I set viewTO.PaymentTO = new PaymentTO(); in the Controller, the validation for the DropDownListFor started to work. So there is probably a problem with DropDownListFor, but the fix can be as simple as initializing the object in the controller.

Passing values between View and Controller in MVC 2

I'm constantly confused about how to pass values between Views and Controllers in MVC. I know I can set ViewData in the Controller and use that in the View, but what about the other way around?
What I have found is I can use a hidden field and then access it through Request.Form["name"] like this:
<% using (Html.BeginForm("Upload", "Customers", FormMethod.Post, new { enctype = "multipart/form-data" }))
{%>
<br />
<input id="currentDir" type="hidden" name="currentDir" value="" />
<input type="file" name="fileTextbox" id="fileTextbox" />
<br />
<br />
<input type="submit" value="Send" />
<% } %>
What complicates it even more is that the value originally comes from a jquery script, so that's why the input field was the only way I could think of. But it still feels wrong... Maybe it isn't but I'd basically like to know if there are other more "proper" established ways to pass values between the View and Controller (both ways). Should one use querystrings instead? If so, how would they look in the html.beginform htmlhelper?
Also, what I'm trying to do here is enable upload possibilities for my application. And I'm trying to make the whole application as "Ajaxy" as possible. But this form will make a complete post. Is there another way to do this and not have to reload the entire page for this upload?
Let's ignore the "AJAX-y" aspects for a moment (because that's a different issue) and just look at passing data between views and controllers. I would first recommend that you check out the NerdDinner Tutorial which provides some good insights into how MVC works and how you use some of the features of MVC.
To address your specific question of how data is passed from View to Controller and back, there are a few ways to do this. However, the one that tends to make sense to most people is the idea of using strongly-typed views.
Let's say you have a model called Person. For now, don't worry about how we store Person data - we just have a Person class in the Models folder inside your MVC project.
public class Person {
public string FirstName;
public string LastName;
public Person() {
FirstName = "John";
LastName = "Doe";
}
}
When we want to display data about Person in a View, we make a request to a specific controller. In this case (and for clarity) we'll call this controller the MainController. This will go in the Controllers folder and will be called MainController. Let's call the Action (an action is really just a specialized method) we want to get data from Index. Due to how ASP.NET MVC routing works, the path to our server will be: http://localhost/Main/Index. Notice the Controller (minus the "Controller" name), and the Action make up the path. (The first part is your server name, of course.)
Let's look at your controller - I'm going to keep it very simple for now:
public class MainController : Controller {
public ActionResult Index() {
Person person = new Person();
return View(person);
}
}
What we have going on inside the Index Action is that it is returning a View (which, by default, has the same name as the Action) and a model to correspond with that view. Now, we have to create our view.
The important part here is that you want to strongly-type the model that is being returned in the controller to your view. You do that with this line (which is first in your aspx file).
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewUserControl<Project.Namespace.Person>" %>
Notice the "Inherits" attribute and notice that your Person model makes up that attribute.
Now, just code the rest of your view as normal. Let's say we want to display the current Person name, and allow someone to change the name. The page would look like this (I'm not making this pretty):
<% using (Html.BeginForm()) { %>
<%: Html.LabelFor(model => model.FirstName) %>
<%: Html.TextBoxFor(model => model.FirstName) %>
<%: Html.LabelFor(model => model.LastName) %>
<%: Html.TextBoxFor(model => model.LastName) %>
<input type="submit" value="Submit" name="submitButton" />
<% } %>
This is the important part about getting data back and forth between Controllers and Views. What we are doing here is that we are taking your strongly-typed view (which is typed with the Person class) and using helper methods (like LabelFor and TextBoxFor) to tie the model together with its data and, ultimately, together with the actions contained in the controller (which we have to finish developing here in one moment).
So, you can now see the data. But, if a user changes the name and clicks submit - we want the page to display the new name. This means we need to add one more action to MainController - the one that receives the data.
[HttpPost]
public ActionResult Index(Person person) {
// Do whatever you want with the Person model. Update a database, or whatever.
return View(person);
}
This action looks very similar to the other action we just developed. However, this one takes a person object (from the form that is being submitted) and it gives the controller an opportunity to do whatever needs to be done with that object. Once this is done, you can choose to redirect to a different page, redisplay the page (useful if there are errors), or do any number of other things.
Again, this is ALL covered (and much more) in the NerdDinner Tutorial. I highly recommend you read and follow through that.
As for the AJAX-y aspects you discussed, the premise is still the same (although there is a little bit of JavaScript/jQuery work that goes on in there). I won't go into it now, but the basics are also covered in the NerdDinner tutorial.
I hope this gets you started. I remember being a bit confused too when I first started working with web technologies, so I hope this helps you out!

Resources