Bind model property collections - asp.net-mvc

My scenario is much complicated so i simplified it with the example below. The main problem is binding collection properties of a model, specifying the path of the property like Html.TextBox("List[0].Name") and not the Html.TextBoxFor(t => t.List[0].Name). So, for the current view i will only know some of the metadata of the model so i will have to construct it this way. Here is the scenario :
Model
public class ModelTest
{
public int Id {get;set;}
public List<Foo> Collection {get;set;}
}
public class Foo
{
public string Value1 {get;set;}
public string Value2 {get;set;}
}
Controller
public class TestController: Controller
{
[HttpGet]
public ActionResult Test()
{
var model = new ModelTest()
{
Id = 455,
Collection = new List<Foo>()
{
new Foo(){ Value1 = "sagasga", Value2 = "Beul"},
new Foo(){ Value1 = "dgdsgds", Value2 = "fhfhd" }
}
};
return View(model);
}
[HttpPost]
public ActionResult Test( ModelTest model)
{
//....
return View();
}
View:
#using (Html.BeginForm())
{
#Html.TextBox("Id")
#Html.TextBox("Collection[0].Value1")
#Html.TextBox("Collection[0].Value2")
<input type="submit" value="Add" />
}
For the code above i get empty textboxes for the collection values. However, when the page is submited i get the model built correct in the Post method.
Many thanks,
Alex

This is the way to name you input fields when you wanna post a collection to your controller. However, you have to specify the initial value yourself. Your code is currently just creating textbox with the name property set to Collection[0].Value1. You still need to specify the input this way,
#Html.TextBox("Collection[0].Value1", Model.Collection.FirstOrDefault().Value1)
#Html.TextBox("Collection[0].Value2", Model.Collection.FirstOrDefault().Value2)

Related

Correct way to display a list in a dropdown using mvc

I know there are a lot of similar question here but none seem quite the same as mine.
In my View:
#model LocalInformedMotionServer.Models.FeedData
#Html.DropDownList("Premise", Model.LoadUp("key"), new { #style = "width: 218px;height:35px;" })
In my controller:
public class CloudController : Controller
{
public IEnumerable<wsCommon.Premise> Premises { get; set; }
public ActionResult Feed(string key)
{
var feedData = new FeedData();
Premises= feedData.LoadUp(key);
return View(feedData);
}
}
In my Model:
public class FeedData
{
public IEnumerable<wsCommon.Premise> LoadUp(string saltKey)
{
Premises premises = new InformedBiz.Premises();
return premises.GetPremises(saltKey);
}
}
It errors because the variable:
"key"
in this call:
Model.LoadUp("key")
is being read in as'null' in my controller method.
Of course as this is all new to me I could be doing this all wrong..
ADDITIONAL:
In my CloudController Class I have this:
public class CloudController : Controller
{
public ActionResult Feed(string saltKey)
{
var feedData = new FeedData();
feedData.LoadUp(saltKey);
return View(feedData);
}
public ActionResult Index()
{
return View();
}
public ActionResult LogIn()
{
return View();
}
}
I'm not sure what your Premise class looks like, but I usually use an IEnumberable of SelectListItem for drop downs in my views. So you could do something like this:
public IEnumerable<SelectListItem> LoadUp(string saltKey)
{
Premises premises = new InformedBiz.Premises();
return premises.GetPremises(saltKey).Select(
p => new SelectListItem { Text = p.Name, Value = z.PremiseId.ToString() }
);
}
You'll also need to create a Post ActionResult method that accepts the model in your view (FeedData) as well as wrap your DropDownList control in a Html.BeginForm, to post results to the controller. Hope this makes a bit of sense.
You have not posted the properties of your FeedData model but assuming it contains a property which is typeof Premise and you want to be able to select a Premise from a collection, then using a view model that represents what you want to display/edit is the recommended approach (refer View Model Design And Use In Razor Views and What is ViewModel in MVC?)
You view model might look like
public class FeedDataVM
{
.....
[Display(Name = "Premise")]
[Required(ErrorMessage = "Please select a Premise")]
public int? SelectedPremise { get; set; }
....
public SelectList PremiseList { get; set; }
}
and in your controller (not sure what saltKey is for?)
public ActionResult Feed(string saltKey)
{
FeedDataVM model = new FeedDataVM();
IEnumerable<Premise> premises = // get the collection of premise objects from your repository
// assuming you want to display the name property of premise, but post back the key property
model.PremiseList = new SelectList(premises, "key", "name");
return View(model);
}
View
#model FeedDataVM
#using(Html.BeginForm())
{
....
#Html.LabelFor(m => m.SelectedPremise)
#Html.DropDownListFor(m => m.SelectedPremise, Model.PremiseList, "-Please select")
#Html.ValidationMessageFor(m => m.SelectedPremise)
....
<input type="submit" value="Save" />
}
and the POST method
public ActionResult Feed(FeedDataVM model)
{
// model.SelectedPremise contains the value of the selected option as defined by the key property of Premise
}
Side note: Your FeedData model contains a method to retrieve a collection of Premise which appears to be calling another service. You should avoid this type of design which makes it difficult to debug and unit test. Your controller is responsible for initializing/getting you data models and view models and for populating/mapping their properties.

String as a Model

I thought this should have been an easier task :
Edit:
It seems till this day Asp.Net MVC couldn't provide a neat solution on this case:
If you want to pass a simple string as a model and you don't have to define more classes and stuff to do so...
Any ideas ??
Pass simple string as a model
here I'm trying to have a simple string model.
I'm getting this error :
"Value cannot be null or empty" / "Parameter name: name"
The View :
#model string
#using (Html.BeginForm())
{
<span>Please Enter the code</span>
#Html.TextBoxFor(m => m) // Error Happens here
<button id="btnSubmit" title="Submit"></button>
}
The Controller :
public string CodeText { get; set; }
public HomeController()
{
CodeText = "Please Enter MHM";
}
[HttpGet]
public ActionResult Index()
{
return View("Index", null, CodeText);
}
[HttpPost]
public ActionResult Index(string code)
{
bool result = false;
if (code == "MHM")
result = true;
return View();
}
There's a much cleaner way of passing a string as a model into your view. You just need to use named parameters when returning your view:
[HttpGet]
public ActionResult Index()
{
string myStringModel = "I am passing this string as a model in the view";
return View(model:myStringModel);
}
I know you've already accepted an answer here - I'm adding this because there's a general gotcha associated with using a string model.
String as a model type in MVC is a nightmare, because if you do this in a controller:
string myStringModel = "Hello world";
return View("action", myStringModel);
It ends up choosing the wrong overload, and passing the myStringModel as a master name to the view engine.
In general it is easier simply to wrap it in a proper model type, as the accepted answer describes, but you can also simply force the compiler to choose the correct overload of View() by casting the string to object:
return View("action", (object)myStringModel);
The other issue you're having here of using TextBoxFor having issues with an 'unnamed' model - well you shouldn't be surprised by that... The only reason to use TextBoxFor is to ensure the fields are named correctly for binding when the underlying value is a property on a model type. In this case there is no name, because you're passing it as a top-level model type for a view - so you it could be argued that you shouldn't be using TextBoxFor() in the first place.
Either wrap the string in a view model object:
Model:
public class HomeViewModel
{
public string CodeText { get; set; }
}
Controller:
private HomeViewModel _model;
public HomeController()
{
_model = new HomeViewModel { CodeText = "My Text" };
}
[HttpGet]
public ActionResult Index()
{
return View("Index", _model);
}
View:
#Html.TextBoxFor(m => m.CodeText);
Or use EditorForModel:
#Html.EditorForModel()
You can simply use an overload of View() method.
View(string ViewName, object model)
in action method, call View with that signature.
return View("MyView", myString);
in view(.cshtml), define the model type as string
#model string
Then, #Model will return the string (myString).

The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[System.Int32]'

ASP.Net MVC 4
I am trying to populate a list of Countries (data from Country table in DB) in a dropdownlist. I get the following error:
The model item passed into the dictionary is of type
System.Collections.Generic.List`1[System.Int32]', but this dictionary requires a model item of type 'BIReport.Models.Country'.
I am new to ASP.Net MVC and I don't understand that error. What I feel is what Index method is returning doesn't match with the model that I am using in the View.
Model::
namespace BIReport.Models
{
public partial class Country
{
public int Country_ID { get; set; }
public string Country_Name { get; set; }
public string Country_Code { get; set; }
public string Country_Acronym { get; set; }
}
}
Controller::
public class HomeController : Controller
{
private CorpCostEntities _context;
public HomeController()
{
_context = new CorpCostEntities();
}
//
// GET: /Home/
public ActionResult Index()
{
var countries = _context.Countries.Select(arg => arg.Country_ID).ToList();
ViewData["Country_ID"] = new SelectList(countries);
return View(countries);
}
}
View::
#model BIReport.Models.Country
<label>
Country #Html.DropDownListFor(model => model.Country_ID, ViewData["Country_ID"] as SelectList)
</label>
Where am I going wrong?
You are selecting CountryIDs, therefore you will have a list of integers passed into the view.
I think you really want something like this:
public ActionResult Index()
{
var countries = _context.Countries.ToList();
ViewData["Country_ID"] = new SelectList(countries, "Country_ID", "Country_Name");
return View();
}
I'm not really sure why you have single country as a model for your view.
Update:
I'm still not sure why the model is a country, if you are just going to post the ID of the selected country you don't necessarily need a model at all (or just have an integer). This will be just fine though:
View
#model MvcApplication1.Models.Country
#Html.DropDownListFor(m => m.Country_ID, ViewData["Country_ID"] as SelectList)
the problem is in line 1 of your view. change it like this :
#model IEnumerable<BIReport.Models.Country>
also there is no need to pass the model to view if you already did it by :
ViewData["Country_ID"] = new SelectList(countries);
When you say #model BIReport.Models.Country it means your view is expecting a model consisting single country details. On the contrary you need a list of countries to be displayed in the drop-down list. Hence you should tell the view to look for a list of country details instead.
Therefore #model IEnumerable.

MVC3 - How to correctly use #html.checkbox?

I'm new to MVC3 and I can't figure out how to use checkboxes in MVC.
I have a bunch of text in my view like
text1
text2
text3
text4
text5
submitbutton
This text is not related to any model its just plain text. I would like to place a checkbox for each item and a link it to the controller so that when a user selects some of the checkbox values and clicks on the submit button my controller picks up which items have been selected.
I tried using #html.checkbox("text"+ index) and tried the controller to be
[HttpPost]
public ActionResult controller(List<string> list)
{
}
But that doesn't pick up the list of selected items. Can you tell me what i'm doing wrong or another way to do it?
What i would do in this situation is to make those items to be a property of my ViewModel.
public class MyViewModel
{
public bool text1 { set;get}
public bool text2 { set;get;}
public bool SomeMeaningFullName { set;get;}
// Other properties for the view
}
and in my Get Action method i will return this ViewModel to my View
public ActionResult Edit()
{
MyViewModel objVM=new MyViewModel();
return View(objVM);
}
and in my View
#model MyViewModel
#using (Html.BeginForm("Edit","yourcontroller"))
{
#Html.LabelFor(Model.text1)
#Html.CheckBoxFor(Model.text1)
#Html.LabelFor(Model.text2)
#Html.CheckBoxFor(Model.text2)
<input type="submit" value="Save" />
}
Now this property value will be available in your post action method
[HttpPost]
public ActionResult Edit(MyViewModel objVM)
{
//Here you can access the properties of objVM and do whatever
}
Create a ViewModel with all of your values. Populate the ViewModel and send it to the view. When something is checked, you'll know what's what on the post.
public class MyModelViewModel
{
public List<CheckBoxes> CheckBoxList {get; set;}
// etc
}
public class CheckBoxes
{
public string Text {get; set;}
public bool Checked {get; set;}
}
[HttpPost]
public ActionResult controller(MyModelViewModel model)
{
foreach(var item in model.CheckBoxList)
{
if(item.Checked)
{
// do something with item.Text
}
}
}
Basically ViewModels are your friend. You want to have a separate ViewModel for each View, and it's what gets passed back and forth between the Controller and the View. You can then do your data parsing either in the controller, or (preferably) in a service layer.
Additional Reference:
Should ViewModels be used in every single View using MVC?

ViewModel with List<BaseClass> and editor templates

I have a view that lists tables being added to a floor plan. Tables derive from TableInputModel to allow for RectangleTableInputModel, CircleTableInputModel, etc
The ViewModel has a list of TableInputModel which are all one of the derived types.
I have a partial view for each of the derived types and given a List of mixed derived types the framework knows how to render them.
However, on submitting the form the type information is lost. I have tried with a custom model binder but because the type info is lost when it's being submitted, it wont work...
Has anyone tried this before?
Assuming you have the following models:
public abstract class TableInputModel
{
}
public class RectangleTableInputModel : TableInputModel
{
public string Foo { get; set; }
}
public class CircleTableInputModel : TableInputModel
{
public string Bar { get; set; }
}
And the following controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new TableInputModel[]
{
new RectangleTableInputModel(),
new CircleTableInputModel()
};
return View(model);
}
[HttpPost]
public ActionResult Index(TableInputModel[] model)
{
return View(model);
}
}
Now you could write views.
Main view Index.cshtml:
#model TableInputModel[]
#using (Html.BeginForm())
{
#Html.EditorForModel()
<input type="submit" value="OK" />
}
and the corresponding editor templates.
~/Views/Home/EditorTemplates/RectangleTableInputModel.cshtml:
#model RectangleTableInputModel
<h3>Rectangle</h3>
#Html.Hidden("ModelType", Model.GetType())
#Html.EditorFor(x => x.Foo)
~/Views/Home/EditorTemplates/CircleTableInputModel.cshtml:
#model CircleTableInputModel
<h3>Circle</h3>
#Html.Hidden("ModelType", Model.GetType())
#Html.EditorFor(x => x.Bar)
and final missing peace of the puzzle is the custom model binder for the TableInputModel type which will use the posted hidden field value to fetch the type and instantiate the proper implementation:
public class TableInputModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var typeValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".ModelType");
var type = Type.GetType(
(string)typeValue.ConvertTo(typeof(string)),
true
);
var model = Activator.CreateInstance(type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
return model;
}
}
which will be registered in Application_Start:
ModelBinders.Binders.Add(typeof(TableInputModel), new TableInputModelBinder());
and that's pretty much all. Now inside the Index Post action the model array will be properly initialzed with correct types.
There was "Derived Type Model Binder" in mvccontrib. But, unfortunately, there is no such binder in mvccontrib version 3

Resources