ASP.NET Core 2.1 Dropdown empty - c#

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;

Related

DropDownListFor is changing SelectedItem I've put on Cache

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());
}

ASP.NET Returning Multiple Variables to View

I am having trouble figuring out how to return multiple variables to a view. Something like this. Can I get a little help?
public ActionResult CheatSheet()
{
var var1 = from ts in db.thisdatabase
select ts;
var var2 = from ls in db.thisdatabase
select ls;
var var3 = from d in db.thisdatabase
select d;
return View(var1,var2,var3);
}
Consider Using a ViewModel
You'll want to use a ViewModel to compose all of these different results and pass that model to the View:
public class ExampleViewModel
{
// Example collections for each of your types
public IEnumerable<Something> CollectionA { get; set; }
public IEnumerable<Something> CollectionB { get; set; }
public IEnumerable<Something> CollectionC { get; set; }
}
And then just use something like the following code to perform your specific queries and use the results of those queries to build your model and then pass it along to the View:
// Build the model
var model = new ExampleViewModel(){
// You'll likely want a .ToList() after these to ensure things work as expected
CollectionA = db.thisdatabase.Select(x => x.ts),
CollectionB = db.thisdatabase.Select(x => x.ts),
CollectionC = db.thisdatabase.Select(x => x.ts),
};
// Pass it to your View
return View(model);
Note: This assumes that you aren't actually querying the same exact table with each of your queries. If that was the case, then it may be more efficient to pull back a single collection with each of your properties and then assign your individual collections of properties to the model (instead of performing multiple, possibly redundant, queries.
Then within the View, you can reference the underlying property collections as expected and iterate through them or perform any other types of operations:
#model YourProject.ViewModels.ExampleViewModel
#foreach (var item in Model.CollectionA) { ... }
#foreach (var item in Model.CollectionB) { ... }
#foreach (var item in Model.CollectionC) { ... }
For More Complex Scenarios
If you didn't want to simply access a single column from your database but rather multiple, you'll likely want to create another model/class to map your properties and then store instances of those within your ViewModel.
Let's look at your example and see how that might work. So you currently are looking to store the ts, ls and d properties, so let's make a class to store those in:
public class Example
{
public string Ts { get; set; }
public string Ls { get; set; }
public string D { get; set; }
}
Now, when you perform your query, simply grab all of these and map them within a Select() call:
// This will now be an IEnumerable<Example>
var models = db.thisdatabase.Select(x => new Example()
{
Ts = x.ts,
Ls = x.ls,
D = d
});
You could now pass this along directly to your View if that's all that you needed:
// If you did this, you'd need to adjust your #model declaration
return View(model);
Or you could perform multiple of these if you needed to build different models for different tables and then compose all of those collections into a ViewModel similar to the original example:
var model = new ExampleViewModel(){
CollectionA = db.thisdatabase.Select(x => new Example(x)),
CollectionB = db.othertable.Select(x => new OtherExample(x)),
CollectionC = db.yetanother.Select(x => new LastExample(x))
};
Other Forms Of Storage
There are a few other approaches you could consider depending on your needs, such as using the ViewBag collection. The ViewBag is a dynamic collection that allows you to easily store and access objects within the View:
ViewBag.A = db.thisdatabase.Select(x => x.ts),
ViewBag.B = db.thisdatabase.Select(x => x.ts),
ViewBag.C = db.thisdatabase.Select(x => x.ts),
return View();
This will essentially work the same way, but instead of references #Model within your View, you would just use #ViewBag instead. It's also worth noting that this can also be used in conjunction with an actual model as well.
There are other approaches such as using the ViewData or Session collections, but you really should use a model. It's more in line with the actual MVC pattern, and if you get used to it, it should make your life much easier.
You can pass dynamic model in you view using ExpandoObject.
Example:
Controller:
public ExpandoObject ToExpando( object anonymousObject)
{
IDictionary<string, object> anonymousDictionary = new RouteValueDictionary(anonymousObject);
IDictionary<string, object> expando = new ExpandoObject();
foreach (var item in anonymousDictionary)
expando.Add(item);
return (ExpandoObject)expando;
}
public ActionResult CheatSheet()
{
var var1 = from ts in db.thisdatabase
select ts;
var var2 = from ls in db.thisdatabase
select ls;
var var3 = from d in db.thisdatabase
select d;
var model= ToExpando(new{ var1 =var1 ,var2 =var2 , var3 =var3})
return View(model);
}
View:
#foreach (var item in Model.var1 ) { ... }
#foreach (var item in Model.var2 ) { ... }
#foreach (var item in Model.var3 ) { ... }

