I am trying to build a drop down list using enum.
I tried the following, but do not know how to display it in view. I am using MVC framework
public enum Condition
{
And,
Or,
Not,
}
private List<Condition> userTypes = Enum.GetValues(typeof(Condition)).Cast<Condition>().ToList();
public List<Condition> UserType
{
get
{
return userTypes;
}
set
{
userTypes = value;
}
}
Is the above code right to display a simple drop down list?
And how do I pass in it view to display drop down list.
Thanks
in your Action :
ViewData["ddl"] = userTypes.Select(t => new SelectListItem { Text = t.ToString(), Value = ((int)t).ToString() });
in your aspx :
<%=Html.DropDownList("ddl", ViewData["ddl"] as IEnumerable<SelectListItem>)%>
Rest of this is alright.
You suppose to return string list from property UserType not Condition type. Secondly property must is of readonly since enum is constant and user won`t going to change it. Lastly don't use variable, property itself handle this.
public List<string> UserType
{
get
{
return Enum.GetNames(typeof(Condition)).ToList();
}
}
In your model add a List like:
private List conditionList= Enum.GetValues(typeof(Condition))
.Cast()
.Select(e => new SelectListItem { Value = ((int)e).ToString(), Text = e.ToString() });
And Then just add this on your view
#Html.EditorFor(m=>m.Condition,Model.conditionList)
I believe that will make things more easier.
Related
I am using cache on SelectListItems in my asp.net MVC app to store SelectListItems I'm using on lots of pages. The issue is that when I'm using it trough DropDownListFor, if I provide a selected value to this DropDownListFor, the SelectListItems looks to be changed... And I want to keep th SelectListItems in cache without "selected" property!
Here is the cache:
public IEnumerable<SelectListItem> GetAllPersonnelCached()
{
CacheHelper.SaveToCache("mykey", valueToCache, 240); //HttpContext.Current.Cache.Add
CacheHelper.GetFromCache<IEnumerable<SelectListItem>>("mykey"); //HttpContext.Current.Cache["mykey"]
}
This is the properties of the model I'm using
public int? personnelSelected{ get; set; }
public IEnumerable<SelectListItem> personnelList{ get; set; }
And How I fill it:
responsibleSelected = 100;
personnelList = GetAllPersonnelCached();
And here is how I am using the data on HTML part
#Html.DropDownListFor(x => x.personnelSelected, Model.personnelList, "PlaceHolder", new { data_placeholder = " " })
When I am running this code for a webpage, it works well. BUT then, when I call the GetAllPersonnelCached, it gives me all item as expected but the ListItem that has id 100 is "selecteditem". WHY? The fact that I'm using DropDownListFor makes changes on the List (referenced by the cache property in memory)? If yes, how to prevent this? Make the select list item readonly?
Thanks to all
The source code of the DropDownListFor shows that this extension method internally sets the Selected property.
Because a SelectListItem is a reference type, this change occurs on the corresponding item in the cache.
One way to prevent this is to return new SelectListItem objects from the GetAllPersonnelCached method, instead of the original cached ones.
public IEnumerable<SelectListItem> GetAllPersonnelCached()
{
CacheHelper.SaveToCache("mykey", valueToCache, 240);
var cachedItems = CacheHelper.GetFromCache<IEnumerable<SelectListItem>>("mykey");
return cachedItems.Select(o => new SelectListItem(o.Text, o.Value);
}
You might consider not to cache the SelectListItem instances, but the personnel-data objects instead which you transform to SelectListItem instances on retrieval.
// Assume your PersonnelData looks like below.
class PersonnelData
{
int Id { get; set; }
string Name { get; set; }
}
public IEnumerable<SelectListItem> GetAllPersonnelCached()
{
// valueToCache is a list of PersonnelData objects.
CacheHelper.SaveToCache("mykey", valueToCache, 240);
var cachedPersonnelData = CacheHelper.GetFromCache<IEnumerable<PersonnelData>>("mykey");
return cachedPersonnelData.Select(o => new SelectListItem(o.Name, o.Id.ToString());
}
I have this dropdown that should contain a list of items i.e.
Debug
Information
Warning
Danger
This is how I obtain the list
public IEnumerable<string> GetLogLevels()
{
var data = dbContext.EventLogs.Distinct().ToList();
var modifiedData = data.Select(u => u.Level);
return modifiedData;
}
This is my controller and viewmodel
public IActionResult Index()
{
var levels = new SelectList(logsData.GetLogLevels(),"Level","Level");
var llvm = new LevelsListViewModel
{
Levels = levels
};
return View(llvm);
}
public class LevelsListViewModel
{
public SelectList Levels { get; set; }
}
This is how I declare it in my view
var level = "<div class='col-sm-3 search-spacing'><label for='Level'>Level</label><select asp-for='Level' asp-items='#Model.Levels'></select></div>";
The problem now is that for some reason it loads an empty list. When I debug the controller, I can see my values.
On a sidenote, is that the correct way of obtaining one field and populating the list? I want a distinct value only.
The problem lies in this line:
var levels = new SelectList(logsData.GetLogLevels(),"Level","Level");
The GetLogLevels method returns a collection of string, which does not contain a Level property.
There's two ways of resolving this:
change the return type of the GetLogLevels method so it returns a collection of event logs which have a Level property; or
use a different contructor of the SelectList class which only takes a collection of objects without specifying the value property name nor the text property name, like var levels = new SelectList(logsData.GetLogLevels());
This line causing problem because GetLogLevels() method returns IEnumerable<string>, which doesn't have property named Level in its context:
var levels = new SelectList(logsData.GetLogLevels(),"Level","Level");
There are some solutions to solve this issue:
1) Converting directly to IEnumerable<SelectListItem>
// Controller action
var levels = logsData.GetLogLevels().Select(x => new SelectListItem { Text = x, Value = x }).ToList();
// Model class
public class LevelsListViewModel
{
public List<SelectListItem> Levels { get; set; }
}
2) Using SelectList overload with single parameter
var levels = new SelectList(logsData.GetLogLevels());
3) Using ToDictionary()
var levels = new SelectList(logsData.GetLogLevels().ToDictionary(), "Key", "Value");
Note: If EventLogs contains multiple columns, you can't use Distinct() on that way. You should use GroupBy and Select first distinct values:
var data = dbContext.EventLogs.GroupBy(x => x.Level).Select(gr => gr.FirstOrDefault()).ToList();
return data;
When I am changing the "model => model.id" to "model => model.Supplierid" i am getting below error
"The parameter 'expression' must evaluate to an IEnumerable when
multiple selection is allowed."
please have look on below code
// this my model class
public class clslistbox{
public int id { get; set; }
public int Supplierid { get; set; }
public List<SuppDocuments> lstDocImgs { get; set; }
public class SuppDocuments
{
public string Title { get; set; }
public int documentid { get; set; }
}
public List<SuppDocuments> listDocImages()
{
List<SuppDocuments> _lst = new List<SuppDocuments>();
SuppDocuments _supp = new SuppDocuments();
_supp.Title = "title";
_supp.documentid = 1;
_lst.Add(_supp);
return _lst;
}
}
// this my controller
[HttpGet]
public ActionResult AddEditSupplier(int id)
{
clslistbox _lst = new clslistbox();
_lst.lstDocImgs= _lst.listDocImages();
return View(_lst);
}
// this is view where i am binding listboxfor
#model clslistbox
#using (Html.BeginForm("AddEditSupplier", "Admin", FormMethod.Post))
{
#Html.ListBoxFor(model => model.id, new SelectList(Model.lstDocImgs, "documentid", "title"))
}
Can anyone see the reason for it?
I think the changing of the property in the expression here is a red-herring - it won't work in either case.
Update
However, see at the end of my answer for some probably needlessly detailed exposition on why you didn't get an error first-time round.
End Update
You're using ListBoxFor - which is used to provide users with multiple selection capabilities - but you're trying to bind that to an int property - which cannot support multiple selection. (It needs to be an IEnumerable<T> at least to be able to bind a list box to it by default in MVC)
I think you mean to be using DropDownListFor - i.e. to display a list of items from which only one can be selected?
If you're actually looking for single-selection semantics in a listbox, that's trickier to do in MVC because it's Html helpers are geared entirely around listboxes being for multiple selection. Someone else on SO has asked a question about how to get a dropdown to look like a list box: How do I create a ListBox in ASP.NET MVC with single selection mode?.
Or you could generate the HTML for such a listbox yourself.
(Update) - Potentially needlessly detailed exposition(!)
The reason you don't get an exception first time round is probably because there was no value for id in ModelState when the HTML was generated. Here's the reflected MVC source (from SelectExtensions.SelectInternal) that's of interest (the GetSelectListWithDefaultValue call at the end is the source of your exception):
object obj =
allowMultiple ? htmlHelper.GetModelStateValue(fullHtmlFieldName, typeof(string[])) :
htmlHelper.GetModelStateValue(fullHtmlFieldName, typeof(string));
if (!flag && obj == null && !string.IsNullOrEmpty(name))
{
obj = htmlHelper.ViewData.Eval(name);
}
if (obj != null)
{
selectList =
SelectExtensions.GetSelectListWithDefaultValue(selectList, obj, allowMultiple);
}
Note first that the control variable allowMultiple is true in your case, because you've called ListBoxFor. selectList is the SelectList you create and pass as the second parameter. One of the things that MVC (unfortunately in some cases) does is to use ModelState to modify the select list you pass when re-displaying a view in order to ensure that values which were set in ModelState via a POST are re-selected when the view is reloaded (this is useful when page validation fails because you won't copy the values to your underlying model from ModelState, but the page should still show those values as being selected).
So as you can see on the first line, the model's current value for the expression/field you pass is fished out of model state; either as a string array or as a string. If that fails (returns null)then it makes another go to execute the expression (or similar) to grab the model value. If it gets a non-null value from there, it calls SelectExtensions.GetSelectListWithDefaultValue.
As I say - what you're trying to do will ultimately not work in either the case of Id or SupplierId (because they would need to be IEnumerable) but I believe this ModelState->Eval process is yielding a null value when you use Id, so the process of getting an 'adjusted' SelectList is skipped - so the exception doesn't get raised. The same is not true when you use SupplierId because I'll wager that there's either a value in ModelState at that point, or the ViewData.Eval successfully gets an integer value.
Not throwing an exception is not the same as working!.
End update
Try changing your property from int to int[]
public class SuppDocuments
{
public string Title { get; set; }
public int documentid { get; set; }
}
Assuming above is the class used for binding the model , try changing the documentid property as below
public class SuppDocuments
{
public string Title { get; set; }
public int[] documentid { get; set; }
}
I have a large model (large I mean model class contains a lot of fields/properties and each has at least one validation attribute (such as Required, MaxLength, MinLength etc)). Instead of creating one view with a lot of inputs for user to fill model with data I want to create several views where user will fill part of model fields and go to the next step (some kind of "wizard"). While redirecting between steps I store not fullfilled model object in Session. Something like below:
Model:
public class ModelClass
{
[MaxLength(100)] ...
public string Prop1{get;set;}
[MaxLength(100)] ...
public string Prop2{get;set;}
...
[Required][MaxLength(100)] ...
public string Prop20{get;set;}
}
Controller:
[HttpPost]
public ActionResult Step1(ModelClass postedModel)
{
// user posts only for example Prop1 and Prop2
// so while submit I have completly emty model object
// but with filled Prop1 and Prop2
// I pass those two values to Session["model"]
var originalModel = Session["model"] as ModelClass ?? new ModelClass();
originalModel.Prop1 = postedModel.Prop1;
originalModel.Prop2 = postedModel.Prop2;
Session["model"] = originalModel;
// and return next step view
return View("Step2");
}
[HttpPost]
public ActionResult Step2(ModelClass postedModel)
{
// Analogically the same
// I have posted only Prop3 and Prop4
var originalModel = Session["model"] as ModelClass;
if (originalModel!=null)
{
originalModel.Prop3 = postedModel.Prop3;
originalModel.Prop4 = postedModel.Prop4;
Session["model"] = originalModel;
// return next step view
return View("Step3");
}
return View("SomeErrorViewIfSessionBrokesSomeHow")
}
Step1 view has inputs only for Prop1 and Prop2, Step2 view contains inputs for Prop3 and Prop4 etc.
BUT HERE IS THE THING
When user is on, for example, step 1, and fills Prop1 with value more than 100 characters length client side validation works fine. But, of course , I have to validate this value and on the server side in controller. If I had full model I'd just do the following:
if(!ModelState.IsValid) return View("the same view with the same model object");
so user has to fill the form again and correct.
BUT on step 1 user has filled only 2 properties of 20, and I need to validate them. I can't use ModelState.IsValid because model state will be invalid. As You can see Prop20 is marked with [Required] attribute, when user submits Prop1 and Prop2, Prop20 is null and that's why ModelState is invalid. Of course I could allow user to go to step2, fill all of the steps and validate model state only on the last step but I don't want to allow user to go to step 2 if he filled step 1 incorrect. And I want this validation in controller. So the question is:
How can I validate only part of the model? How can I verify that only some of the model properties match their validation attributes?
One possible solution:
Use ModelState.IsValidField(string key);
if (ModelState.IsValidField("Name") && ModelState.IsValidField("Address"))
{ ... }
Then at the end when everything is done use:
if(ModelState.IsValid) { .. }
I think the most elegant way is to do it like that:
List<string> PropertyNames = new List<string>()
{
"Prop1",
"Prop2"
};
if (PropertyNames.Any(p => !ModelState.IsValidField(p)))
{
// Error
}
else
{
// Everything is okay
}
or:
List<string> PropertyNames = new List<string>()
{
"Prop1",
"Prop2"
};
if (PropertyNames.All(p => ModelState.IsValidField(p)))
{
// Everything is okay
}
else
{
// Error
}
In MVC Core, this will be the equivalent of:
if (ModelState.GetFieldValidationState("Name") == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Valid)
{
// do something
}
However, I would recommend simply creating a separate view model in this instance.
Your partial view model could be inherited by your larger view model so you won't have to repeat yourself in code (DRY principal).
It's better to avoid hard-coding the property names!
Just to add to the existing answers for this. Rather than hardcoding the property names I would use an attribute to be added along with the rest of your validation attributes along the lines of:
public class ValidationStageAttribute : Attribute
{
public int StageNumber { get; private set; }
public ValidationStageAttribute(int stageNumber)
{
StageNumber = stageNumber;
}
}
Now that we can get the property names without knowledge of the model itself the partial validation can be pulled into a method (if you use it a lot, your base controller would be a good spot).
protected bool ValidateStage(object viewModel, int stageToValidate)
{
var propertiesForStage = viewModel.GetType()
.GetProperties()
.Where(prop => prop.GetCustomAttributes(false).OfType<ValidationStageAttribute>().Any(attr => attr.StageNumber == stageToValidate))
.Select(prop => prop.Name);
return propertiesForStage.All(p => ModelState.IsValidField(p));
}
Now all you'd need to do in your post action would be to call ValidateStage(viewModel, 1)
My model looks like this
public IEnumerable<SelectListItem> ProductTypes { get; set; }
public ProductContent()
{
productxEntities db = new productxEntities();
ProductTypes = db.ProductCodes.Select(c => new SelectListItem
{
Value = c.product_type.ToString(),
Text = c.code.ToString()
});
}
when i try to use it for DropDownList I get a error saying casting is wrong... what is the correct way of populating DDL using list from DB in MVC3 Razor C#, i have a tightly coupled view for this model type.
#Html.DropDownList("ProductTypes", (IEnumerable<SelectListItem>) Model.ProductTypes)
this is the error
Unable to cast object of type 'System.Data.Entity.Infrastructure.DbQuery1[System.Web.WebPages.Html.SelectListItem]' to type 'System.Collections.Generic.IEnumerable1[System.Web.Mvc.SelectListItem]'.
this is my controller
public ActionResult Create()
{
ProductContent productViewModel = new ProductContent();
return PartialView("../PartialViews/NewProduct", productViewModel);
}
You should be calling ToList somewhere in your EF query. Otherwise you are returning a Queryable directly to your View.
Possibly like this:
public ProductContent()
{
productxEntities db = new productxEntities();
ProductTypes = db.ProductCodes.ToList().Select(c => new SelectListItem
{
Value = c.product_type.ToString(),
Text = c.code.ToString()
});
}
As I mentioned in my comment though; I'd discourage this kind of code in a constructor of a Model. Someone else should be assigning it to the Model.
You should then be able to remove your cast in your View.
The root of the problem is that the types System.Web.WebPages.Html.SelectListItem and System.Web.Mvc.SelectListItem are not assignable.
It's likely that there are different namespace imports in the controller to the view. You need to be explicit in this case: either the model needs to use new System.Web.Mvc.SelectListItem(...) or the view needs to cast to (IEnumerable<System.Web.WebPages.Html.SelectListItem>).