I'm trying to develop an mvc page that upfront we don't know what model to use until certain conditions are met. When we have the model needed, the view has to resemble that information.
Since we are using MVC 4 and using models with lambda notation we can't render every property from the model because all of them have different properties with different variable types and we need to use only one view and one controller to accomplish this.
I implemented dynamic classes but got stuck when trying to render the view since I don't know what properties will there be nor the names they have.
Any pointers how to get this done or tutorials out there that explain this situation.
I don't know if this answers your question, but you can specify both what model you want to send, and what view you want to send it to, without really doing anything too fancy:
public ActionResult Index(int id)
{
var model1 = new Model1();
var model2 = new Model2();
if (id == 1)
{
return View("ViewForModel1", model1);
}
if (id == 2)
{
return View("ViewForModel2", model2);
}
// if it gets this far, return index
return View();
}
MSDN: http://msdn.microsoft.com/en-us/library/dd460310(v=vs.118).aspx
This is messy. But if your view takes a dynamic model you could do something like this in your view:
#model dynamic
#if(Model is Model1)
{
...
return;
}
#if(Model is Model2)
{
...
return;
}
In general if you want something dynamic it might be best to do things in a weakly typed fashion. You might want to avoid using the lambda helpers and instead use the weakly typed html helper counter parts. You can get the property names using reflection or the model meta data engine. You might also want to take a look at Dynamic MVC.
http://dynamicmvc.com
Related
I've been able to successfully return a model to a view and display the results in a strongly-typed fashion.
I have never seen an example where multiple models get returned. How do I go about that?
I suppose the controller would have something like this:
return View(lemondb.Messages.Where(p => p.user == tmp_username).ToList(), lemondb.Lemons.Where(p => p.acidity >= 2).ToList());
Does MVC let you return multiple models like that?
And then in the view I have this line at the top of the file:
#model IEnumerable<ElkDogTrader.Models.Message>
And I frequently make calls to "model" in the view.
#foreach (var item in Model)
If there were 2 models, how would I refer to them separately?
Is this possible with multiple models, or is this why people use ViewBag and ViewData?
You can create a custom model representing the data needed for your view.
public class UserView
{
public User User{get;set;}
public List<Messages> Messages{get;set;}
}
And then,
return View(new UserView(){ User = user, Messages = message});
In the view:
Model.User;
Model.Messages;
The ViewBag is useful because it is dynamically typed, so you can reference members in it directly without casting. You do, however, then lose static type checking at compile time.
ViewData can be useful if you have a one-off on your view data types and know the type and will be doing a cast in the view anyway. Some people like to keep the actual typed view pure in a sense that it represents the primary model only, others like to take advantage of the type checking at compile time and therefore make custom models needed for the view.
I believe ViewModel should be the way to go. Within the customary ViewModel, you can reference other models or define all the related domain models in the viewModel itself.
I'm learning MVC and I'm trying to create an implicitly typed variable for my model but not sure how to reference it in the view #model IEnumerable<???>
Controller...
public ActionResult Users() {
var model = from MembershipUser u in Membership.GetAllUsers()
select new {user = u, roles = string.Join(", ", Roles.GetRolesForUser(u.UserName))};
View(model);
}
You can't reference an anonymous-typed variable in the view (or anywhere outside its local scope). It's possible to use a dynamic type:
#model IEnumerable<dynamic>
But this way you don't have Intellisense/compile-time type checking; the best approach would be to just create a class for your model.
That's an anonymous type you're returning. I believe you'll want to use an actual type. If you don't have one for what you're selecting in your query, you'll want to create one.
Once you have your type, here's an example of using it in the view.
This is the first line in my view:
#model IEnumerable<AutoTrackerCommon.Entities.TrackerJob>
And I'm using it in a WebGrid:
#{
var grid = new #WebGrid(
source: Model,
rowsPerPage: 10);
}
You reference it as Model, but why would you do such a thing?
Use ViewModels, don't go with untyped or implicitly typed models. Its a nightmare to work with: you lose everything from IDE support to automatic validation. Don't complicate your life unnecessarily. View Models (or strongly typed views) are the way to go.
Good article to read about this: View Models in ASP.NET MVC.
Hope this is of help to you & good luck
Still Learning MVC3, EF. For now I am connecting to MySql but I believe that will not be relevant. For simplicity, I decided to use one database for my test application and I have included a category to differentiate the data. For eg I have a news, events,info and pages categories. Now when it comes to listing contents in views for example at the homepage, I want to list latest 5 news items(news category), latest 5 events(events category), welcome text(info category). i have been able to create partialViews to list these at the different sections of the homepage. But I feel am doing this wrongly since in each of these PartialViews I am querying the same table over and over and just filtering with where cat=....in the LINQ query.
Can you please confirm if that should be the practice or there is a better way to do this.
You could do the following:
Controller:
public ActionResult Home()
{
IEnumerable<MyDateRecords> myData = LinqQueryToGetAllTheDataUnFiltered();
ViewData.Model = new MyViewData { MyData = myData; }
return View();
}
ViewModel class:
public class MyViewData
{
List<MyDataRecords> MyData { get; set; }
List<MyDataRecords> News { get { return MyData.Where(m => m.Category = "News"); } }
List<MyDataRecords> Events { get { return MyData.Where(m => m.Category = "Events"); } }
}
View:
#model MyViewModel
#Html.Partial("NewsPartial", Model.News)
#Html.Partial("EventsPartial", Model.Events)
Partial:
#model IEnumerable<MyDataRecord>
This way we only queried for the data once and just passed a different set to each partial
For an uncomplicated way of presenting this type of data, what you are doing is fine. You should look at the OutputCacheAttribute for any PartialView Method you use on your Controller.
This is pretty inefficient. But it's good that you noticed this because querying that database is often the bottleneck in any given request.
For starters you should retrieve that data into a dictionary or model and then pass it to the partial views to render similar to what Bassam outlined. Ideally, this should be taken care of in the Controller to stick to the MVC paradigm and then passed to the main view, which would then pass the appropriate data to the partial views.
Once you get more advanced/familiar with ASP.NET MVC, you can start looking into caching. I'd stay away from caching for right now since it get somewhat tricky if you have data that is rapidly changing since you need to start worrying about updating/synchronizing/etc.
Let's say I have a theoretical MVC framework that uses a ViewData object to pass data from the controller to the view. In my controller, let's say I have some code like this (in pseudocode):
function GetClientInfo()
{
// grab a bunch of data from the database
var client = Database.GetClient();
var clientOrders = Database.GetClientOrders();
var clientWishList = Database.GetClientWishList();
// set a bunch of variables in the ViewData object
ViewData.set("client", client);
ViewData.set("clientOrders", clientOrders);
ViewData.set("clientWishList", clientWishList);
showView("ClientHomePage");
}
And then in my ClientHomePage view, I display the data like so:
<p>Welcome back, [ViewData.get("client").FirstName]!</p>
<p>Your order history:</p>
<ul>
[Html.ToList(ViewData.get("clientOrders")]
</ul>
<p>Your wishlist:</p>
<ul>
[Html.ToList(ViewData.get("clientWishList")]
</ul>
This is what I understand MVC to be like (please correct me if I'm wrong). The issue I'm having here is those magic strings in the view. How does the view know what objects it can pull out of the ViewData object unless it has knowledge of what the controller is putting in there in the first place? What if someone does a refactor on one of the magic strings in the controller, but forgets to change it in the view, and gets a runtime bug instead of a compile-time error? This seems like a pretty big violation of separation of concerns to me.
This is where I'm thinking that a ViewModel might come in handy:
class ClientInfo
{
Client client;
List clientOrders;
List clientWishList;
}
Then the controller creates an instance of ClientInfo and passes it to the view. The ViewModel becomes the binding contract between the controller and the view, and the view does not need to know what the controller is doing, as long as it assumes that the controller is populating the ViewModel properly. At first, I thought this was MVVM, but reading more about it, it seems like what I have in mind is more MVC-VM, since in MVVM, the controller does not exist.
My question is, what am I not understanding here about MVC vs. MVVM? Is referring to variables in the ViewData by magic strings really not that bad of an idea? And how does one insure that changes made in the controller won't adversely affect the view?
Your understanding of MVC is wrong, it stands for Model View Controller but you are missing the Model in your example. This is the typed entity that gets passed back to the View to do the rendering. In ASP.Net MVC you would use typed Views that also type the Model within the View so it is checked at compile time. This eliminates the need for magic strings (second part of your question).
In MVVM you have Model View ViewModel. This is a way of binding a ViewModel directly to the UI layer via a View which is used a lot in WPF. It replaces the need for a controller and it's generally a 1-to-1 mapping with the UI. It's just an alternative mechanism that solves the same problem (of abstraction and seperation of concerns) but better suited to the technology.
Theres some useful info here which might help understand the difference.
Best approach to use strongly typed views
Models:
public class ContentPage
{
public string Title { get; set; }
public string Description { get; set; }
}
public class ContentPagesModel
{
public ContentPage GetAboutPage()
{
var page = new ContentPage();
page.Title = "About us";
page.Description = "This page introduces us";
return page;
}
}
Controller:
public ActionResult About()
{
var model = new ContentPagesModel();
var page = model.GetAboutPage();
return View(page);
}
View:
#model Experiments.AspNetMvc3NewFeatures.Razor.Models.ContentPage
#{
View.Title = Model.Title;
}
<h2>About</h2>
<p>
#Model.Description
</p>
for more detail check out here
I case of using string as keys of ViewData - yes, it will be a lot of exceptions if someone will refactor code.
It sounds like your understanding of MVC is very old, probably based on MVC 1. Things have changed tremendously in the last couple of years.
Now we have strongly typed view models, and we have the ability to use expressions in the view, which by default aren't compile-time validated, but they can be for debug purposes (though it slows down the build a great deal).
What's more, we don't pass model data through ViewDate anymore (well, not directly anyways.. it's still passed that way but the framework hides it).
I'm relatively new to view models and I'm running into a few problems with using them. Here's one situation where I'm wondering what the best practice is...
I'm putting all the information a view needs into the view model. Here's an example -- please forgive any errors, this is coded off the top of my head.
public ActionResult Edit(int id)
{
var project = ProjectService.GetProject(id);
if (project == null)
// Something about not found, possibly a redirect to 404.
var model = new ProjectEdit();
model.MapFrom(project); // Extension method using AutoMapper.
return View(model);
}
If the screen only allows editing of one or two fields, when the view model comes back it's missing quite a bit of data (as it should be).
[HttpPost]
public ActionResult Edit(int id, ProjectEdit model)
{
var project = ProjectService.GetProject(id);
if (project == null)
// Something about not found, possibly a redirect to 404.
try
{
if (!ModelState.IsValid)
return View(model) // Won't work, view model is incomplete.
model.MapTo(project); // Extension method using AutoMapper.
ProjectService.UpdateProject(project);
// Add a message for the user to temp data.
return RedirectToAction("details", new { project.Id });
}
catch (Exception exception)
{
// Add a message for the user to temp data.
return View(model) // Won't work, view model is incomplete.
}
}
My temporary solution is to recreate the view model from scratch, repopulate it from the domain model, reapply the form data to it, then proceed as normal. But this makes the view model parameter somewhat pointless.
[HttpPost]
public ActionResult Edit(int id, ProjectEdit model)
{
var project = ProjectService.GetProject(id);
if (project == null)
// Something about not found, possibly a redirect to 404.
// Recreate the view model from scratch.
model = new ProjectEdit();
model.MapFrom(project); // Extension method using AutoMapper.
try
{
TryUpdateModel(model); // Reapply the form data.
if (!ModelState.IsValid)
return View(model) // View model is complete this time.
model.MapTo(project); // Extension method using AutoMapper.
ProjectService.UpdateProject(project);
// Add a message for the user to temp data.
return RedirectToAction("details", new { project.Id });
}
catch (Exception exception)
{
// Add a message for the user to temp data.
return View(model) // View model is complete this time.
}
}
Is there a more elegant way?
EDIT
Both answers are correct so I would award them both if I could. The nod goes to MJ however since after trial and error I find his solution to be the leanest.
I'm still able to use the helpers too, Jimmy. If I add what I need to be displayed to the view bag (or view data), like so...
ViewBag.Project= project;
I can then do the following...
#Html.LabelFor(model => ((Project)ViewData["Project"]).Name)
#Html.DisplayFor(model => ((Project)ViewData["Project"]).Name)
A bit of a hack, and it requires the domain model to be decorated with System.ComponentModel.DisplayNameAttribute in some cases, but I already do that.
I'd love to call...
#Html.LabelFor(model => ViewBag.Project.Name)
But dynamic causes a problem in expressions.
After some trial-and-error (aka code it, then hate it) learning, my currently preferred approach is:
I use view-models only to bind input fields. So in your case, if your view is only editing two fields, then your view-model will only have two properties. For the data required to populate the view (drop-down lists, labels, etc), I use the dynamic ViewBag.
I believe that displaying the view (i.e. populating anything the view needs to display), and capturing the posted form values (binding, validation, etc) are two separate concerns. And I find that mixing the data required to populate the view with that which is posted back from the view gets messy, and creates exactly your situation more often than not. I dislike partially populated objects being passed around.
I’m not sure how this plays out with Automapper (for mapping the domain object to the dynamic ViewBag) though, as I haven’t used it. I believe it has a DynamicMap method that may work? You shouldn’t have any issues auto-mapping the posted strongly-typed ViewModel onto the Domain object.
If I understand correctly, your viewmodel probably looks very similar to your domain entity. You mentioned that the viewmodel can come back mostly empty because only certain fields were editable.
Assuming you have a view where only a few fields are available for edit (or display), these are the only fields you should make available in your viewmodel. I usually create one viewmodel per view, and let either the controller or a service handle the user's input and map it back up with the domain entity after performing some validation.
Here's a thread concerning best-practices for viewmodels that you might find useful.
Edit: You can also accept a different view model in your Edit/POST action than your Edit/GET action serves up. I believe this should work as long as the model binder can figure it out.