Quoting from the NerdDinner ASP.NET MVC sample application
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%
if (Request.IsAuthenticated) {
%>
Welcome <b><%= Html.Encode(Page.User.Identity.Name) %></b>!
[ <%= Html.ActionLink("Log Off", "LogOff", "Account") %> ]
<%
}
else {
%>
[ <%= Html.ActionLink("Log On", "LogOn", "Account") %> ]
<%
}
%>
This is from the partial view usercontrol named LoginStatus.ascx. As you can see there is condition present which alters the 'entire' output of the view. Is this the correct way. Would it have been better if there was controller evaluating this condition and then rendering the appropriate partial view?
And regardless of your answer to the previous question, How can I take the latter approach in ASP.NET MVC i.e. can a parent view call a controller (instead of doing a RenderPartial of the UserControl) and let it decide which partial view to render?
How about this approach:
Solution 1:
Create an extension method on HtmlHelper which will render whether "WelcomeMessage.Anonymous.aspx" or "WelcomeMessage.Authenticated.aspx" view based on the request.
<%= Html.LoginStatus() =>
And put these views under /Views/Shared
/Views/Shared/LoginStatus.Anonymous.ascx
/Views/Shared/LoginStatus.Authenticated.ascx
Solution 2:
Just replace if / else statements with ASP.NET's LoginView control in your LoginStatus.ascx
<asp:LoginView Runat="Server">
<LoggiedInTemplate>
Welcome, <%= Html.Encode(Model.UserName) %>!
<button>Sign Out</button>
</LoggedInTemplate>
<AnonymousTemplate>
<button>Sign In</button> | <button>Join Now!</button>
</AnonymousTemplate>
</asp:LoginView>
See also:
ASP.NET MVC Best Practices, Tips and Tricks
I think that if the view is going to change accordingly to some condition it is responsibility of the view to enforce this. But if the condition is changing not appearance (i.e. "the negative numbers shall be red") but behaviour (i.e. "if the user is logged in s/he must see the LOGOUT button instead of the LOGIN button") then it's controller's to decide. You might introduce a "renderer" level between controller and page.
I think what you are doing is quite allright. Such little things as changes in displaying could and should be undertaken by views.
For example I have a main menu rendered by a separate ascx. There are lots of such little checks inside in order to decide what text to show and which styles to apply to list elements.
If there are big decisions like thinking of what view to render based on some user action then the controller questions the business logic and decides what view to return of where to redirect. But if it is a quite stable UI element where only text and color options are slightly changed then put the necessary logic into it.
You could also pass a separate model to your ascx with several flags defining what and how should be displayed. Then the actual logic to set those flags will be somewhere else on the business logic level and all your view will do is just look at those flags and render accordingly.
Don't worry, you're doing it right.
Instead of deciding if the user is authenticated in the view, you can do it in the controller, like so:
public ActionResult ShowAPage()
{
if(!HttpContext.User.Identity.IsAuthenticated)
{
return RedirectToRoute("ShowLoginPage")
}
return View();
}
Then you can redirect to a login page rather than having this logic in a view, which is not really a separation of concerns.
A really cool way of doing this is to use a different master page for authenticated users than for not authenticated users. I take it you are wanting to always show either a log on, or log off link, this way you could use an ActionFilter to change the master page depending on the authentication of the user. Then your logged on users can get things like navigation bars that you would maybe want to hide from outsiders, without you having to decide this in display logic.
Here's one way of doing it without using an action filter, but you can do all kinds of ways, one good one is to create a custom controller that inherits from controller and overrides the View methods to select the appropriate master page.
public ActionResult ShowAPage()
{
if(!HttpContext.User.Identity.IsAuthenticated)
{
return View("ShowAPageView", "LoggedInMasterPageName");
}
return View("ShowAPageView", "LoggedOutMasterPageName");
}
Hope this helps.
Related
Virtually every ASP app I've written (hundreds) follows the exact same pattern. A "single page app" with a header and footer, and a dynamically updated content area that changes depending upon what going on/in the url. Something like the following (very simplified, but demonstrates the principle):
<% select case lcase(request("action") %>
<% case "home" %>
<div class='class_home'>
Home Screen HTML/Script/ASP
</div>
<% case "enroll" %>
<div class='class_enroll'>
Enroll Screen HTML/Script/ASP
</div>
<% case "checkout" %>
<div class='class_checkout'>
<!-- if it's gonna be lengthy, will often do this instead: -->
<!--
#include file=checkout.inc.asp
-->
</div>
<% end select %>
This pattern may even be nested several layers deep with additional request("subaction") subarea/subforms involved. Every form submits to itself ([form action="" method=POST]), and asp script at the top catches the form and processes it, then continues.
So, the question is, is this pattern still done inside MVC? Or do I have to duplicate the common areas over and over again in each separate page that I create?
Is this even a good idea to WANT to do this? Or is there a better way to accomplish the same goal of a "single page app"?
Thanks!
Even in classic ASP you could achieve this without all the craziness that is going on in that select statement.
In MVC, you use partials and layout pages to avoid repeating code. Here is a nice rundown http://weblogs.asp.net/scottgu/archive/2010/12/30/asp-net-mvc-3-layouts-and-sections-with-razor.aspx
This is still the same in MVC. If you are using Razor, look for the _Layout.cshtml file in /Views/Shared. If you are using the old ASP.Net engine, it will in the same location but called MasterPage.
Aditionally, there is a file called _ViewStart.cshtml. This is invoked automatically by the framework, and this is what points to the _Layout.cshtml file.
I'll add a little more to the suggestions of using _ViewStart.cshtml and _Layout.cshtml. Make sure to use strongly typed view for all your Views and have each View Model extend from a base View Model class that has all the "common" data such as the menu state, logged in status, etc.
You would just do this using ineritance:
public class MyBaseViewModel
{
public string UserName { get; set; }
//other properties
}
public class MySampleViewModel : MyBaseViewModel
{
//additional properties for this View only
}
I need to write a menu in Site.Master where certain menu items have to be visible or not depending on current user role. How can I check that from the page?
Usually would just write the logic in a controller, but the Site.Master doesn't have one (in my project at least!). I'd appreciate any pointers.
Use HttpContext.Current.User.
That would always be visible from your views / partial views / master pages.
For example, to display different html for a given role, say, MyRole, you would simply write:
<% if(HttpContext.Current.User.IsInRole("MyRole")) { %>
// tags for MyRole
<% } else { %>
// tags for other users
<%} %>
This is fine as it is "display logic" which belongs in the View (or master page), as opposed to "application logic" which belongs in the controller.
Note that views are just templates. You can write code into them without messing the neat MVC pattern, so long as it is display logic only.
A view or a master page don't "have a controller". They are just templates that the controller can find and use.
I have a partial view/user control called LogOnUserControl which I display in a side bar on my site (defined in Site.Master). I also have a separate LogOn view, which also renders the LogOnUserControl.
I don't want two instances of the LogOnUserControl in the LogOn view, because it's just plain confusing, so my current thinking is to include a condition such as
// Semi-pseudocode
if (!Request.IsAuthenticated) && View.Name != "LogOn")
in the LogOnUserControl.
This feels wrong, as the partial view now knows about the LogOn view. Also, I can't find out how to get the View's name, which reinforces the feeling that I'm doing something wrong! :-)
Edit: There is the further complication that the same partial view is used for both the LogOn view and the sidebar in Site.Master.
Have you considered using a different master page for your login View without the login partial in the sidebar? If you are concerned about duplication of html markup you could use nested master pages to avoid that issue.
On the master page wrap the content of the sidebar area with content area tags and give it an id like SideBarContentArea or something. What this does is create a new content area that you can choose to override on pages based of the master and specifies default content that will show up when you do not implement in on the child pages. Now on the login page all you have to do is override the SideBarContentArea and not include the login control this time.
Bada Bing!
You could store a flag in ViewData to indicate this. Whether you want to strong-type it or just access it directly is up to you. So on your master page you could have this:
<% if (ViewData["HideLogOnUserControl"] == "Y") { %>
Insert HTML here
<% } else { %>
Insert HTML here
<% } %>
It seems that my understanding of the use of partial views is not quite right.
I am trying to add a partial view which builds from a database and use the partial view within a master page.
Using the NerdDinner project (wich is great for mvc) I have added the following:
Views\Shared\dinners.ascx" :
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<NerdDinner.Models.Dinner>>" %>
<ul>
<% foreach (var dinner in Model) { %>
<li>
<%= Html.Encode(dinner.Title) %>
</li>
<% } %>
</ul>
Within "Views\Shared\Site.Master" :
<%# Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
...
<% Html.RenderPartial("dinners"); %>
The above will only currently function on the pages where the dinners object is currently availabe as a list (eg site/Dinners). Is there a way to do this in an elegant and mvc way or is this something that requires a clever compromise?
Why do you have it in your master? I would add another place holder to your master and then add it where it makes sense. I feel that master pages should be as agnostic to specifics. If you want it be on all dinner pages, just make nested master of the original one.
This is, IMO, one of the biggest limitations of ASP.NET MVC - managing shared data across multiple views (next to rendering partial ascx views to strings!). If you google or search on stackoverflow for something like 'managing shared view data asp.net mvc' you'll get a ton of results with various options, none of which are really perfect. The MVC team at Microsoft have acknowledged this as a problem and will hopefully include a standard solution in a future release.
Depending on how you manage data access, the easiest way may be to create a base Controller class and retrieve the data you need for the partial either inside the constructor or inside OnActionExecuting().
The option that I have chosen is to use the Html.RenderAction() helper method inside the MvcContrib project. It basically enables you to call an action method from your view and render the response. This isn't great because it requires your view to have yet more knowledge about controllers, but it gives an easy short-term solution that doesn't require hooking up any extra code on your part.
This tutorial on stephan Walther's site deal with this issue. If you use an abstract base class where the dinners object is populated and inherit from that, it will always be available, but you'll have to be aware that it's there always even when you don't need it ;).
One method I use is to create a helper method and use it in your Master Page.
public static void RenderDinners(this HtmlHelper helper)
{
helper.RenderAction<DinnersController>(c => c.Dinners());
}
<%# Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
...
<% Html.RenderDinners(); %>
As you can see the helper calls the Dinners Action method on the DinnersController.
public ActionResult Dinners()
{
...get dinners and put in the View
return Dinners(view);
}
partialview seems to me to be inherently flawed. It creates module coupling and breaks cohesion intentionally by definition.
I agree with Daniel, even if your control does not shows on every page, it shows on some of them, you should create your master as template only
How would you go about supporting external composable parts in an ASP.NET MVC view?
What do I mean by this? Think either "login box on every page" or "iGoogle". It's stuff that needs to be in certain places that is external to each controller/view.
One approach at this would be adding components in the view like so:
<% foreach (var component in GetComponents()) {%>
<%= Html.RenderPartial(component.ViewName, component.ViewData)%>
<%} %>
In the example above I'm looking for a good way to have the viewname and viewdata delivered by each component's controller and not the controller of the view they are displayed on. Any totally different solution you can suggest would also be of interest. Filters, WebForms, etc.
Update: I'll try to explain what I'm trying to wrap my head around with an example. I'll pick the login functionality.
In a typical webforms application this could be a user control that retrieves the appropriate data in the load event of the page's life cycle and updates some UI controls. Upon click the page would post back and we can act upon the posted information in the click event in the same user control.
As of my current understanding of the ASP.NET MVC style the controller that first accepts the request would retrieve the appropriate data and pass it to the view which in turn would pass it along to the login partial view. The login view would define a form whose post action is directed at the login action of the login controller. The posted information is used by the login action and we can choose to pass the request along to the original controller using some nifty scheme.
I assume there is a smarter approach than the above that doesn't require I put controller logic in a view/a master page.
There is two ways you can do this:
One: Make a BaseController, that allways gets the data needed for these components, set the ViewData["loginbox"] = loginBoxData, and then you can pass it along like so.
<% foreach (var component in GetComponents()) {%>
<%= Html.RenderPartial(component.ViewName, ViewData[component.Name])%>
<%} %>
The problem about this method is that you need to have logic to say WHEN all these data needed for the components is going to be fetched, and when not to.
TWO:
There is a MVC Futures DLL that you can download from here.
It you reference that, and remember to add its namespace to your web.config, then you should be able to say: Html.RenderAction("blah") - this method makes the full trip by contacting a controller, initiate the view, and returns the HTML.
You could use the master page and use content placeholders for view content pages and view user controls.
The default project template in ASP.NET MVC Framework Beta has a content placeholder for the View Content Page. There is also a View User Control for the login/logout link at the top right corner of the pages.
View Content Page
The view content pages are supposed to be used with the data that comes from the View that you are looking at. I.e. if you're browsing to the Index action in HomeController, the server will render the Home/Index.aspx view content page.
In the master page you need the following line:
<asp:ContentPlaceHolder ID="Holder1" runat="server" />
And in the view content page itself you compose for that place holder with the following:
<asp:Content ID="Content1" ContentPlaceHolderID="Holder1" runat="server">
<p>This text goes to "Holder1" content place holder and
I can access the ViewData.</p>
<p><%= ViewData["Message"] %>
</asp:Content>
Each asp:ContentPlaceHolder corresponds to the view's asp:Content through the id. So if you want several placeholders like this:
<asp:ContentPlaceHolder ID="Holder1" runat="server" />
<asp:ContentPlaceHolder ID="Holder2" runat="server" />
…you can handle them in the view content page like this:
<asp:Content ID="Content1" ContentPlaceHolderID="Holder1" runat="server">
<p>This text goes to "Holder1" content place holder</p>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="Holder2" runat="server">
<p>This text goes to "Holder2" content place holder</p>
</asp:Content>
View User Controls
View user controls can be used regardless of what view you are looking at. Here are some examples of viewing user controls, are taken from Wekeroad's blog post about View User Controls. To use a view user control:
<%=Html.RenderUserControl(“~/UserControls/UserList.ascx”)%>
If you have the need you can also pass data and anonymous types to it.
<%=Html.RenderUserControl(“~/UserControls/UserList.ascx”,ViewData.Users, new {GroupID=2})%>
without understanding the question properly, i'd suggest a masterpage approach. ie. have the masterpage render the components and pages derive from the masterpage.
i fear this is not a good answer however. i dont understand this: "..have the viewname and viewdata delivered by each component's controller and not the controller of the view they are displayed on."
This is an old question but here's my take on it with razor:
In CQRS style apps you can push an ICommand to a single service endpoint and the service figures out how to route it.
a good example of this: https://github.com/ncqrs/ncqrs/blob/master/Framework/src/Ncqrs/Commanding/ServiceModel/CommandService.cs
What say - instead of trying to execute a command we were trying to execute a query?
And instead of 1 we were passing 1 or more as a list of IViewModelExecutor?
In your main page you have a master ViewModel it has a definition for each of it's sub ViewModels, when the page loads we ask the service to get all the view models pertaining to this view:
service.Load(new List<IViewModelExecutor>{UserinfoViewModel, UserAccountViewModel });
the service loads the data from the readmodel and caches where needed. The sub view models are passed to the sub views using Html.RenderPartial("subviewname", Model.SubViewModel)
The sub view knows how to render itself based on the model.
Now - as we have a compositional UI with ajax updates - lets say we want to update the UserAccount part of the page. In the controller we have an action that is responsible for rendering JUST that part.
it can also request from the service.Load(new List<IViewModelExecutor>{UserAccountViewModel })
and only get back the part of the view model it needs - probably cached. unless the params change obv.
Now we have a compositional UI that can be constructed in whole or in part.
And using content negotiation we can serve HTML fragments, JSON or XML depending on the HTTP headers in the request.