I am aware that views should not have code in them but in a project I am working on I have a lot of logic in the views.
My home page has
<% Html.RenderPartial("SearchResults"); %>
Now in the partial view I have an aweful lot of logic like this;
<div id="RestaurantsList">
<%if (Model.restaurantsList.Count() > 0)
{
foreach (var item in Model.restaurantsList)
{ %>
<% Html.RenderPartial("SearchResult", item); %>
<%
} %>
<%
}
else
{
Html.RenderPartial("NoResults");
} %>
Now I could make the home controller return a different view based on the list being empty but I don't really want that as the Index view has a few things that I want displayed no matter if there are results or not.
The only other thing I can think of here is to encapsualte this in a helper method like Html.SearchResults. But then I would need the helper to call the renderPartial for each search result also. That doesn't seem like clean seperation of concerns.
I would still have to have the first if statement in the partial view though.
How would you best handle this?
My personal opinion is that this is okay. The logic that you've used is totally related to how the model needs to be displayed.
You just need to be aware and make sure that you're never mixing in business logic, data access logic or anything else that isn't strictly tied in to the display of the model.
I agree with Praveen Angyan's answer. The only thing I could say to extend his answer is to put some of the logic in the ViewModel.
For example in the ViewModel you could hide
Model.restaurantsList.Count() > 0
behind a method or property.
E.G.:
<%if (Model.HasResturant){...}%>
this answer has nothing to do with your question.
However, I just want to let you know that calling Html.RenderPartial() inside a loop is not efficient.
ASP.NET MVC - For loop inside RenderPartial or Outside RenderPartial
Changing it to something like below would be better.
<%if (Model.restaurantsList.Count() > 0)
{
// render the Restaurant item right away
foreach (var item in Model.restaurantsList) { %>
<div>
<%= Html.Encode(item.RestaurantName); %><br />
<%= Html.Encode(item.Address); %>
</div>
<% }
}
else
{
Html.RenderPartial("NoResults");
} %>
Praveen Angyan is correct - that's view logic and it's fine that it is where it is.
But that does not change need for tidier views.
Just wanted to share small improvement.
If we attach tiny HtmlHelper method, we can shorten view to something like this:
<div id="RestaurantsList">
<% if (Model.HasRestaurants)
Html.RenderPartialForEach("SearchResult", Model.restaurantsList);
else
Html.RenderPartial("NoResults"); %>
</div>
For some - it might not look readable and nice, but it fits for me good enough.
Related
I've seen plenty of examples (NerdDinner, Sanderson's Sports Store, etc.) where a view is bound to a collection of objects. The syntax in the view is usually something like this...
<%# Page... Inherits="System.Web.Mvc.ViewPage<IEnumerable<MyViewModel>>" %>
...
<% foreach (var myViewModel in Model) { %>
I've also seen plenty of examples of inserts or updates where the controller automatically binds the model parameter to the form elements in the view.
I'm looking for a mix of the two techniques where my view has form elements pertaining to a collection of myViewModels where each myViewModel has 3-4 properties. The intent is to allow the user to enter a set of these in one take.
Assuming this is possible, can anyone help me with the syntax? I can't figure out how to label the form elements to make the binding work.
This is possible through the built-in model binder, but you have to do a little bit of convention-based naming of your form objects. First, your action needs to take a collection:
[HttpPost]
public ActionResult CreateFoos(List<Foo> foos)
{
// I have a list of the foo objects that were posted
}
And then, in the view, let's say you wanted to make a form for each object:
<% for (int i = 0; i < Model.Count; i++) { %>
<%: Html.TextBoxFor(x => x[i].Property1) %>
<%: Html.TextBoxFor(x => x[i].Property2) %>
<%: Html.TextBoxFor(x => x[i].Property3) %>
<% } %>
Pay attention to how the controls are rendered in the HTML, because in your "create" view, you might want to have a javascript button that allows the user to add another record, and you'll have to increase the index for each additional control. It's not too hard, but I just wanted to warn you to pay attention to the source it actually generates.
The definitive answer is here: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
You need to name each field as if it were part of an array: "PropertyName[i]"
Ok I have a menu system with a menu (Dynamicly Generated from a datavbase field) I want to include this menu system on several views - All of which use differant controllers and models.
<ul>
<li>All</li>
<%
foreach (var curCat in Model.CategoryList)
{
%>
<li><%= Html.Encode(curCat.Category1)%></li>
<%
}
%>
</ul>
Whast the best way to achieve this? Do i need to just pass the Categories model with every other model so that I can do the RenderPartial("Name",Model) synatx?
There are two different ways to accomplish this. You can include the Categories in every model or you can store the Categories in the ViewDataDictionary and retrieve them from there. Normally, I would want to extend the model with data so that I can use it in a strongly-typed way, but in this case -- since the data is ubiquitous -- I would probably go with ViewData and use a base controller to populate it (likely in OnActionExecuted) so that it is always available. In my partial view I would cast the ViewData item to a strongly-typed object and use it from there.
The reason I would do this is to keep my models clean for the actual view, which doesn't need to know about the data for the menu. To me this seems like a reasonable exception to the usual route of creating a view-specific model.
<% var categories = ViewData["categories"] as IEnumerable<Category>; %>
<ul>
<li>All</li>
<%
foreach (var curCat in categories)
{
%>
<li><%= Html.Encode(curCat.Category1)%></li>
<%
}
%>
</ul>
Third way--check out the MVC Futures on codeplex, specifically the Html.RenderAction method. Then you can create a controller that just ouputs the menu and let it handle it's business. No need to pollute ViewData.
Here is a way that we are currently spitting out some HTML to our view.
foreach (var reportGroup in managedReportGroup.CustomerTags)
{ %>
<%= reportGroup.Name %>
<%
}
Is there something that I can do like this? (This won't work for me)
Action<ManagedReportGroups> handlesReportGroup = delegate(ManagedReportGroups managedReportGroup)
{
foreach (var reportGroup in managedReportGroup.CustomerTags)
{
Html.Encode(reportGroup.Name);
}
};
The reason would be to cut down on the aligator brackets and clean up my code.
Thanks,
Mike
If you move it to partial views, you will still have to do the loop inside the partial.
You could create custom Html extension methods if you were really dead-set against your first example.
Or if your main goal really is to reduce "alligator brackets" you could cut it down to two if you did it this way:
<%
foreach (var reportGroup in managedReportGroup.CustomerTags)
{
Response.Write(reportGroup.Name)
}
%>
You might also consider the Spark View Engine
Why not use PartialViews?
foreach (var reportGroup in managedReportGroup.CustomerTags)
{ %>
<% Html.RenderPartial("NamePartialView", reportGroup.Name) %>
}
Then your partial view can render the name.
This is more effective when you pass in the reportGroup to the PartialView and let the partialView then either write all the HTML or call further PartialViews each rendering at a more atomic level.
I have a database menu structure which I would like to add to the site.master file.
I’ve looked at other questions on StackOverflow but cannot get this to work on my website.
How do I add a User Control to the Site.Master file?
Menu.ascx
<%foreach (MainMenuSort mainMenuSort in (List<MainMenuSort>)ViewData["MainMenuSortListDisplay"])
{ %>
<li><%= Html.Encode(mainMenuSort.MainMenuId.MainMenuName)%></li>
<%foreach (SubMenuSort subMenuSort in (List<SubMenuSort>)ViewData["SubMenuSortListDisplay"])
{%>
<%if (mainMenuSort.MainMenuId.Id == subMenuSort.SubMenuId.MainMenu.Id)
{ %>
<li><%= Html.Encode(subMenuSort.SubMenuId.SubMenuName)%></li>
<%} %>
<%} %>
<%}%>
You need to use the Html.RenderPartial method in your master page.
You will need to set the MainMenuSortListDisplay and SubMenuSortListDisplay view data keys in whatever action is calling the view that uses your master page.
In your master use this
<% Html.RenderPartial("~/Views/Shared/Menu.ascx");
The path needs to be the app relative path to the control's folder. Typically these go under Shared. You can make the structure how you want below the Shared folder.
To make this technique stronger, use a strongly typed partial. In the question you would perhaps make a new class (MenuModel) with two generic collections as properties and place it in the models folder of the application. Then in the model's constructor call a method that populates the lists.
public class MenuModel
{
public IEnumerable<MainMenuSort> OuterList {get; set;}
public IEnumerable<SubMEnuSort> InnerList {get; set;}
public MenuModel()
{
VoidThatFillsTheInnerAndOuterList();
}
This will mean that you can do this in your controller
public ActionResult ShowAForm()
{
ViewData["MenuPartialData"] = new MenuModel();
return View();
}
Having set this key, your master page can use the overload of RenderPartial, like this
<% Html.RenderPartial(
"~/View/Shared/Menu.ascx",
(MenuModel)ViewData["MenuPartialData"]); %>
This assumes that your partial is strongly typed to the MenuModel class. Then in the partial you can use the model which rewrites your code slightly
<% foreach (MainMenuSort mainMenuSort in Model.OuterList) { %>
<li><%= Html.Encode(mainMenuSort.MainMenuId.MainMenuName)%></li>
<% foreach (SubMenuSort subMenuSort in Model.InnerList) {%>
<%if (mainMenuSort.MainMenuId.Id == subMenuSort.SubMenuId.MainMenu.Id)
{ %>
<li><%= Html.Encode(subMenuSort.SubMenuId.SubMenuName)%></li>
<%} %>
<%} %>
<%}%>
Hope that helps
Try something like
<% Html.RenderPartial("Menu") %>
EDIT: Corrected a typo
You could also do it as a HTMLHelper and in the MasterPage just call <%= Html.Menu() %>. Then in your HTMLHelper you have the code to get the database records and loop through them. Here is a link I found to get you started. Note my comments as there is a bug in the code example provided. I'm still having issues handling subitems of menus, I guess I need a recursive function or something??
With the help of this link. I was able to display a menu in the site.master page.
I have a search page that if there is results in the list it passes this list to a view.
However if there are no results I want to send the searched text to a no results found view.
How would I go about this?
You will need to have the searched text available as part of the model that is returned to the view. Then you have two options -
Using the RenderPartial will pass the returned view to the partial view so you can access the value you want from there.
Html.RenderPartial("PartialView");
Alternatively, you can pass the string as the model for the partial view using
Html.RenderPartial("PartialView", Model.SearchedText);
Which might make sense if you want to use the no results partial view with different models.
<%Html.RenderPartial("SimpleTrustGridViewer", ViewData["departmentGrid"]); %>
this passes an object ViewData["departmentGrid"] (this comes from viewdata of the non-partial view) to the partial view SimpleTrustGridViewer.
simplified:
<%Html.RenderPartial("myUserControl", myString); %>
And your partial view inherits like this:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<string>" %>
Then, in your partial view 'Model' will be the passed string.
The ViewDataDictionary passed from the controller to the view will be the same passed from the View to the Partial View. So if the string you want to pass is in the ViewDataDictionary you don't have to pass it.
<%=Html.RenderPartial("NorResultFound")) %>
But you can use the same view whether there were results or not:
<%if (Model.ResultCount!=0){ %>
<%foreach(var result in Model){ %>
<%= // display results %>
<%}}%>
<%else {%>
<p>There is no results for <%=ViewData["keyword"]%> </p>
<%} %>
I tried this and couldn't get it to work. Say I have
<div id="SearchBar">
<% using (Html.BeginForm("IndexNoJavaScript", "Home"))
{%>
<%= Html.TextBox("SearchTextBox", ViewData["SearchText"]) %>
<input type="submit" value="Search" /> <% } %>
</div>
<% Html.RenderPartial("SearchResults"); %>
And when I try to display the search text in this view like so:
<%= Html.TextBox("SearchedText", ViewData["SearchText"] ) %>
My text box is blank.
You can use jquery and load() action on div tag , insted of using partial; the result is similar.
The load() ajax method call on controller with the text that you want.
like:
$('#divId').load('url/'+ serch content );
Partial view unless you pass to it something else explicitly, has the same Model as parent view.
Two ways (you are talking about views, not partial views right?)
1) in your controller just call a different view in case of no results passing a string as model
2) create a model containing a search status (found x items, no match found, etc...) and a list of results to the same view, allowing the view to render the different results with a switch statement.