ASP.NET Returning Multiple Variables to View - c#

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

Related

ASP.NET Core 2.1 Dropdown empty

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;

Passing my ViewModel to View and getting single data

My ViewModel looks that:
public class WerehouseViewModel
{
public Werehouse Werehouse { get; set; }
public WerehouseKey WerehouseKey { get; set; }
}
That's how I get data from database
var viewModel =
from Wh in db.Werehouses
join WhK in db.WerehouseKeys on Wh.WhID equals WhK.WhID
where Wh.WhID == id
select new WerehouseViewModel { Werehouse = Wh, WerehouseKey = WhK };
return View(viewModel);
I know I have to use IEnumerable type to show in my View, but I would like to show only one Werehouse (and only one is in view model) and Keys (let's say that there are three keys for one werehouse). How can I show one Werehouse in list and below all keys in table? Because when I use
#foreach (var x in Model)
{
<div>#x.Werehouse.Kod_magazynu</div>
<div>#x.Werehouse.Ulica</div>
<div>#x.Werehouse.Numer_magazynu</div>
<div>#x.Werehouse.Miasto</div>
}
It shows 3 times same data.
You can get the first warehouse only from your model:
#{
var warehouse = Model.Select(x=> x.Werehouse).FirstOrDefault();
<div>#warehouse.Kod_magazynu</div>
<div>#warehouse.Ulica</div>
<div>#warehouse.Numer_magazynu</div>
<div>#warehouse.Miasto</div>
}
Then you can loop to show the keys:
#foreach (var x in Model)
{
<div>#x.WerehouseKey.SomeProperty</div>
}

Reference an c# Object Field by its name passed as a string

I am writing a custom repoting module using ASP.NET MVC in C#
The user will be able to define a list of the fields they want to see in the report.
I would like to know if it is possible to reference a object field using a string, so that I can enumerate through the list of chosen fields.
for example normally in the view, quite basically I would do the following
#foreach (Title item in Model)
{
#item.Name
#item.Isbn
}
I would be looking for something like
#foreach (Title item in Model)
{
#item.Select("Name")
#item.Select("Isbn")
}
One of the ways to do that is through reflection. Add this helper method somewhere:
private object GetValueByPropertyName<T>(T obj, string propertyName)
{
PropertyInfo propInfo = typeof(T).GetProperty(propertyName);
return propInfo.GetValue(obj);
}
Usage:
#foreach (Title item in Model)
{
var name = GetValueByPropertyName(item, "Name");
var isbn = GetValueByPropertyName(item, "Isbn");
}
Well, i'd strongly recommend against using reflection in View as it breaks main principles of MVC pattern. YES, you should use reflection, but it is better to use it in controller. Let's have a look at simple and working example.
In controller we set up stub data to work with. And in action method About() we obtain a dynamic list of properties that user has selected :
class Title
{
// ctor that generates stub data
public Title()
{
Func<string> f = () => new string(Guid.NewGuid().ToString().Take(5).ToArray());
A = "A : " + f();
B = "B : " + f();
C = "C : " + f();
D = "D : " + f();
}
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
public string D { get; set; }
}
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
var data = new List<Title>()
{
new Title(), new Title(),
new Title(), new Title()
};
// list of properties to display for user
var fieldsSelectedByUser = new[] { "A", "C" };
// here we obtain a list of propertyinfos from Title class, that user requested
var propsInfo = typeof(Title).GetProperties().Where(p => fieldsSelectedByUser.Any(z => z == p.Name)).ToList();
// query that returns list of properties in List<List<object>> format
var result = data.Select(t => propsInfo.Select(pi => pi.GetValue(t, null)).ToList()).ToList();
return View(result);
}
...
}
And in view we can use it by simply iterating the collection :
#model List<List<object>>
<br/><br />
#foreach (var list in #Model)
{
foreach (var property in list)
{
<p> #property </p>
}
<br/><br />
}
P.S.
According to MVC pattern, view should utilize data returned by controller but should at no circumstances perform any business logic and comprehensive operations inside of it. If view need some data in some format - it should get this data returned by controller exactly in the format it needs.
I'm not experienced with asp, so I don't know for sure if this is possible in your specific context.
But normally you could use reflection for that. But you had to know if you are looking for properties or fields
For fields:
FieldInfo fi = item.GetType().GetField("Name", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
var value = fi.GetValue(item); // read a field
fi.SetValue(item, value); // set a field
For properties:
PropertyInfo pi = item.GetType().GetProperty("Name", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
var value = pi.GetValue(item); // read a property
pi.SetValue(item, value); // set a property
The word to google for is "Reflection" and most methods can be found in the Type class.

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.

how to retrieve the values of the attributes of a nested class in a view?

I am busy in my homecontroller.cs (in a MVC Razor project) where I send a class looking like the following one to my view:
private class Evt
{
public SEvent Event { get; set; }
public String Duration { get; set; }
};
public ActionResult Index()
{
List<SEvent> modFromWcf = proxy.GetAllEventsByPeriod(#System.DateTime.Now.Year, #System.DateTime.Now.Year + 1, "EN").ToList();
List<Evt> modTosend = new List<Evt>();
foreach (SEvent item in modFromWcf)
{
Evt ES = new Evt();
ES.Event = item;
ES.Duration = GetDuration(item.StartDate ,item.EndDate);
modTosend.Add(ES);
};
return View("Index", modTosend);
}
SEvent is just another class.
In my view I try to do something with some of the attributes, but I do not know how to retrieve the values of that class Event. This is my code:
#{
foreach (var item in ViewData.Model) {
string year = item.Event.StartDate.Year.ToString();
}
....
}
The error that I receive is: 'object' does not contain a definition for 'Event'. But I CAN debug and see that item DOES consist ofa class Event and a string Duration. I can see the contents too. Does somebody know how I can retrieve e.g. item.Event.StartData??
You need to explicitly define item.
ViewData.Model will return a collection of objects, and objects do not have Event properties.
Something like this should work:
#{
foreach (var item in ViewData.Model) {
if (item is Evt){
Evt itemEvt = (Evt)item;
string year = itemEvt.Event.StartDate.Year.ToString();
}
}
//...
}
Though I usually do something like:
#{
foreach (Evt item in ViewData.Model) {
string year = item.Event.StartDate.Year.ToString();
}
//....
}
You need to cast item to your class.
ViewData is of type ViewDataDictionary, which implements IDictionary<string, Object>, indicating that it is a dictionary matching keys of type string to values of type object.
Use this to make your code run as is:
#{
foreach (var item in ViewData.Model) {
string year = ((Evt)item).Event.StartDate.Year.ToString();
}
....
}
However, you should probably be using a strongly typed view for this instead of shoving data into ViewData for this.

Categories