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
Related
I want to create a custom razor tag like <text></text> to decide what to do with the html code inside of it. Is there any way to create razor elements like <text></text> element and add it to the razor engine?
I don't want to create any HtmlHelpers for this.
For Examle:
<WYSYWIG>
Hello There!
</WYSYWIG>
or
<WeatherChart City="NY">
</WeatherChart>
Explanation:
Well the idea is to have server tags to be translated (Parsed) to html codes by the attributes given to them. This kind of codes helps junior developers not to be involved with the complexity of controls.
The closest thing to what you are describing is to create display or editor templates. You can then define a template for a model and use it with #Html.DisplayFor() in the view.
Here is a good blog post to get you started aspnet mvc display and editor templates and a quick overview of the structure below.
Example
Model - WeatherChartModel.cs
public class WeatherChartModel
{
}
Display template - WeatherChart.cshtml
<div class="weather-chart">
// Some other stuff here
</div>
View - Index.cshtml
#model WeatherChartModel
#Html.DisplayForModel() // This will output the template view for the model
In order to create custom element handling in razor, such as <text>, you'd need to implement a custom System.Web.Razor.dll (which is responsible for parsing the document). Specifically, the class you're looking to re-implement would be the System.Web.Razor.Parser.HtmlMarkupParser.
However, I don't believe this is necessary given how flexible the framework itself is. If you're looking to keep things modular, have a look at either using DisplayTemplates/EditorTemplates or consider writing your own extension method. For example, either of the following would be more ideal:
#* TextField is decorated with UIHint("WYSIWYG"), therefore
calling ~/Views/Shared/EditorTemplates/WYSIWYG.cshtml *#
#Html.EditorFor(x => x.TextField)
#* WeatherField is decorated with UIHint("WeatherChart"), therefore
calling ~/Views/Shared/DisplayTemplates/WeatherChart.cshtml *#
#Html.DisplayFor(x => x.WeatherField)
Alternatively:
#* Custom extension method *#
#Html.WysiwygFor(x => x.TextField)
#* Another custom extension method *#
#Html.WeatherChartFor(x => x.WeatherField)
Apologies if this has been posted before, however, I am struggling to distinguish the basic logic from the many more complex tutorials/samples that exist.
I have some basic static HTML that I'd like to re-use within multiple (but not all) Views:
<div class="logout">
#Html.ActionLink("Log out", "Login", "Login", null, null)
</div>
How do I place this into a reusable component, and how do I then reference it in my Views?
All the other examples I've found today seem to pass Models to the object, or make it doing something dynamic, which is not required.
Just put your static html inside partial view and then include partial view in your view by
using Html Helpers such as #Html.Partial() or #Html.RenderPartial().
Examples :-
#{ Html.RenderPartial("Path/to/my/partial/view"); }
#Html.Partial("Path/to/my/partial/view")
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
}
We're in the process of transferring a big WebForms app to MVC.
In the WebForms app we have some reusable controls (.ascx). For instance, a TextBox that shows username suggestions as you type. The control adds some JS/CSS to the page, has some server-side logic and custom properties (like SelectedUserID).
What should it be in an MVC app? How do I encapsulate those things into one reusable entity in an MVC app? Maybe a partial view? But you can't add, say, JS/CSS to the page's <head> from a partial view (preferably, with a check that it's not already been added)... Also, how do I return something like the mentioned SelectedUserID in a partial view?
To rephrase the question - how would you implement such a control in an MVC app?
PS. I know you can still use .ascx user-controls in MVC apps but it seem a "hacky/legacy" way. What is the "legitimate" option?
Your question is pretty broad. All my comments/answer are going to be in Razor. In my opinion, if you're going to switch to MVC, you might as well use Razor. There are plenty of good reasons to switch, but I'd say my top pick for anyone who is migrating, the best reason is two fold; first razor allowed me to drop some bad habits about how programming works with webforms and in the same manor it forced me to re-write my code instead of trying to copy and paste and change the original code to work in MVC.
The control adds some JS/CSS to the page, has some server-side logic and custom properties.
What should it be in an MVC app?
This is a pretty religious software question. There are plenty of ways of doing it.
So onto my religous opinion about how to add JS/CSS in MVC. One way to include server side is to create a region in the Layout/Master template.
Views/_ViewStart.cshtml
#{
Layout = "~/Views/Shared/Layout.cshtml";
}
Views/Shared/Layout.cshtml
<!doctype html>
<head>
<link href="/Styles/Global.css" rel="stylesheet" type="text/css" />
#RenderSection("Styles", required: false)
<script src="/Scripts/Global.js" type="text/javascript"></script>
#RenderSection("Scritps", required: false)
This would allow each view to optionally (because required=false) add any Styles or Scripts using the RenderSecion code.
Views/Home/Index.cshtml
#section Styles {
<link href="/Styles/Home/Index.css" rel="stylesheet" type="text/css" />
}
This is pretty simple, and probably a good solution for many simple to moderately complex sites. This wasn't enough for me, as I needed to do what you requested, and only include files if they were truly needed. Additionally, partial views and templates cannot render sections, which I found to be a giant PITA. So I added some HtmlHelper extension methods. (i'm only going to show the code for Javascript as the Stylesheets are nearly Identical code. These methods don't allow duplicate urls.
Domain/HtmlHelperExtensions.cshtml
public static class HtmlHelperExtensions
{
private const string _JSViewDataName = "RenderJavaScript";
private const string _StyleViewDataName = "RenderStyle";
public static void AddJavaScript(this HtmlHelper HtmlHelper, string ScriptURL)
{
List<string> scriptList = HtmlHelper.ViewContext.HttpContext.Items[HtmlHelperExtensions._JSViewDataName] as List<string>;
if (scriptList != null)
{
if (!scriptList.Contains(ScriptURL))
{
scriptList.Add(ScriptURL);
}
}
else
{
scriptList = new List<string>();
scriptList.Add(ScriptURL);
HtmlHelper.ViewContext.HttpContext.Items.Add(HtmlHelperExtensions._JSViewDataName, scriptList);
}
}
public static MvcHtmlString RenderJavaScripts(this HtmlHelper HtmlHelper)
{
StringBuilder result = new StringBuilder();
List<string> scriptList = HtmlHelper.ViewContext.HttpContext.Items[HtmlHelperExtensions._JSViewDataName] as List<string>;
if (scriptList != null)
{
foreach (string script in scriptList)
{
result.AppendLine(string.Format("<script type=\"text/javascript\" src=\"{0}\"></script>", script));
}
}
return MvcHtmlString.Create(result.ToString());
}
}
Updated Views/Shared/Layout.cshtml
<!doctype html>
<head>
<link href="/Styles/Global.css" rel="stylesheet" type="text/css" />
#Html.RenderStyleSheets()
<script src="/Scripts/Global.js" type="text/javascript"></script>
#Html.RenderJavascripts()
Updated Views/Home/Index.cshtml
#Html.AddStyleSheet("/Styles/Home/Index.css")
Onto your next question...
How do I encapsulate those things into one reusable entity in an MVC app? Maybe a partial view?
Razor supports both partial views and templates. Although technically there is large overlap in what each can do, there are limitations of how each were designed to allow a programmer to take advantage of each when needed.
Partial views do not require a Model/Class in order to be rendered. Here is a completely valid partial view:
/Views/Home/Index.cshtml
#Html.Partial("Partial-Menu")
/Views/Shared/Partial-Menu.cshtml
<div id="menu">
Home
About Us
</div>
Templates on the other had, do required a Model in order to be rendered. This is because Templates are a way to render any class or struct as either an Editing Template or a Display Template.
/Models/Person.cs
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
/Controllers/HomeController.cs
public ActionResult Index()
{
Person model = new Person();
model.FirstName = "Jon";
model.LastName = "Doe";
return this.View(model);
}
/Views/Home/Index.cshtml
#model Project1.Models.Person
#Html.DisplayModel()
#Html.EditforModel()
/Views/Shared/DisplayTemplates/Person.cshtml
#model Project1.Models.Person
<div>#Html.LabelFor(x => x.FirstName) #Html.DisplayFor(x => x.FirstName)</div>
<div>#Html.LabelFor(x => x.LastName) #Html.DisplayFor(x => x.LastName)</div>
/Views/Shared/EditorTemplates/Person.cshtml
#model Project1.Models.Person
<div>#Html.LabelFor(x => x.FirstName) #Html.EditorFor(x => x.FirstName)</div>
<div>#Html.LabelFor(x => x.LastName) #Html.EditrorFor(x => x.LastName)</div>
The way I see it, any model that might turn into a Form for data entry should probably be a Template. This is the way I prefer to encapsulate my models. Using the extension methods provided earlier, both my partial views and templates can load includes as needed (currently only one of my models of oodles of them actually needed to use this).
My preference is to have up to three includes (of each Javascript and Styles) per page rendered. I basically have a Global.css/js, a controller Home.css/js, and a page Index.css/js as the possible includes. It's been very seldom that I have a controller include.
The best solution I've found so far is Razor declarative helpers. They fit awesomely.
#helper UserTextBox() {
<input type="text" />
<!--my own helper that registers scripts-->
#Html.AddJS("../js/myscript.js")
}
Related question: Razor: Declarative HTML helpers
Related note: you can't use #Html helper inside a declarative helper but there's a workaround: https://stackoverflow.com/a/5557503/56621
Well, you can't have something that "automatically" includes css/js. That's something the user has to add to the page (either the master/layout or the current page). But in general, one would create an Html Helper. Don't expect these to be complex systems (like the gigantic grids of asp.net days) but you can do a lot with them.
Yes, a partial page may be easier for simple things. Even simpler might be an Editor or Display Template.
In general, however, most "controls" for MVC these days are jQuery based.
A short video is available here: http://www.asp.net/mvc/videos/mvc-2/how-do-i/how-do-i-create-a-custom-html-helper-for-an-mvc-application
Don't be tempted to include your javascript in these mechanisms. While it may work for a single control, if you have multiple controls on a page you will get duplicate css/js.
I would add the js/css to the main layout then write an html helper to render your custom control.
Many controls that uses jquery in aspnet mvc works this way.
Take a look at this controls here
1) for custom js/css you can use sections like this:
//code in view
#section someName
{
...
}
//code in layoutpage
#if (IsSectionDefined("someName"))
{
#RenderSection("someName")
}
2) i'd better do the following:
create model SelectedUser with Id property
create templates for this model
add a SelectedUser object to any model, which needs it
They must be separated from surrounding text by blank lines.
The begin and end tags of the outermost block element must not be indented.
Markdown can't be used within HTML blocks.
What is the replacement for a server control in ASP.NET MVC? What I want to do is to create a declarative and imperative binding so I can write
<cc1:MyControl Header="Some Header" Content="Some Content" />
which would mean that an instance of the MyControl class will be created and possibly rendered to
<h1>Some Header</h1>
<p>Content</p>
I don't want any viewstate or postback crap, just the modularity. I also want these modules to be contained in a separate class library, so ViewUserControls will not do for me. Using a server controls in the normal way works, but it generates a form tag and a viewstate field, which I do not want if I can avoid it.
I have seen this question and this one about how to use server controls in ASP.NET MVC, but they do not provide enough answer.
Edit: I found the answer. When I added the user control using the designer, it automatically created a <form> which I missed. If I simply remove that tag, everything works perfectly.
You can still use all controls in ASP.NET MVC if they don't require rendering in a server form.
ascx files and #Register directives still work pretty well. The great new thing is Html.RenderPartial method that lets you pass a model object to a partial view (ascx) and have it render accordingly.
Just adding one more possibility to Mehrdad answer, you can use extension methods to do a simple control like this:
<%= html.MyControl( "Some header", "Some content" ) %>
<Extension()> _
Public Function MyControl(ByVal htmlHelper As HtmlHelper, _
ByVal Header As String, _
ByVal Content As String) As String
Dim sb As New StringBuilder()
sb.AppendFormat("<h1>{0}</h1>", Header)
sb.AppendFormat("<p>{0}</p>", Content)
Return sb.ToString()
End Function
Or you can make a more complex control like this example: Create an ASP.NET MVC GridView Helper Method
Other than the controls which still work with ASP.Net MVC, you can use mvc controls.
Repeater example - dead link
Exploring ASP.Net MVC Futures - dead link
UPDATE: This answer was for ASP.Net MVC 1.0 in 2009. It is outdated and irrelevant at this point.