Simple question on best practice. Say I have:
public class Product
{
public string Name { get; set; }
public string Price { get; set; }
public int CategoryID { get; set; }
public bool IsAvailable { get; set; }
}
and i have a view using IEnumerable< Product> as the model, and i iterate through the Products on the page and want to show the total of the prices at the end of the list, should I use:
<%= Model.Sum(x=> x.Price) %>
or should I use some other method? This may extend to include more involved things like:
<%= Model.Where(x=> x.CategoryID == 5 && x.IsAvailable).Sum(x=> x.Price) %>
and even
<% foreach (Product p in Model.Where(x=> x.IsAvailable) {%>
-- insert html --
<% } %>
<% foreach (Product p in Model.Where(x=> !x.IsAvailable) {%>
-- insert html --
<% } %>
I guess this comes down to should I have that sort of code within my view, or should i be passing it to my view in ViewData? Or perhaps some other way?
If the logic you are using on the page is related to the display of data on the page, then I don't have a problem using logic that does calculations. Calculating the values in your controller and supplying it as part of the model would couple the display to the action performed by the controller, i.e., if you simply want to change how the data is displayed -- for example, group by category and show sub-totals -- and expected all the data to be in the model, then you'd have to touch both the controller and the view to make the change. If you put the display-related calculations in the view, then only the view needs to change.
The call on whether the logic is business-related or view-related is heavily context dependent. For example, you might have a business rule that says you only display products that are available. Enforcing this rule certainly shouldn't be a function of the view so, in that case, you should move it into the controller (or even the model). But if it's a simple calculation of the contents of a shopping cart or filtering what you display based on model properties, I'd be ok with it in the view.
I would push this logic back into the controller and the "view model". I consider this to be the "model of the view". A simple example would be
return View(new ProductSummaryViewModel
{
Products = products,
TotalPrice = products.Sum(p => p.Price)
});
You can obviously extend this for whatever the view has to display. You can even have collections of sub-viewmodels which can make things a lot easier.
Hope this helps
Related
Using an MVC5 application and I am trying to load a model within a partial view that is also within the _layout page. This doesn't seem to work as it conflicts with other models loaded up in different views.
I found an alternative with ViewBags, but it's messy and I don't think it's the best approach. Is there a way to load up a partial view within a model that's inside the _layout page so the model loaded from the partial view doesn't conflict with another model?
Updated:
What I'm trying to do is this:
In the _layout page. I'm trying to use an image carousel which is supplied by bootstrap to be interactive with the user (so the user can change the images, titles, and sub-titles).
I created a model to store the data, such as the title, etc. :
public class BannerEditor
{
public int ID { get; set; }
[Display(Name="Title")]
[StringLength(150)]
public string title { get; set; }
[Display(Name = "Sub-Title")]
[StringLength(300)]
public string subTitle { get; set; }
[Display(Name = "Image Path")]
public string imgPath { get; set; }
[Display(Name= "Starting Banner")]
public int startBanner { get; set; }
}
Then for my partial View I wrote some code and tried to load the model.:
#model IEnumerable<webby.Models.BannerEditor>
#foreach (var item in Model)
{
if (item.startBanner == 1)
{
<div class="item active">
<img src=#item.imgPath alt="...">
<div class="carousel-caption">
<h3>#item.title</h3>
<p>#item.subTitle</p>
</div>
</div>
}
else
{
<div class="item">
<img src=#item.imgPath alt="...">
<div class="carousel-caption">
<h3>#item.title</h3>
<p>#item.subTitle</p>
</div>
</div>
}
}
then in my _Layout page, I just added the Partial.
<div class="carousel-inner">
#Html.Partial("BannerLoad")
Here is the controller:
public ActionResult BannerLoad()
{
return PartialView(db.BannerEditors.ToList())
}
It gives me an error stating that it cannot load the Articles model into my partial view. The model that it needs to load is the BannerEditor. Since my _layout is loaded into every page, and the partial view is loading a model. The partial view's model conflicts with every other loaded model. The models name is "Articles".
Is there a way around this?
Use child actions for this kind of thing. They're like partial views, but they get their own context, allowing you to fetch and work with whatever model you need to without affecting the rest of the page:
Controller
[ChildActionOnly]
public ActionResult BannerLoad()
{
var banners = db.BannerEditors.ToList();
return PartialView(banners);
}
BannerLoad.cshtml
#model IEnumerable<Namespace.To.BannerEditor>
<!-- HTML to display your banners -->
_Layout.cshtml
#Html.Action("BannerLoad");
All that looks pretty similar to what you already have, but there's some key differences:
Partials don't work with actions, that's actually what makes a child action a child action and not a partial. It seems in your current code that you're expecting Html.Partial to render your BannerLoad action, but it will never do that. It will only dump the content from BannerLoad.cshtml onto the page, which because it never got fed an appropriate model, will cause an error.
Partials don't have their own context (because they don't work within a separate action), so they must receive their model from the parent view. If you don't pass a model in (which you aren't), they are automatically passed the "current" model. This will be whatever model the view the main action is using and will obviously change action to action. You cannot set a model on _Layout.cshtml, for example, and have that passed in, as that's not the origin point.
Even if you could set the model on something like _Layout.cshtml, that would only work for that one partial. If you had any other partials, you're still out of luck.
I have the following ViewData that I pass into a view.
public class MerchantSignUpViewData : BaseViewData
{
public Merchant Merchant { get; set; }
public Address Address { get; set; }
public Deal Deal { get; set; }
public List<MerchantContact> Contacts { get; set; }
public int TabIndex { get; set; }
public List<DealPricing> DealPricing { get; set; }
}
I also created 3 partial views. Merchant Info, Address, Merchant Properties
In my View I have a Deal Model that shares the same field names as Merchant which is "Name"
I can't put these in the same form cause the names will be the same.
What I ended up doing was putting all 10 partial views into one huge form (I started crying at this point) and bound like this.
<%: Html.TextBoxFor(model => model.Deal.Name)%>
<%: Html.TextBoxFor(model => model.Deal.Name)%>
This gives me the correct names of the form elements.
What I want to do is the following.
<% Html.RenderPartial("MerchantForm", Model.Merchant) %>
<% Html.RenderPartial("DealForm", Model.Deal) %>
But how do I add a prefix to all the TextBoxFor pieces or preferable the render partial tags.
Hope I provided enough information or maybe I'm just doing this the wrong way. Either will help me in the long run so thanks in advance.
Maybe I'm not quite getting the problem but I think this is exactly what Html.EditorFor(x=>x...) is for.
Create a folder called "EditorTemplates" in the same directory where your views are. Put your partials in here and give them the same name as your model type (eg rename "MerchantForm.ascx" to "Merchant.ascx").
In your main view instead of
Html.RenderPartial("MerchantForm", Model.Merchant)
use
Html.EditorFor(x=>x.Merchant)
The templating system will deal with the prefixes for you so that on post the model binder will tie everything up properly.
If you have templates set up for all the complex objects in the model you can even go a step further and on your main view just call
Html.EditorForModel()
Which will reflect over the properties in your model and call their relevant editors.
Is there a way to make a list of links for each action in controller instead of having to add
<li><%= Html.ActionLink("Link Name", "Index", "Home")%></li>
for each item?
yes there is.
You can either return a SelectList of key value pairs that you can render as anchor tags.
Or you can create a model in the, and this is not the best place for it, controller and return it to the view which you can then itterate through.
public class myAnchorList
{
public string text {get;set;}
public string controller {get;set;}
public string action {get;set;}
}
then in your code create a List<myAnchorList>.
List<myAnchorList> Anchors = new List<myAnchorList>();
Fill the list with data and return.
return View(Anchors).
if you are already passing over a model then you need to add this list into the model you are returning.
Make sense? if not post a comment and i'll try to explain further.
Edit
Let me complete the picture now that i have a little more time.
On the client side you'd have this untested code;
<ul>
<% foreach(myAnchorList item in Model.Anchors){ %>
<li><%= Html.ActionLink(item.text, item.action, item.controller)%></li>
<% } %>
</ul>
In addition to griegs answer, it might be helpful to construct the list of controller actions via reflection. In which case you might want to look at:
new ReflectedControllerDescriptor(typeof(TController)).GetCanonicalActions()
Credit for this answer goes to: Accessing the list of Controllers/Actions in an ASP.NET MVC application
I am currently learning ASP.NET MVC so please excuse my question if it has been asked or seems rather simple, but if I could get some help I would greatly appreciate it.
I am trying to return two different repositories to the View. I am going through ASP.NET MVC's tutorials and I thought I would try taking it a step further. I can display Movies from the Movie table in the database just fine but I also want to display data from the Actors table on the same as well and I am not sure how to go about doing this. For displaying the Movies I was following the Repository pattern.
I hope this makes sense.
Thanks,
Create a new class that has both a list of Movies and Actors in it:
public class MoviesAndActorsModel
{
public IList<Movie> Movies { get; set; }
public IList<Actor> Actors { get; set; }
}
Then, in your controller action, instantiate an object of type MoviesAndActorsModel that is populated from your repository:
public ActionResult List()
{
MoviesAndActorsModel model = new MoviesAndActorsModel();
model.Movies = _repository.GetMovies();
model.Actors = _repository.GetActors();
return View(model);
}
Now make sure your view inherits from ViewPage<MoviesAndActorsModel> and you should be able to access both the movies and actors like so:
<% foreach (Movie movie in Model.Movies) { %>
<%= movie.Title %>
<% } %>
<% foreach (Actor actor in Model.Actors) { %>
<%= actor.Name %>
<% } %>
ViewData["Movies"] = //Function to get Movies;
ViewData["Actors"] = //Function to get Actors;
return View();
That's what I'm doing in my app to pass multiple sets of data to the View.
EDIT:
Or, if you have relations set up in your database you could do something like:
var movies = /*Function to get Movies*/ as Movies;
movies.Actors.Load();
That will accomplish the same thing assuming you have foriegn keys set up in your tables.
And then in your view you would do:
<%
var movies = ViewData["Movies"] as Movies;
var actors = ViewData["Actors"] as Actors;
%>
There's a tutorial on http://asp.net/mvc, which demonstrates a variation of dhulk's solution for a slightly different problem: how to return some common data that is used by each view (e.g. by the master page) from each action method without having a lot of duplicated code.
The tutorial introduces a so-called application controller (simply a base class for all controllers) which handles returning the common/shared data to the view.
Let's say I have a table of staff members, and a separate table of staff roles in a many-to-many relationship (ie, with a StaffMembersInRoles table in between). Using the ADO.net entity framework we have an object model like this:
alt text http://martindoms.com/img/datamodel.png
I have a StaffController controller with a create method and a Create view which is pretty much the standard automatically generated type. I want the create page to list the roles in the StaffRoles table with a checkbox next to each, and when the submit button is pressed these are added to the new StaffMember StaffRole IEnumable (and so the appropriate entries are made in the StaffMembersInRoles table). How would I go about this with a strongly-typed view?
This is how i'd do it:
Firstly, you need an array of all possible roles.
In the controller, i'd do something like this (i'm making some assumptions about your DAO):
ViewData["AllRoles"] = (StaffRole[])StaffRole.FindAll();
Then in your view, loop through the roles:
<% foreach (StaffRole Role in (StaffRole[])ViewData["AllRoles"]) { %>
<p>
<label>
<%= Html.CheckBox("Role_"+Role.RoleId.ToString()) %>
<%= Html.Encode(Role.RoleName) %>
</label>
</p>
<% } %>
Then in your POST controller, do something like this:
foreach (StaffRole Role in (StaffRole[])StaffRole.FindAll())
{
if (Request.Params["Role_"+Role.RoleId.ToString()]=="true")
MyStaff.Roles.Add(Role);
}
Hi the Problem with this approach is you don't really have a strongly type entity passed to the View. In this problem you need the StaffMember information and a list of all StaffRole entities. PS: I really dun like the approach of casting the list in the view : StaffRole[])ViewData["AllRoles"]
Basicly i will prefer to work with DTO.
DTO:
public StaffMemberDto
{
public int StaffMemberId { get; set; }
public IList<StaffRoleDto> AllStaffRoles { get; set;}
public IList<StaffRoleDto> MembersRolesAttached { get; set;}
}
public StaffRoleDto
{
public int RoleId {get; set;}
public string RoleName { get; set; }
}
Controller:
return View(StaffMemberDto);
So in the view you get all roles strongly typed:
foreach (var role in ViewDate.Model.AllStaffRoles)
{
...
}
And in the post you can send the StaffMemberDto with the good RoleDto already assigned in the view or you can do the Request trick to get the id of the checkboxes ticked.
Well in a view like this i will probably use jquery to request an addRole each time someone tick the box to add a role. It will add some ajax to your form and you will not have some postback.