Model binding not working inside a partial view - asp.net-mvc

The problem I currently have is that in my partial view, the checkboxes are not being binded correctly by the MVC framework. The CaseViewModel.IsCaseSelected property will always be false regardless of whether or not the checkbox is selected. However, if I hardcode the html in the parent view instead of rendering a partial, then the CaseViewModel.IsCaseSelected property will be properly set corresponding to the checkboxes.
My code is shown below.
The form in my view looks something like this:
<div class="form-group">
<div>
<label for="ProfileName">Profile Name:</label>
<input type="text" name="ProfileName"><br>
</div>
#Html.Partial("~/Views/Shared/_CasesSelection.cshtml", Model.Cases_Category1)
<div id="category2-cases">
<p>category-2</p>
<label for="select-all">Select all</label>
<input id="select-all" type="checkbox" onclick="select_all_toggle(this)" />
#for (int i = 0; i < Model.Cases_Category2.Count; i++)
{
#Html.Label(Model.Cases_Category2[i].CasesNumber.ToString())
#Html.CheckBoxFor(model => model.Cases_Category2[i].IsCaseSelected)
}
</div>
<div>
<input type="submit" value="Create" class="btn btn-default" />
</div>
My partial view looks like this:
#model List<Models.CaseViewModel>
<div id="some-case">
<p>some-case</p>
<label for="select-all">Select all</label>
<input id="select-all" type="checkbox" onclick="select_all_toggle(this)" />
#for (int i = 0; i < Model.Count; i++)
{
#Html.Label(Model[i].CaseNumber.ToString())
#Html.CheckBoxFor(model => model[i].IsCaseSelected)
}
</div>
The model it is binded to looks like this:
public class TestProfileVM
{
[Required]
[RegularExpression(#"^[a-zA-Z0-9-_]+$")]
public string ProfileName { get; set; }
public List<CaseViewModel> Cases_Category1 { get; set; }
public List<CaseViewModel> Cases_Category2 { get; set; }
}
And finally, CaseViewModel looks like this:
public class CaseVM
{
public string CaseType { get; set; }
public int CaseNumber { get; set; }
public bool IsCaseSelected { get; set; }
}
Additional information:
Additionally, in my parent view, when I replace Model.Cases_Category2[i] with Model.Cases_Category2.ElementAt(i), the binding does not work correctly. What is the reason for this, and could it be related to the original problem? Thanks.

I guess problem is the name of checkbox inside partial view. Hence you pass part of view model to partial page, name of checkbox will generated upon that model which you pass to partial page.
So compare generated name for checkbox inside parent view with the name of checkbox inside partial view and if they are not same, change name of checkbox inside partial page accroding to the name checkbox inside parent page.

Related

Create dynamic forms that grow at run time

I'm working in asp.net core inside a MVC application. I'm using the scaffolding feature that creates the views and controller based on a model. Below is the model that i'm using:
class ShoppingList
{
public int ShoppingListId { get; set; }
public string Name { get; set; }
public List<string> ListItems { get; set; }
}
The form that displays to the user via the view only displays the field for Name. I would like the form to be able to show a field for a list item, and then if the user wants to add another list item they can hit a button to add another field to do so. They at run time decide how many shopping list items they want to add.
Here is the razor cshtml form i'm using:
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
Is there an easy way to do this? I don't want to have to hard code a number.
If you want to allow the user to add a new form element on the client side you need to use javascript to update the DOM with the new element you want to add. To list the existing items you may use editor templates. Mixing these 2 will give you a dynamic form. The below is a basic implementation.
To use editor templates, we need to create an editor template for the property type. I would not do that for string type which is more like a generic one. I would create a custom class to represent the list item.
public class Item
{
public string Name { set; get; }
}
public class ShoppingList
{
public int ShoppingListId { get; set; }
public string Name { get; set; }
public List<Item> ListItems { get; set; }
public ShoppingList()
{
this.ListItems=new List<Item>();
}
}
Now, Create a directory called EditorTemplates under ~/Views/YourControllerName or ~/Views/Shared/ and create a view called Item.cshtml which will have the below code
#model YourNameSpaceHere.Item
<input type="text" asp-for="Name" class="items" />
Now in your GET controller, create an object of the ShoppingList and send to the view.
public IActionResult ShoppingList()
{
var vm = new ShoppingList() { };
return View(vm);
}
Now in the main view, All you have to do is call the EditorFor method
#model YourNamespace.ShoppingList
<form asp-action="ShoppingList" method="post">
<input asp-for="Name" class="form-control" />
<div class="form-group" id="item-list">
Add
#Html.EditorFor(f => f.ListItems)
</div>
<input type="submit" value="Create" class="btn btn-default" />
</form>
The markup has an anchor tag for adding new items. So when user clicks on it, we need to add a new input element with the name attribute value in the format ListItems[indexValue].Name
$(function () {
$("#add").click(function (e) {
e.preventDefault();
var i = $(".items").length;
var n = '<input type="text" class="items" name="ListItems[' + i + '].Name" />';
$("#item-list").append(n);
});
});
So when user clicks it adds a new input element with the correct name to the DOM and when you click the submit button model binding will work fine as we have the correct name attribute value for the inputs.
[HttpPost]
public IActionResult ShoppingList(ShoppingList model)
{
//check model.ListItems
// to do : return something
}
If you want to preload some existing items (for edit screen etc), All you have to do is load the ListItems property and the editor template will take care of rendering the input elements for each item with correct name attribute value.
public IActionResult ShoppingList()
{
var vm = new ShoppingList();
vm.ListItems = new List<Item>() { new Item { Name = "apple" } }
return View(vm);
}
First this is you must have a public accessor to your ShoppingList class.
So, public class ShoppingList.
Next is your view will need the following changes.
#model ShoppingList
<h1>#Model.Name</h1>
<h2>#Model.ShoppingListId</h2>
foreach(var item in Model.ListItems)
{
<h3>#item</h3>
}
So, the above code is roughly what you are looking for.
In Razor you can accessor the models variables by using the #model at the top of the view. But one thing you need to note is if your model is in a subfolder you'll need to dot into that.
Here's an example: #model BethanysPieShop.Models.ShoppingCart.
Here BethanysPieShop is my project name, Models is my folder the ShoppingCart class is in.

How manage a View (Validation and Binding etc) for Different models in a view in MVC

Consider a user can create a Sale advertising (Post model). but every advertising have different properties depend on its Group. Properties are not certain and can be added by admin with different constraints(Required. MinLength etc.)
I define a class like this:
public class Property
{
public int Id { get; set; }
public int Priority { get; set; }
[Required()]
public InputType Type { get; set; }
[Required()]
[MaxLength(150)]
public string Title { get; set; }
[Index(IsUnique=true)]
[Required()]
[MaxLength(100)]
public string Values { get; set; }
[MaxLength(100)]
public string Description { get; set; }
public ICollection<GroupProperty> GroupProperties { get; set; }
public ICollection<PostProperty> PostProperties { get; set; }
}
For example admin can add a model's car property to cars group. after that users must fill a model car field for advertisings in car group.
Create view for advertising is like this:
#model IEnumerable<Property>
<section>
<div class="container-fluid">
<div class="row">
<div class="col-md-6">
<h1>New Advertising</h1>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
foreach (var item in Model)
{
#Html.EditorFor(m => item)
}
<button type="submit">hvah</button>
}
</div>
<div class="col-md-6">
</div>
</div>
</div>
</section>
Ah everything goes harder! I have a Editor template for Property class like this:
#model Property
#helper Helper(Property model)
{
switch (model.Type)
{
case WebSite.Models.DomainModels.InputType.NonNegative:
{
<div class="form-group">
<label for="#(model.Name)">#(model.Title)</label>
<span class="field-validation-valid text-danger" data-valmsg-for="#(model.Name)" data-valmsg-replace="true"></span>
<input class="form-control text-box single-line valid" data-val="true"
name="#(model.Name)" type="number" value="0"/>
</div>
return;
}
case WebSite.Models.DomainModels.InputType.RequiredShortString:
{
<div class="form-group">
<label for="#(model.Name)">#(model.Title)</label>
<span class="field-validation-valid text-danger" data-valmsg-for="#(model.Name)" data-valmsg-replace="true"></span>
<input class="form-control text-box single-line" data-val="true"
id="#(model.Name)" name="#(model.Name)" type="text" value="BB"/>
</div>
return;
}
}
}
#Helper(Model)
After all i have Client validation for properties. with hard code i can validate them in server side too. but new problem is Binding! if server side validation goes wrong i need to pass a model to view again. so i am think im doing this with a wrong way. can some one help me? maybe about how solve my problem or a better way to implement this? a simple way to use MVC validation On a complex model like this?
I think you want to create a class and validate ModelState. you can do it like-
Example:
You can pass your model state around like this:
public class MyClass{
public static void errorMessage(ModelStateDictionary ModelState) {
if (something) ModelState.AddModelError("", "Error Message");
}
}
Use in controller:
MyClass.errorMessage(ModelState);
If you need more information about modaestate validation outside then you can fiend more help from this link.

List of uploaded files with ASP.MVC 5 is empty

I have a webform where I want to select a list of files, enter some additional informations and submit the whole bundle. The controller should than process the list of files according to the additionally entered datas. Therefore, I want to bundle all in one Model:
public class MyModel
{
public string AdditionalInformations { get; set; }
public int AdditionalInformationsId { get; set; }
public IEnumerable<HttpPostedFile> Files { get; set; }
}
public class MyController: Controller
{
[HttpPost]
public ActionResult UploadFile(MyModel Model)
{
switch(Model.AdditionalInformationsId)
{
case 1:
// do something with Model.Files
case 2:
// do something else with Model.Files
...
}
RedirectToAction("Index");
}
}
My view looks like
#model MyProgram.Models.MyModel
#using MyProgram.Models
#using (Html.BeginForm("UploadFile", "MyController", FormMethod.Post,
new { enctype = "multipart/form-data" }))
{
<div class="row">
<div class="col-sm-6">
<input type="file" name="Files" multiple />
</div>
<div class="col-sm-6"></div>
</div>
<div class="row">
<div class="col-sm-3">
#Html.DropDownListFor(model => model.AdditionalInformationsId,
((MyDropDownList)Session["DDLists"]).companies,
Model.AdditionalInformation,
new { #class = "form-control", id = "ReceivingCompany" })
</div>
<div class="col-sm-3">
<br />
<button class="btn btn-default" type="submit">Upload</button>
</div>
<div class="col-sm-6"></div>
</div>
}
If I now use the submit-Button, the model is sent to the controller. E.g. AdditionalInformationsId has the value entered in the form. But IEnumerable Files is allways empty, except if I submit the form without choosing any files for upload, in that case the list contains a null-object. Also adding the attribute id="Files" to the multiple-file input-tag does not work.
I can't find the error. Is there maybe a setting I have to set in the web.config?
Change your property type to IEnumerable of HttpPostedFileBase
public IEnumerable<HttpPostedFileBase> Files { get; set; }
Now the posted files will be mapped to the Files property in your HttpPost action.

Razor checkboxfor not working as expected

There are razor checkbox controls in application which needs to be repeated for each of the collection. But for the collection number second onwards below code passes nothing as value for checkbox:
<div class="checkbox">
<label class="checkbox-inline">
#Html.CheckBoxFor(m => m.Collection[i].Item)Some Label
</label>
</div>
The viewmodel is:
public class Items{
public List<Collection> Collection{get; set;}
}
public class Collection{
public bool Item { get; set; }
}
Assuming your HttpPost action's parameter is an object of
[HttpPost]
public ActionResult Create(Items model)
{
//to do : Save and Redirect
}
You need to make sure that the checkboxes in your form will have the name matching to your ViewModel property hierarchy. So , for model binding to work, you need to have your checkboxes with names like this
<input name="Collection[1].Item" type="checkbox" >
So in your view, Make sure you manipulate the name like that
#model Items
#using (Html.BeginForm())
{
for (int index = 0; index < Model.Collection.Count; index++)
{
var collection = Model.Collection[index];
#Html.CheckBox("Collection["+index+"].Item",collection.Item)
}
<input type="submit"/>
}
Another (better) option is to use Editor Templates. With this approach, you do not need to manipulate the form field name. Here is a complete post which explains the step by step
Instead of Item write selected
<div class="checkbox">
<label class="checkbox-inline">
#Html.CheckBoxFor(m => m.Collection[i].selected)Some Label
</label>
</div>
Or try this
#Html.CheckBoxFor(m => m[i].Checked,new {Style ="vertical-align})

Create Template for complex object

I have an object like this
public class Locs
{
public string City {get; set; }
public int Zip {get; set; }
}
public class Names
{
public string FirstName {get; set; }
public string LastName {get; set; }
public Locs[] Locations {get; set; }
}
For the class Names I am generating strongly typed View based on [Create Template]. But when its generated it only show input controls for FristName and Last Name. How can I create a View that can also get Locations from the html page? So that I can easily save data from submit button.
My Form is like this
<input type="text" id="FirstName" name="FirstName" />
<input type="text" id="LastName" name="LastName" />
<p>
<input type="text" id="City1" name="City1" />
<input type="text" id="Zip1" name="Zip1" />
</p>
Add more locations
As you can see User can dynamically create City and Zip. I am now sure how many he will create. How can I get such object in my view? Can i get such object automatically? Also I want to apply validations.
If you are trying to show an editor an for each locations, you can loop through the Locations property in your view:
Rest of your view...
<% for (int i = 0; i < Model.Locations.Count; i++)
{ %>
<%= Html.TextboxFor(model => model.Locations[i].City) %>
<%= Html.TextboxFor(model => model.Locations[i].Zip) %>
}%>
Continue your view.

Resources