I have a CreateViewModel.
public class CreateViewModel
{
public AttributesViewModel AttributesInfo { get; set; }
}
The AttributesViewModel is sent to a partial view.
public class AttributesViewModel
{
public AttributesViewModel()
{
ChosenAttributes = new List<int>();
}
public List<Attributes> Attributes { get; set; }
public List<int> ChosenAttributes { get; set; }
}
The List of Attributes is outputted in the partial view. Each one has a checkbox.
foreach (var attribute in Model.Attributes)
{
<input type="checkbox" name="ChosenAttributes" value="#attribute.ID" /> #Attribute.Name
}
When I post CreateViewModel, AttributesInfo.ChosenAttributes is always empty even though I checked some boxes. How do I properly name each checkbox so that it binds to the ChosenAttributes List?
My Solution
I took Stephen Muecke's suggestion to do the two way binding. So, I created a CheckboxInfo class that contained Value, Text, and IsChecked. I created a EditorTemplate for it:
#model Project.CheckboxInfo
#Html.HiddenFor(model => model.Text)
#Html.HiddenFor(model => model.Value)
#Html.CheckBoxFor(model => model.IsChecked) #Model.Text
One GIANT caveat. To get this to work properly, I had to create an EditorTemplate for the AttributesViewModel class. Without it, when CreateViewModel is posted, it cannot link the checkboxes to AttributesInfo.
Your naming the checkbox name="ChosenAttributes" but CreateViewModel does not contain a property named ChosenAttributes (only one named AttributesInfo). You may be able make this work using
<input type="checkbox" name="AttributesInfo.ChosenAttributes" value="#attribute.ID" /> #Attribute.Name
but the correct approach is to use a proper view model that would contain a boolean property (say) bool IsSelected and use strongly typed helpers to bind to your properties in a for loop or using a custom EditorTemplate so that your controls are correctly names and you get 2-way model binding.
I had a similar scenario, but this was how I did it. The solution is not perfect so please excuse if I have left something out, but you should be able to relate. I tried to simplify your solution as well :)
I changed the Attribute class name to CustomerAttribute, rename it to whatever you like, use a singular name, not plural. Add a property to your CustomerAttribute class, call it whatever you like, I called mine IsChange.
public class CustomerAttribute
{
public bool IsChange { get; set; }
// The rest stays the same as what you have it in your Attributes class
public string Name { get; set; } // I'm assuming you have a name property
}
Delete your AttributesViewModel class, you don't really need it, I like simplicity.
Modify your CreateViewModel class to look like this:
public class CreateViewModel
{
public CreateViewModel()
{
CustomerAttributes = new List<CustomerAttribute>();
}
public List<CustomerAttribute> CustomerAttributes { get; set; }
}
Your controller will look something like this:
public ActionResult Create()
{
CreateViewModel model = new CreateViewModel();
// Populate your customer attributes
return View(model);
}
Your post controller action method would look something like this:
[HttpPost]
public ActionResult Create(CreateViewModel model)
{
// Do whatever you need to do
}
In your view, you will have something like this:
<table>
<tbody>
#for (int i = 0; i < Model.CustomerAttributes.Count(); i++)
{
<tr>
<td>#Html.DisplayFor(x => x.CustomerAttributes[i].Name)</td>
<td>#Html.CheckBoxFor(x => x.CustomerAttributes[i].IsChange)</td>
</tr>
}
<tbody>
</table>
Create a sample app and try out the code above and see if it works for you.
Related
I have a model like
public class Model
{
public int Value { get; set; }
public List<OtherModel> List { get; set; }
}
public class OtherModel
{
public int Value1 { get; set; }
public int Value2 { get; set; }
public bool IsPropTrue { get; set; }
}
I am using Model in a View where I'm looping through the List to show data in a table.
Depending on whether one of the properties (IsPropTrue) in OtherModel is true or false, I want to use the HiddenFor Html helper and send the data to the HttpPost controller.
#model Model
#foreach (var item in Model.List)
{
if (item.IsPropTrue)
{
#Html.HiddenFor(model=> item.Value1)
#Html.HiddenFor(model=> item.Value2)
}
}
I think it doesn't work because I should in some way add these properties to the OtherModel, which is inside the Model; But the way I have it now, I am adding properties to Model.
you can do it like this :
#model Model
#foreach (var item in Model.List)
{
if (item.IsPropTrue)
{
#Html.HiddenFor(model => model.List[Model.List.IndexOf(item)].Value1)
#Html.HiddenFor(model => model.List[Model.List.IndexOf(item)].Value2)
}
}
this way the binding system will bind the hidden fields with your List OtherModel in the Model
if you want send an array to server based on the Model you have to use indexer in #Html.HiddenFor .
#model WebApplication1.Models.MyModel
<form>
#if (Model != null && Model.List != null)
{
for (int i = 0; i < Model.List.Count; i++)
{
if (Model.List[i].IsPropTrue)
{
#Html.HiddenFor(model => Model.List[i].Value1)
#Html.HiddenFor(model => Model.List[i].Value2)
}
}
}
<button type="submit">submit</button>
</form>
if you want know reason of using indexer on model i recommend How does MVC 4 List Model Binding work?
Consider if it the responsibility of the view or the controller action to make the decisions - you can send everything back to the action to do the decision making.
In your Views/Shared folder, create a controller called EditorTemplates
In this folder, add a partial view called OtherModel
In this view, set the model to OtherModel and set the Layout=null
Add the three OtherModel fields in EditorFor (and HiddenFor if not displaying isPropTrue). This partial view displays just one instance of your list.
In your main view, use the above editor model like so. MVC will take care of all rendering and postback of the Model State for your complete list of items. We like one-liners...
#Html.EditorFor(model => model.OtherModel)
When the data is subsequently posted back to an action, Model State has wrapped up all of your displayed items into a list again, so you can check the isPropTrue value for each item on the server.
The only issue with MVC is that is you pass an empty list out to a view, you get a null value back, so just replace this with an empty list when null is returned
I've seen a couple possible solutions but they look very messy to me. Does anyone have a simple solution for this?
Model:
public class MyClass
{
public KeyValuePair<int,string> Field { get; set; }
}
Get method in controller:
public ActionResult Index()
{
var model = new MyClass();
model.Field = new KeyValuePair<int, string>(1, "test");
return View(model);
}
View:
#model WebApplication1.Models.MyClass
#using (Html.BeginForm("MyMethod", "Home", FormMethod.Post))
{
#Html.Hidden("Field", Model.Field);
<input type="submit" value="Submit" />
}
Post method in controller:
public ActionResult MyMethod(MyClass input)
{
var x = input.Field;
....
}
The Key Value pair is not passed with this method as it just comes up empty. What would be the easiest way of getting 'Field' passed to the controller?
Replace following line and try it.let me know any problem.
#Html.Hidden("Field", Model.Field);
TO
#Html.HiddenFor(m => m.Field);
If memory serves correctly, it has a Key and Value property, which like any other object would be bound like:
#Html.HiddenFor(m => m.Field.Key)
#Html.HiddenFor(m => m.Field.Value)
One input field can't represent the key and value in this way, unless you look at using a Custom Model Binder to manage the value posted back, and a Display/Editor template to render the input in a way the model binder will understand.
Following Brians answer, I've had to accept I must do it like this:
Class:
public class MyClass
{
public KeyValuePair<int,string> Field { get; set; }
public string FieldKey { get; set; }
public string FieldValue { get; set; }
}
View:
#Html.Hidden("FieldKey",Model.Field.Key)
#Html.Hidden("FieldValue", Model.Field.Value)
It will get messy as I have many of these kvp's I'd like to pass back to the controller, but I guess it's the only uncomplicated way. It's not the first time I've wished it was possible to pass objects to a controller, and I'm sure it won't be the last. Maybe we'll get the happy news in a dotnet update one day that we can do things like this more easily.
This is just something that has been puzzling me, I'm wondering if there's a built in way for this.
Say you have a Package class
public class Package
{
public A AObject { get; set; }
public B BObject { get; set; }
}
And you have a view that uses this Package.
public ActionResult Action()
{
return View(new Package());
}
Now the view will accept this model and have 2 forms.
#model Path.To.Package
#Html.BeginForm("SubmitA", "MyController")
{
#Html.TextBoxFor(m => m.AObject.SomeProperty);
<input type="submit" />
}
#Html.BeginForm("SubmitB", "MyController")
{
#Html.TextBoxFor(m => m.BObject.AnotherProperty);
<input type="submit" />
}
If one would create two actions needed above that take Package as argument, this would work without question...
public JsonResult SubmitA(Package items) { ... }
public JsonResult SubmitB(Package items) { ... }
But at SubmitA the BObject would be null and in SubmitB AObject would be null.
My question here is whether you can submit only a part of the model? So the first form would only submit AObject and the second BObject so you could actually reach these via the following actions:
public JsonResult SubmitA (A a) { ... }
public JsonResult SubmitB (B b) { ... }
You can use the Prefix property of BindAttribute to bind to complex properties of a model. The attribute effectively removes the prefix from the submitted name/value pairs when binding to model.
Your controller methods would be
public JsonResult SubmitA([Bind(Prefix = "AObject")]A model) { ... }
public JsonResult SubmitB([Bind(Prefix = "BObject")]B model) { ... }
You should really use separate view model for each form. You can of course, use bind attribute or use specific property names in the controller action. But, that doesn't solve your real problem. You can only get either of the values and the other object will be unassigned or NULL. This is why you should have separate view model for each view / form. You can build your Package object once you have values for both objects.
I am very new to asp.net development. In my asp.net mvc project I have model "Employee" and I'm passing a list of "Employee" model to a RAZOR view and I'm trying to count different type of employees and show a summary.
my view is like this,
#{
int available = 0;
int onLeave = 0;
int away = 0;
int unAvailable = 0;
}
#foreach (var employee in Model){
<lable>#employee.Name</lable></br>
#if (#employee.Available){
#available=available+1;
}
#if (#employee.Unavailable){
#unAvailable=unAvailable;
}
#if (#employee.Away){
#away=away+1;
}
#if (#employee.Onleave){
#onLeave=onLeave+1;
}
}
<div>
<!--additional summary is displayed here-->
<label>Available:</label>#available
<label>Unavailable:</label>#unAvailable
<label>Away:</label>#away
<label>On Leave:</label>#onLeave
</div>
but when I run the my project variables "available","unAvailable","away" and "onLeave" don't get updated.
I'm sure that list is not empty because employee names are displaying.
can some explain me what is happening here and correct way of doing this
You should be doing this outside the before passing to the view like I mentioned in my original comment. You can create a new object called a ViewModel to represent the data exactly like you want it on the page. So I created a simple example, I only used the 4 properties of Employee you are displaying in you CSHTML page. On your View where you said your MODEL is either a list, arrary or whatever of Employee change it to EmployeeViewModel. Then in your controller where you get your list of employees set them to the Employees property of the Employee ViewModel.
public class EmployeeViewModel
{
public IEnumerable<Employee> Employees { get; set; }
public int TotalAvailable { get { return Employees.Count(emp => emp.Available); } }
public int TotalUnavailable { get { return Employees.Count(emp => emp.Unavilable); } }
public int TotalAway { get { return Employees.Count(emp => emp.Away); } }
public int TotalOnLeave { get { return Employees.Count(emp => emp.OnLeave); } }
}
public class Employee
{
public bool Available { get; set; }
public bool Unavilable { get; set; }
public bool Away { get; set; }
public bool OnLeave { get; set; }
}
//In the controller do this.
public ActionResult Index() //use your controller Action Name here
{
var employeeViewModel = new EmployeeViewModel { Employees = /*Your list of empoyees you had as a Model before here*/}
return View(employeeViewModel)
}
Change your CSHTML code to something like this:
#foreach(var employee in Model.Employees)
{
<label> #employee.Name </label></br>
}
<div>
<!--additional summary is displayed here-->
<label> Available:</label> #Model.TotalAvailable
<label> Unavailable:</label> #Model.TotalUnavailable
<label> Away:</label> #Model.TotalAway
<label> On Leave:</label> #Model.TotalOnLeave
</div>
An easy and quick way is:
<div>
<!--additional summary is displayed here-->
<label>Available:</label>#Model.Count(i => i.Available)<br>
<label>Unavailable:</label>do the same.
<label>Away:</label>do the same.
<label>On Leave:</label>do the same.
</div>
Make sure the model has already been "ToList()", or it might lead to mult-access of database.
Basically, I only use viewmodel when I need to pass more than 1 models to the view. Not worth in this case.
Make such calculations in View considered a BAD practice.
In your case better option will be create ViewModel with corresponding properties and then pass it to the model, previously calculating count for every type in controller using LINQ. Where you could reference your types like Model.available, Model.away and so on. Using ViewModel it is the best practice for MVC.
#Thorarins answer show you how to use LINQ in your code to calculate count for you types.
UPDATE:
You can use JS, but you should not, because it still not what supposed to happen in View. Work with data should not be handled in View. Don't be scared by ViewModels, they not that hard as it could seem. Please read this article which consider all ways to pass data to View, which has good example how create and pass ViewModel.
Mvc sample on how to do it:
you need a model class
public class EmployeeModel
{
public int Available {get; set;}
public int OnLeave {get; set;}
public int Away {get; set;}
public int UnAvailable {get; set;}
}
and a command:
public ActionResult Index()
{
var model = new EmployeeModel();
model.Available = employee.count(e=> e.available);
model.OnLeave = employee.count(e=> e.onLeave);
model.Away = employee.count(e=> e.away);
model.UnAvailable = employee.count(e=> e.unAvailable );
return View(model);
}
and a view
#model EmployeeModel
<div>
<!--additional summary is displayed here-->
<label>Available:</label>#Model.Available
<label>Unavailable:</label>#Model.UnAvailable
<label>Away:</label>#Model.Away
<label>On Leave:</label>#Model.OnLeave
</div>
I have a base class for model:
public abstract class ViewModel {
public string Title{ get; set; }
public abstract string Type { get; }
}
and I created two classes:
public class SomeViewModel: ViewModel {
public override string Type { get { return "a type" } }
public int Id { get; set; }
}
public class AnotherModel: ViewModel {
public override string Type { get { return "another type" } }
public string System { get; set; }
}
Now, I have a view which uses these classes ( I send a List<ViewModel> to the view)
#model List<ViewModel>
...
<form>
#Html.DisplayFor(model => model)
</form>
I have a view called ViewModel.cshtml in DisplayTemplates folder
#model ViewModel
#if(Model is AnotherModel) {
// do something and print value
AnotherModel conv = Model as AnotherModel;
#Html.TextboxFor(model => conv.System)
} else {
// put some inputs here
}
#Html.HiddenFor(model => model.Type)
Now I have a javascript ajax. I want to send a list of objects to an action which renders a partial view
The ajax:
$.ajax({
url: 'My/GetData',
dataType: "html",
type: "POST",
data: { id: 3, myList: $("form").serialize() }
success: function(data) {
// print html
}
});
and action looks like:
[HttpPost]
public ActionResult GetData(int id, List<ViewModel> myList){
...
return PartialView("myView", someModel)
}
The problem is myList parameter which always is 0 as length (count)... I expect to be 2...
Where is my mistake ?
The reason myList would contain no elements is no doubt because the control names do not match the property names, but you have more problems than just that. The Type property in the derived models has a getter only, so even if the collection did bind correctly (it wont as discussed below), the value of Type would be an empty string because there is no setter on the property (the IsReadonly property of ModelMetadata is set to true so the DefaultModelBinder ignores it), and you would have no way of knowing what type your objects were.
Next, The DefaultModelBinder will only initialize instances of ViewModel, not your derived types, so only those properties of ViewModel with public getters/setters would be bound (for example the Id property of SomeViewModel wont exist)
I am assuming your models must be far more complex than what you have shown (otherwise there would be no point doing this) which means your template (and it should be an EditorTemplate not a DisplayTemplate!) must be polluted with lots of other logic to determine which properties to render and how (and a nightmare to debug).
The simple solution would be to create a view model with collections for each type (probably also including the id property you want to post, but not sure what that is or how it relates to your model) and then use an EditorTemplate for each type
public class TypeVM
{
public List<SomeViewModel> SomeTypes { get; set; }
public List<AnotherModel> AnotherTypes { get; set; }
}
/Views/Shared/EditorTemplates/AnotherModel.cshtml
#model SomeViewModel
....
#Html.TextBoxFor(m => m.Id)
/Views/Shared/EditorTemplates/SomeViewModel.cshtml
#model SomeViewModel
....
#Html.TextBoxFor(m => m.System)
and in the view
#model TypeVM
....
#Html.EditorFor(m => m.SomeTypes)
#Html.EditorFor(m => m.AnotherTypes)
If you need to submit using ajax, then
var url = '#Url.Action("GetData", "My")'; // don't hard code your url's!
var data = $("form").serialize();
$.post(url, data, function(data) {
// update DOM
});
and post back to
[HttpPost]
public ActionResult GetData(TypeVM model)
This would mean rendering different the types in groups. If that is not suitable, then you need to create a custom ModelBinder where you can use the bindingContext to read the Type property and initialize each derived type and set the appropriate properties. A daunting task, but The Features and Foibles of ASP.NET MVC Model Binding
might help you get started with how to approach it (the section on Abstract Model Binder).
Side note: Do you really need to post back the whole form in order to return a partial view?