MVC5 Custom View Model, Get Changes, Update Database

I have a complex object that I've broken down to a ViewModel for form. I'll illustrate a sample of the structure but the number of fields are around 60 total from about 6 different entities intermingled.
//ViewModel
public class SomeViewModel
{
public TabOne TabOne {get;set;}
public TabTwo TabTwo {get;set;}
public SomeViewModel(ComplexObject co)
{
this.TabOne = new TabOne { Name = co.Name, Value = co.Value};
this.TabTwo = new TabTwo { Name = co.Name, Another = co.Another };
}
}
All this works fine and I get to the Controller from the View
//Controller
[HttpPost]
public ActionResult Update(SomeViewModel vm)
{
//TODO: 1. Know which properties in the ViewModel changed for auditing
//TODO: 2. Update all changed EF entities in the database.
}
Before sending the ViewModel to the View I cache the original Value in a Session object and use reflection to compare the two. It's drawn out and I'll have to do a ton of work for any changes. Is there a better way??
The ViewModel is different structure from the EF Models, so it's a bunch of custom mapping that will need to be done to Update the correct entities. I'd like to avoid that because of the number of fields. Is there a better way?
Ok, not sure if this is what you need but for what I understood:
You can get the original and current values like this:
using (var dbCtx = new YourDBEntities())
{
var yourEntity = dbCtx.YourEntity.Find(1);
var entry = dbCtx.Entry(yourEntity);
foreach (var propertyName in entry.CurrentValues.PropertyNames )
{
Console.WriteLine("Property Name: {0}", propertyName);
var originalVal = entry.OriginalValues[propertyName];
Console.WriteLine("Original Value: {0}", originalVal);
var currentVal = entry.CurrentValues[propertyName];
Console.WriteLine("Current Value: {0}", currentVal);
}
}
For mapping ViewModels to your EF Models you can use Automapper, it can be as easy as:
AutoMapper.Mapper.CreateMap<Book, BookViewModel>();
var model = AutoMapper.Mapper.Map<BookViewModel>(book);
Edit:
Ok, if you need to do it manually, you can use the following method for mapping objects:
public static void MapObjects( object source, object destiny)
{
var modelPropertiesName = new HashSet<string>(source.GetType().GetProperties().Select(x => x.Name));
var entityProperties = destiny.GetType().GetProperties();
var propertyList = entityProperties.Where(p => modelPropertiesName.Contains(p.Name))
.ToList();
foreach (var prop in propertyList)
{
var modelProperty = source.GetType().GetProperty(prop.Name);
var value = modelProperty.GetValue(source);
prop.SetValue(destiny, value, null);
}
}
You just need to pass a destiny object and a source from where to map the properties.

Dropdownlist with IEnumerable Data type

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>).

How can I turn an anonymous type into a key value pair object

I'm trying to figure out a clean way of getting data back from my web service that is key/value pair.
Is there a way to take a query like this:
var q = db.Ratings.Select(x => new
{
x.pro,
x.con,
x.otherThoughts,
x.Item.title,
x.score,
x.subject
});
And just return a key value pair where the key would be the columns listed above, with their corresponding value?
Rather than creating a separate class?
myDict.Add("pro", q.pro);
myDict.Add("con", q.con);
This is not efficient and creating a class like this isn't either, especially if I have dozens of methods:
public class Rating
{
public string pro { get; set; }
public string con { get; set; }
}
All of my searches turn up examples that contain the previous two code samples.
I don't recommend to use the 'untyped' approach that you are looking at.
I would use typed objects instead, the ones that you don't want to create.
However, here is the answer to your question. You can use DataTable object for what you need. Like this:
var items = new[]
{
new Item { Id = 1, Name = "test1" },
new Item { Id = 2, Name = "test2" }
};
var dataTable = new DataTable();
var propeties = typeof(Item).GetProperties();
Array.ForEach(propeties, arg => dataTable.Columns.Add(arg.Name, arg.PropertyType));
Array.ForEach(items, item => dataTable.Rows.Add(propeties.Select(arg => arg.GetValue(item, null)).ToArray()));
return dataTable;

Categories