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.
Related
I am newbie in MVC and trying to create application in asp.net mvc2. For the common header and footer i wants to use master pages which are having dynamic content, Can anyone please tell me where is the best place to write code for master page. I am trying to add code behind for master page but I am not sure about what is recommended in this situation.
In my opinion, you can create partial views (type of user control) and render it in your master page. Write all the logic to get data from database into partial view's action method. You can write action method in any of your controller. ex. You can create a Layout controller and write action method related to master page. A partial view can also be reusable.
To render your partial view in master page you can write code like :
<div><%= Html.Action("Header", "Layout") %></div>
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.
Take the example of wanting to have a "Latest news items" sidebar on every page of your ASP.NET MVC web site. I have a NewsItemController which is fine for pages dedicating their attention to NewsItems. What about having a news sidebar appear on the HomeController for the home page though? Or any other controller for that matter?
My first instinct is to put the logic for selecting top 5 NewsItems in a user control which is then called in the Master Page. That way every page gets a news sidebar without having to contaminate any of the other controllers with NewsItem logic. This then means putting logic in what I understood to be the presentation layer which would normally go in a Controller.
I can think of about half a dozen different ways to approach it but none of them seem 'right' in terms of separation of concerns and other related buzz-words.
I think you should consider putting it in your master page. Your controller can gather data (asynchronously, of course), store it in a nice ViewModel property for your view (or in TempData) and then you can call RenderPartial() in your master page to render the data.
The keeps everything "separate"
http://eduncan911.com/blog/html-renderaction-for-asp-net-mvc-1-0.aspx
This seems to address the question - even using the instance of a sidebar - but using a feature not included with MVC 1 by default.
http://blogs.intesoft.net/post/2009/02/renderaction-versus-renderpartial-aspnet-mvc.aspx
This also indicates the answer lies in RenderAction.
For anyone else interested, here's how I ended up doing it. Note you'll need to the MVC Futures assembly for RenderAction.
Basically you'd have something like this in your controller:
public class PostController
{
//...
public ActionResult SidebarBox()
{
// I use a repository pattern to get records
// Just replace it with whatever you use
return View(repoArticles.GetAllArticles().Take(5).ToList());
}
//...
}
Then create a partial view for SidebarBox with the content you want displayed, and in your Master Page (or wherever you want to display it) you'd use:
<% Html.RenderAction<PostController>(c => c.SidebarBox()); %>
Not so hard after all.
You can create a user control (.ascx) and then call RenderPartial().
Design a method in your controller with JsonResult as return type. Use it along with jQuery.
Use RenderAction() as suggested by elsewhere.
News section with ASP.NET MVC
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.
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
<% } %>