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
}
Related
I have an asp.net-mvc website where up until now there have been no entitlements as its been open to everyone. Many of the pages are detailed forms with textboxes, select dropdowns, etc
I now need to change this to make many of the existing pages "entitled" so only certain people have edit capability and everyone else sees a read only page. I am trying to figure out if I should
Create a seperate view for everyone one of my existing views with forms that is just read only html on a page and redirect based on entitlements on the server side, something like
public ActionResult OrderView()
{
var isEntitled = Model.IsEntitled()
if (isEntitled)
{
return("OrderEditableView", GetViewModel());
}
else
{
return("OrderReadOnlyView", GetViewModel());
}
}
or
Reuse the same view and simply disable or hide the "Save" button on the screen.
on my view have
<% if (Model.IsEntitled) { %>
<button id="saveButton">Save Changes</button>
<% } %>
The second option would be much quicker to implement but would be a little weird because it would look like you could edit all of the fields but just dont' see (or see a disabled) Save button on the form.
The first option seems cleaner but I would have to go and create a new view for everyone of my screens and that seems like a lot of work (over 100 views currently)
This seems like a common situation so I wanted to see if there was a best practice on dealing with this situation. Obviously I am looking for a solution given the current situation that I am in but I would also be interested if there were patterns or solution that would be considered best practice or recommended if I was starting from scratch.
I would go for creating a separate partial/full views for both.
Reasons:
easy to change and maintain
validation code only in editable part
clear separation of concerns
cleaner code
may be we can reuse some of the code by abstracting/separating similar code to re usable partial views
easy control over access
Also, in real world we balance among budget,time and effort. so considering that I may use mix of these depending upon the situation.
I'd go for the second options with a single view for both writable and readonly forms.
For the visual side, I'd implement a few things in addition to hiding the Save button:
Set the input fields to readonly. You can easily do this with client side Javascript.
Change the colors (and possibly other attributes) of the input fields to emphasize that they are readonly (e.g. remove the borders). This can be easily achieved by a style sheet and a single additional class name in the form tag.
The authorization on the server side is simple as well. Just annotate the action accordingly.
It could look like this (simplified, using Razor and jQuery):
View:
#using (Html.BeginForm("AddressController", "SaveChanges", FormMethod.Post,
new { #class = Model.IsEntitled ? "regular" : "readonly"}))
{
<p>
#Html.TextboxFor(model => model.Name)
</p>
<p>
#Html.TextboxFor(model => model.Address)
</p>
#if (Model.IsEntitled) {
<p>
<button id="saveButton">Save Changes</button>
</p>
}
}
<script type="text/javascript">
$( document ).ready(function() {
$('form.readonly input').attr("readonly", true);
});
</script>
Controller:
public class AddressController : Controller {
[Authorize(Roles = "xyz")]
[HttpPost]
public ActionResult SaveChanges(Address address) {
...
}
}
CSS:
.readonly input {
border: none;
}
an option is to do the switch in the view..
<% if (Model.IsEntitled)
{
Html.Partial("OrderEditablePartialView", Model)
}
else
{
Html.Partial("OrderReadOnlyPartialView", Model)
}
%>
another option could be to create custom Html helpers to render the elements as editable or readonly, which would give you some flexibily on how you handled each element.
crude example:
public static MvcHtmlString FormTextBox(this HtmlHelper helper, string name, string text, bool editable)
{
return
new MvcHtmlString(
String.Format(
editable
? "<input type='text' id='{0}' name='{0}' value='{1}'>"
: "<label for='{0}'>{1}</label>",
name, text));
}
The quickest solution would be some javascript that disables all input elements on pages where the user isn't entitled. How to implement this is up to you, it depends on how your views and models are structured.
This way you can reuse the edit-views without even having to edit them, but depending on the kind of user you have you can expect "site not working" calls when your forms appear disabled.
patterns or solution that would be considered best practice or recommended if I was starting from scratch.
The proper way to go would be DisplayTemplates and EditorTemplates with proper viewmodels, but from your "create a new view for everyone of my screens and that seems like a lot of work" comment I gather you're not interested in that.
So I'd go with the former. Please make sure you check the permissions in the [HttpPost] action methods, as malicious users can easily re-enable the form and post it anyway.
I have a lot of content-heavy views in my ASP.NET MVC 2 site. These contain several re-occurring HTML patterns. When using ASP.NET Webforms, a class derived from WebControl could encapsulate these patterns. I'd like some pointers on the correct approach for this problem with MVC.
Detailed Explanation
Patterns not unlike the following HTML markup keep occurring throughout these views. The markup renders into an isolated a box of content:
<div class="top container">
<div class="header">
<p>The title</p>
<em>(and a small note)</em>
</div>
<div class="simpleBox rounded">
<p>This is content.</p>
<p><strong>Some more content</strong></p>
</div>
</div>
This is a trivial example, but there are more complex recurring patterns. In ASP.NET Webforms I would have abstracted such code into a WebControl (let's say I'd have named it BoxControl), being included on a page like this:
<foo:BoxControl runat="server">
<Header>The title</Header>
<Note>(and a small note)</Note>
<Content>
<p>This is content.</p>
<p><strong>Some more content</strong></p>
</Content>
</foo:BoxControl>
This abstraction makes it easy to adapt the way the box is constructed throughout the site, by just altering the BoxControl source. It also keeps the static HTML content neatly together in the View Page, even when combining several BoxControls on a page. Another benefit is that the HTML used as content is recognized by the IDE, thus providing syntax highlighting/checking.
To my understanding, WebControls are discouraged in ASP.NET MVC. Instead of a WebControl, I could accomplish the abstraction with a partial view. Such a view would then be included in a View Page as follows:
<%= Html.Partial("BoxControl", new {
Header="The Title",
Note="(and a small note)",
Content="<p>This is content.</p><p><strong>Some more content</strong></p>"});
%>
This is not ideal, since the 'Content' parameter could become very long, and the IDE does not treat it as HTML when passed this way.
Considered Solutions
Strongly-Typed ViewModels can be passed to the Html.Partial call instead of the lengthy parameters shown above. But then I'd have to pull the content in from somewhere else (a CMS, or Resource file). I'd like for the content to be contained in the View Page.
I have also considered the solution proposed by Jeffrey Palermo, but that would mean lots of extra files scattered around the project. I'd like the textual content of any view to be restricted to one file only.
Should I not want to abstract the markup away? Or is there maybe an approach, suitable for MVC, that I am overlooking here? What is the drawback to 'sinning' by using a WebControl?
There is a solution to this problem, although the way to get there is a little more clutsy than other frameworks like Ruby on Rails.
I've used this method to create markup for Twitter Bootstrap's control group syntax which looks like this:
<div class="control-group">
<label class="control-label">[Label text here]</label>
<div class="controls">
[Arbitrary markup here]
</div>
</div>
Here's how:
1) Create a model for the common markup snippet. The model should write markup on construction and again on dispose:
using System;
using System.Web.Mvc;
namespace My.Name.Space
{
public class ControlGroup : IDisposable
{
private readonly ViewContext m_viewContext;
private readonly TagBuilder m_controlGroup;
private readonly TagBuilder m_controlsDiv;
public ControlGroup(ViewContext viewContext, string labelText)
{
m_viewContext = viewContext;
/*
* <div class="control-group">
* <label class="control-label">Label</label>
* <div class="controls">
* input(s)
* </div>
* </div>
*/
m_controlGroup = new TagBuilder("div");
m_controlGroup.AddCssClass("control-group");
m_viewContext.Writer.Write(m_controlGroup.ToString(TagRenderMode.StartTag));
if (labelText != null)
{
var label = new TagBuilder("label");
label.AddCssClass("control-label");
label.InnerHtml = labelText;
m_viewContext.Writer.Write(label.ToString());
}
m_controlsDiv = new TagBuilder("div");
m_controlsDiv.AddCssClass("controls");
m_viewContext.Writer.Write(m_controlsDiv.ToString(TagRenderMode.StartTag));
}
public void Dispose()
{
m_viewContext.Writer.Write(m_controlsDiv.ToString(TagRenderMode.EndTag));
m_viewContext.Writer.Write(m_controlGroup.ToString(TagRenderMode.EndTag));
}
}
}
2) Create a nifty Html helper
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using My.Name.Space
namespace Some.Name.Space
{
public static class FormsHelper
{
public static ControlGroup ControlGroup(this HtmlHelper helper, string labelText)
{
return new ControlGroup(helper.ViewContext, labelText);
}
}
}
3) Use it in the view (Razor code)
#using (Html.ControlGroup("My label"))
{
<input type="text" />
<p>Arbitrary markup</p>
<input type="text" name="moreInputFields" />
}
This is also the way MVC framework renders a form with the Html.BeginForm method
Well you wouldn't render the partial like that, pass it a strongly-typed ViewModel, like this:
<%= Html.RenderPartial("BoxControl", contentModel) %>
contentModel is the ViewModel (just a POCO-like storage mechanism for your views), which the strongly typed partial view would bind to.
So you can do this in your partial view:
<h1><%: Model.Header %></h1>
<p><%: Model.Content %></p>
etc etc
After considering the answers and running an experiment, I'm inclined to adhere to the pure MVC approach and duplicate some presentation code throughout View Pages. I'd like to elaborate on the rationale for that decision.
Partial View
When using a Partial View, The content for the box needs to be passed as a View Model, making the View Page less readable versus declaring the content HTML on the spot. Remember that the content does not come from a CMS, so that would mean filling the View Model with HTML in a controller or setting a local variable in the View Page. Both of these methods fail to take advantage of IDE features for dealing with HTML.
WebControl
On the other hand, a WebControl-derived class is discouraged and also turns out to have some practical issues. The main issue that the declarative, hierarchical style of traditional ASP.NET .aspx pages just does not fit the procedural style of MVC.NET View Pages. You have to choose for either a full blown traditional approach, or go completely MVC.
To illustrate this, the most prominent issue in my experimental implementation was one of variable scope: when iterating a list of products, the MVC-way is to use a foreach loop, but that introduces a local variable which will not be available in the scope of the WebControl. The traditional ASP.NET approach would be to use a Repeater instead of the foreach. It seems to be a slippery slope to use any traditional ASP.NET controls at all, because I suspect you'll soon find yourself needing to combine more and more of them to get the job done.
Plain HTML
Forgoing the abstraction at all, you are left with duplicate presentation code. This is against DRY, but produces very readable code.
It doesnt look like webforms has that much less html to me, it seems more like a lateral move. Using a partial in MVC can make it cleaner but the html markup you needed will still be there, in one place or another. If its mostly the extra html that bothers you, you might take a look at the NHaml view engine or check out haml
the haml website.
I'm by no means a Haml expert but the html does look a lot cleaner...
.top container
.header
%p
The title
%em
(and a small note)
.simpleBox rounded
%p
This is content.
%p
Some more content
I am wondering if it is possible to wrap up an ascx file so that it can be used in different views, without having to type it to a specific Model.
This would mean you could use one ascx file for many pages, as long as the relevant model data was passed in.
Currently I'm duplicating my ascx files for different parts of the site, but this feels very un DRY.
ps this is th kind of model I'm passing in.
public class DinnerPage {
public Dinner dinner {get; set;} //main page data
public List<Dinner> Random6Dinners {get; set;} // ascx content
}
The problem is if I want to use the ascx file that renders the 6 random dinners, but for a different main page, then I have to type it differently.
Basically, can you have an untyped ascx page (or a view)
Thanks all.
I believe what you are asking is if you could re-use many views like a control on a single view? If so, you can indeed. Use the Html.RenderPartial action in your view. This will render an existing "aspx" page on your current view:
Example:
Assuming Model.Dinners is a List/Enumrable of some sort.
Dinners.aspx
<!--html-->
<%foreach(var dinner in Model.Dinners){%>
<% Html.RenderPartial("~/DinnersFolder/Dinner.ascx", new { Dinner = dinner}); %>
<%}%>
<!--html-->
Dinner.aspx
<%
var dinner = ViewData.Eval("Dinner");
%>
<!--html-->
Something like this would work for an individual dinner:
<% Html.RenderPartial("Dinner", Model.Dinner); %> //Dinner being Dinner.ascx
This way your ("MyPartialView" / MyPartialView.aspx), will take in a type of Dinner. You can then pull out that Dinner via ViewData.Eval, and display your "Dinner" accordingly.
In your main page you can: then do something like foreach Dinner in my Dinners, render a PartialControl.
A detailed example of this:
http://highoncoding.com/Articles/638_Understanding_Partial_Views_in_ASP_NET_MVC_Application.aspx
You could aslo use RenderAction, this has the same exact idea. However, it will post to your Controllers action, which in turn returns a view:
<% Html.RenderAction(...); %>
This is a great article about the difference between each, and which you should use:
http://blogs.intesoft.net/post/2009/02/renderaction-versus-renderpartial-aspnet-mvc.aspx
Have you tried having a base Dinner model in which the 6 random dinner classes derive from and then just use the user control as such:
<%= Html.RenderPartial("DinnerControl",
Model.RandomDinnerThatDerivesFromBaseDinner); %>
And then the first line of your control should be something like:
<%# Control Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<BaseDinner>" %>
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.
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