I have a model for person that has dictionary to hold Gender values (the values are added in a controller). I have created a viewmodel with person class and other properties. In controller I tried to add values to dictionary in person class through an instance of viewmodel. It does not throw an error but the dictionary value is always null. The code works if I do not use viewmodel and work with model directly. Important!!!! (I have to add values to dictionary via controller) Thanks for your help. Please code below.
In Model:
public class dictionary
{
[Display(Name ="Dictionary Dropdownlist")]
public Dictionary<string,string> dpdnDict { get; set; }
}
In ViewModel:
public class dictionaryviewmodel
{
public dictionary dictInViewModel {
get { return new dictionary(); }
set { }
}
}
In Controller:
public ActionResult Index(dictionaryviewmodel dictViewModel)
{
dictViewModel.dictInViewModel.dpdnDict.Add("M", "Male");
dictViewModel.dictInViewModel.dpdnDict.Add("F", "Female");
return View(dictViewModel);
}
First of all this code is indeed throwing an exception on this line
dictViewModel.dictInViewModel.dpdnDict.Add("M", "Male");
because dictViewModel.dictInViewModel returns new dictionary() and dictViewModel.dictInViewModel.dpdnDict is null because dpdnDict isn't set anywhere in code. If you want to make this code work change your classes
public class dictionaryviewmodel
{
public class dictionaryviewmodel
{
//this getter will create dictionary instance only once
//and will always return the same instance with previously added values
//also it instantiates dpdnDict object
public dictionary dictInViewModel { get; } = new dictionary()
{
dpdnDict = new Dictionary<string, string>()
};
}
}
And I don't think you pass any data into controller on request so I'd update controller as well
public ActionResult Index()
{
dictionaryviewmodel dictViewModel = new dictionaryviewmodel();
dictViewModel.dictInViewModel.dpdnDict.Add("M", "Male");
dictViewModel.dictInViewModel.dpdnDict.Add("F", "Female");
return View(dictViewModel);
}
Related
In the table column (of type nvarchar) I store an array of string values (List <string>). To get a data model I use Entity Framework.
Model:
namespace Project.Models.Atm
{
public class MyObject
{
[JsonIgnore]
public string _Parameters { get; set; }
[NotMapped]
[JsonProperty("parameters")]
public List<string> Parameters
{
get
{
return JsonConvert.DeserializeObject<List<string>>(string.IsNullOrEmpty(_Parameters) ? "" : _Parameters);
}
set
{
_Parameters = JsonConvert.SerializeObject(value);
}
}
}
}
If a new list of string values is assigned to the field of the myObject.parameters object, then I will do everything well.
MyObject myObject = new MyObject();
List<string> parameters = new List<string>();
parameters.Add("value");
myObject.parameters = parameters;
But if you try to add a value to the list of objects one by one, they are not added.
MyObject myObject = new MyObject();
myObject.parameters.Add("value"); <-- The value will not be added and there will be no errors.
What could be the problem?
While executing:
myObject.parameters.Add("value");
you call:
get
{
return JsonConvert.DeserializeObject<List<string>>(string.IsNullOrEmpty(_Parameters) ? "" : _Parameters);
}
and this code is creating new list for you and you are adding to it not to the string that is actually stored in the database.
To avoid temptation of adding to this list change your property to return IEnumerable<string> instead and always assign new list to the property.
I have two controllers(controllerA.actionA & controllerB.actionB) which share the same view & ViewModel(ViewModel_Shared).
So, in both Controller, I use RedirectToAction to redirect to a third controller(controllerC.actionC) which points to the shared view.
All three Actions are in different Controller.
When ActionA or ActionB was invoked All the parameters that post from view was sent to the modelC in actionC successfully.
However, when I try to feed data into the object(Item), NullReference exception was thrown. the object(Item) was NULL.
But I'm sure the constructor of ViewModel_Shared was hit TWICE while ActionA was called and ActionC was called.
So, basically, object(Item) was NEW twice.
I don't really understand why it's like that.
thank you all in advance!!!
public ActionResult actionA (ViewModel_Shared modelA)
{
return RedirectToAction("actionC", "controllerC", modelA);
}
public ActionResult actionB (ViewModel_Shared modelB)
{
return RedirectToAction("actionC", "controllerC", modelB);
}
public ActionResult actionC (ViewModel_Shared modelC)
{
modelC.FillEditedData();
// Send the data to view
ViewData.Model = modelC;
return View();
}
public class ViewModel_Shared
{
public ItemCustomer Item { get; set; }
public ViewModel_Shared()
{
Item = new ItemCustomer();
}
public void FillEditedData()
{
// NullReference exception was throw here, somehow, Item is null.
Item.LegalCost = "some value";
}
}
[Serializable]
public class ItemCustomer
{
public string Item_No { get; set; }
public string MaterialPricingGroup { get; set; }
public string MaterialGroup { get; set; }
public string Plant { get; set; }
public string LegalCost { get; set; }
}
As mentioned, complex objects cannot travel with requests.
Instead you need to serialize the object and pass the serialized data to the destination action method:
public ActionResult actionA (ViewModel_Shared modelA)
{
var serializedModel = JsonConvert.SerializeObject(modelA);
return RedirectToAction("actionC", "controllerC", serializedModel);
}
UPDATE
My bad. Indeed the parameters, sent through RedirectToAction() are sent as route values so even serialization cannot help here. At least not by itself - the routing must be setup appropriately ...
A different approach must be taken and during actionA, place your modelA into the TempData, then do redirect. In the actionC retrieve the model and carry on.
public ActionResult actionA (ViewModel_Shared modelA)
{
TempData["shared_model"] = modelA;
return RedirectToAction("actionC", "controllerC");
}
Later, in actionC:
public ActionResult actionC (ViewModel_Shared modelA)
{
var modelC = TempData["shared_model"];
modelC.FillEditedData();
ViewBag.Model = modelC;
return View();
}
Do note, that the objects in he TempData are alive only to the next request and then gone. Here you can read more about the TempData.
Yin
1) if you are using the ItemCustomer class as a property you should initialize it when you are using.
public ItemCustomer Item { get; set; }
2) if you are using ItemCustomer as object it will initialize when you constructor call as your example.
public ItemCustomer Item;
I have run the second point without get and set it's working fine.
I have a controller that uses two classes. One is called IndexModel and the other IndexViewModel.
I pass the IndexViewModel into the IndexModel constructor.
[HttpGet]
public ActionResult Index()
{
var model = new IndexModel(new IndexViewModel());
var vm = model.GetViewModel();
return View("Index", vm);
}
Here is the view model class. Notice that the setter is private.
public class IndexViewModel
{
public IList<SelectListItem> SelectListItems { get; private set; }
public IndexViewModel()
{
this.SelectListItems = new List<SelectListItem>();
}
}
Here is the Model. When GetViewModel is called the SelectListItems list is populated and the view model returned.
public class IndexModel
{
private IndexViewModel vm;
public IndexModel(IndexViewModel IndexViewModel)
{
this.vm = IndexViewModel;
}
public IndexViewModel GetViewModel()
{
this.FillSelectListItems();
return vm;
}
private void FillSelectListItems()
{
// this data is pulled in from another class that queries a database...
var data = ...
foreach (var itm in data)
{
vm.SelectListItems.Add(new SelectListItem()
{
Value = itm.Id.ToString(),
Text = itm.Text,
});
}
}
}
I would appreciate any comments on how this is currently structured, but my main questions are:
Is it bad practice to write a bunch of methods, like FillSelectListItems(), that alter collection data and don't return a result?
Should I make the setter public so I can just return a list from my method and set the view model property that way?
What do you have to gain by making it private? A headache... make it public :)
There aren't any problems using view models in other view models... Imagine having a blog post... BlogPostViewModel... you would expect it to also have comments right? BlogPostViewModel > CommentViewModel
public class BlogPostViewModel
{
public string BlogPostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public List<CommentViewModel> Comments { get; set; }
}
Now when you render that, on your PostsController, at Posts/{id}, the view Posts/Index.cshtml would be rendered, and your comments can be rendered inside a partial view...
// Take this as pseudo code as there's some syntatic errors (can't be asked to open VS)
#for(int i = ... i < Model.Comments.Length... i++){
this.Html.Partial("_CommentsPartial.cshtml, Model.Comments[i])
}
On another note, if you wanted, you could pass your Model to the view as a JSON object as well without with this neat little hack... In your controller action...
this.ViewBag.Json = JsonConvert.SerializeObject<TViewModel>(viewModel);
And in your view just pick it backup...
<script> var json = #this.ViewBag.Json </script>
Hopefully this has provided some insight with regards to the purpose these View Models serve...
What's I'm trying to create a select list with static items model.satsList and then selected Items will be set at model.SelectedStatsList
This is my Model :
public class StatModel
{
public IEnumerable<int> SelectedItemsRegion { set; get; }
public IEnumerable<string> SelectedStatsList { get; set; }
public static List<SelectListItem> statList = new List<SelectListItem>()
{
new SelectListItem() {Text="Request Number", Value="demandeR"},
new SelectListItem() { Text="agR", Value="agR"}
};
}
My view :
#Html.DropDownListFor(model=> model.SelectedStatsList, Model.statList)
I get this error :
'pfebs0.Models.StatModel.statList' cannot be accessed with an instance reference; qualify it with a type name instead
(One Solution)You can have a separate helper class in your project solution to handle the static data on your pages. for example:
//Helper.cs
public Helper class(){
public static List<SelectListItem> GetStatList(){
return new List<SelectListItem>()
{
new SelectListItem() {Text="Request Number", Value="demandeR"},
new SelectListItem() { Text="agR", Value="agR"}
};
} }
and in your controller class just bind this list to your Model class as below:
//MyController.cs
public ActionResult MyModel(){
var model = new MyModel();
model.SelectedStatsList = Helper.GetStatList();
return View("MyView", model);
}
make sure you have a right property on your model class to set statlist above.
and in your view you will have:
#Html.DropDownListFor(model=> model.SelectedStatsList, ... )
Simply change your view code as below:
#Html.DropDownListFor(model=> model.SelectedStatsList,pfebs0.Models.StatModel.statList)
// Assuming pfebs0.Models is the namespace for the StatModel
In C#, you cannot access static properties from an instance. In this case,property Model in the view is an instance of model that is being passed in.
In other words, you can access the static properties directly without creating an instance of an object.
Your problem is basic C# usage issue. You are trying to access the static field public static List statList through an instance, namely the model that you have in the Razor view.
Simply replace this (model.SelectedStatsList):
#Html.DropDownListFor(model=> model.SelectedStatsList, Model.statList)
With this (StatModel.SelectedStatsList):
#Html.DropDownListFor(model=> model.SelectedStatsList, StatModel.statList)
I have a ViewModel like so:
public class ProductEditModel
{
public string Name { get; set; }
public int CategoryId { get; set; }
public SelectList Categories { get; set; }
public ProductEditModel()
{
var categories = Database.GetCategories(); // made-up method
Categories = new SelectList(categories, "Key", "Value");
}
}
Then I have two controller methods that uses this model:
public ActionResult Create()
{
var model = new ProductEditModel();
return View(model);
}
[HttpPost]
public ActionResult Create(ProductEditModel model)
{
if (ModelState.IsValid)
{
// convert the model to the actual entity
var product = Mapper.Map(model, new Product());
Database.Save(product);
return View("Success");
}
else
{
return View(model); // this is where it fails
}
}
The first time the user goes to the Create view, they are presented with a list of categories. However, if they fail validation, the View is sent back to them, except this time the Categories property is null. This is understandable because the ModelBinder does not persist Categories if it wasn't in the POST request. My question is, what's the best way of keeping Categories persisted? I can do something like this:
[HttpPost]
public ActionResult Create(ProductEditModel model)
{
if (ModelState.IsValid)
{
// convert the model to the actual entity
var product = Mapper.Map(model, new Product());
Database.Save(product);
return View("Success");
}
else
{
// manually populate Categories again if validation failed
model.Categories = new SelectList(categories, "Key", "Value");
return View(model); // this is where it fails
}
}
But this is an ugly solution. How else can I persist it? I can't use a hidden field because it's a collection.
I would use the repository to fetch whatever data is needed and don't think it's an ugly solution:
[HttpPost]
public ActionResult Create(ProductEditModel model)
{
if (!ModelState.IsValid)
{
// manually populate Categories again if validation failed
model.Categories = Repository.GetCategories();
return View(model);
}
// convert the model to the actual entity
var product = Mapper.Map(model, new Product());
Database.Save(product);
// I would recommend you to redirect here
return RedirectToAction("Success");
}
To further refactor this I would recommend you watching the excellent Putting Your Controllers on a Diet video presentation by Jimmy Bogard.
I typically implement my lists (for drop downs) as a readonly property. When the View gets the value the property is self contained on what it needs to return the values.
public SelectList Categories
{
get
{
var categories = Database.GetCategories(); // made-up method
return new SelectList(categories, "Key", "Value");
}
}
If necessary you can grab the currently selected item (i.e. validation failed) from the property containing the id that was posted and bound to the instance of your class.
In my case I have a BaseModel class where I keep all those property list as class attributes.
Like in the following sample:
public IEnumerable<SelectListItem> CountryList
{
get
{
return GetCountryList().Select(
t => new SelectListItem { Text = t.Name, Value = Convert.ToString(t.CountryID) });
}
}
GetCountryList() is a function that ask a Singleton for data. This would only happen once in the app lifecycle
Another way for doing this, and if those lists are pretty big, would be to have a static utility class with the lookup table that returns the SelectListItem.
If you need to access a list that change from time to time then simply dont use a Singleton class.