I'm running the razor code below in a .cshtml file (it's a simplified version of something more complex that I need to achieve), but the renderTestB helper does not seem to execute.
#renderTestA("test string 1", "test string 2");
#helper renderTestA(string input1, string input2)
{
<div>
#renderTestB(input1)
#renderTestB(input2)
</div>
}
#helper renderTestB(string input)
{
<p class="test">#input</p>
}
Why is this? And is there another way of achieving what I'm trying to do?
I realise I could duplicate the paragraph code within the renderTestA helper, but would obviously prefer a re-usable code solution.
What about something like this?
#renderTestA(renderTestB("test string 1"), renderTestB("test string 2"))
#helper renderTestA(string input1, string input2)
{
<div>
#input1
#input2
</div>
}
#helper renderTestB(string input)
{
<p class="test">#input</p>
}
You should consider using Editor / Display templates or custom HTML helpers instead as the #helper functionality was used back before these features became the norm.
As to why you cannot nest them. It introduces a number of problems that are easily avoided by using the syntax I suggested above. Such as... if you have a circular loop of nested helpers, it could easily cause a stack overflow.
Related
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.
I'm trying to understand what the equivalent of a user control is within an MVC application? From what I understand, it's a Partial. The problem I'm finding is that when I use a user control, I'm able to encapsulate the logic within that control and re-use it across multiple pages without having to worry about the parent page.
How does this work with MVC? There doesn't seem to be any way to encapsulate logic with a partial view.. in fact it's confusing the hell out of me.
Does this mean that for any page that might use the partial, the controller would always have to return the data for the partial just in case it's required? Doesn't this seem incredibly inefficient? I can't seem to wrap my head around how this works...
You can add logic using razor syntax. For example, you can use conditional statements to manipulate the final output.
#{
if(whatever == true)
{
<p>Whatever is true</p>
}
else
{
<span id="someId">
<label for="enterName">Enter name:</label> <input type="text" id="enterName" />
</span>
}
}
In addition to this you can use #helper syntax like
#helper MakeLink(dynamic id)
{
<div id="actionButtons">
#Html.ActionLink("Some Title", "Some Action", new {id = id}) |
#Html.ActionLink("Another Title", "Another Action", "Some Controller", new {id = id})
</div>
}
The logic in partials is slightly different to what you are used to in Web Forms user controls. The above logic can be placed in a Partial so that you can reuse it later, you just need to place it in /Views/Shared/DisplayTemplates or EditorTemplates.
Just started converting to MVC from classic ASP and wondering about best practice for if-statements that is used to decide if a HTML-element should be visible or deciding if that or that HTML-element should be visible.
Example:
if (type = 1)
{
<img src="/Content/Images/gray.jpg" />
}
else
{
<img src="/Content/Images/blue.jpg" />
}
I see several options here.
1. Use if-statements in my views (prefer not to)
2. Use a HTML-helper (will be a lot of helpers = Hard to work with the code)
3. Create HTML-snippets in my controller and pass into my view
4. Work with JavaScript to show/hide element on document load.
The example above if of course a simplification of my problem. Many thanks for the help!
/Mike
EDIT:
Took a bad example, here comes a new:
if (type = 1)
{
<span class="class1">Something something</span>
}
else
{
<div id="id1">
text
<img src="/Content/Images/gray.jpg" />
</div>
}
Personally I'm starting to use a separate layer for loading up by viewmodels with html specific information.
public class ViewModel
{
public string ImageName { get; set; }
}
Keeps the views clean:
<img src="/Content/Images/<%= Model.ImageName %>.jpg" />
I personally prefer to use HtmlHelper extensions or declarative HTML helpers for all but most trivial conditional expressions. However in this case I would probably let my laziness win, and express the conditional directly in the view like so:
<img src="/Content/Images/#(i == 1 ? "gray" : "blue").jpg" />
Edit
For more complex scenarios, definitely HTML helpers. If you're using the Razor syntax, you can create declarative helpers by placing them into your ~/Views/Helpers directory. They're very clean to write and use.
But the idea behind creating a good helper is that it encapsulates an intention. This is easy to test by trying to give your helper a name. If the name is IfOneThenShowSomethingElseGrayImage, you should try to break it into further pieces still.
So let's imagine the intention in your example is to show information about the current user based on the user type. You could create a helper like this:
#helper UserType(int type) {
#if (type == 1) {
<span class="class1">Something something</span>
} else {
<div id="id1">
text
<img src="/Content/Images/gray.jpg" />
</div>
}
}
And then use it in your view like this:
#UserType(Model.UserType)
Even though it's only used on one page, it enhances the readability in the view, and can be reused if necessary.
Just my two cents.
I have a large view that needs some conditional logic to decide which of several html chunks to render in the middle of the view. I have a property on my model which can have several different values which determines the html to be output.
I would normally put conditional logic in an html helper, but given that each output is a fair chunk of html, I am not sure that escaping these in a c# file would be great. I could also put the logic in the action and render different views but given that the majority of the view is the same, this does not seem great either. So I am left with multiple if statements in my view (or partial?) which also seems ugly (and is obviously untestable).
What is the best way of doing this?
(I am using MVC3 in case there is something new and funky I can use!)
I usually put separate visual chunks in their own partials. Then my view conditionally calls each partial with Html.Partial. This keeps you main view from bloating.
In general, I try to avoid Html.Helpers that contain more than a single element.
Something like:
#if(Model.HasA)
{
#Html.Partial("widgetdetails-hasa")
}
#if(Model.HasB)
{
#Html.Partial("widgetdetails-hasb")
}
// etc
IMHO logic like this is fine for a view:
#if (Model.ShouldShowSomeSection)
{
... some large chunk of HTML
}
else
{
... some alternative
}
I agree with the answer from #bmancini , but here's what I'd do slightly differently:
I would logically group those 'several html chunks to render' into different partial views :
_partialViewA.cshtml and _partialViewB.cshtml
I then would use extension methods and have my logic in a Helpers folder, then Html sub-folder like this:
using System.Web.Mvc.Html;
public static class SomeViewHelper
{
public static MvcHtmlString OutputHtmlString(this HtmlHelper helper , SomeModel model)
{
if(model.HasA)
{
return helper.Partial("_partialViewA", model)
}
if(model.HasB)
{
return helper.Partial("_partialViewB", model)
}
}
}
This would remove all the logic from the view which would now only have this code:
#Html.OutputHtmlString(Model);
At least this would remove the 'ugliness' and avoid the conditional statements, and also avoid 'escaping the html chinks in C# code'...
Of course I would have to reference the Helpers.Html folder with a #using statement in the view.
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