MVC 3 Razor, Helpers with custom markup/section - asp.net-mvc-3

I'm not even sure if this is possible, but I thought I would check to see if there is any way to make this easier.
First, I have some repeated markup in my site that looks like this:
<div class="module">
<h3>Title</h3>
<div>
<p>Information goes here</p>
</div>
</div>
What I want to do is wrap this up in some kind of helper/section so that I could do something like this
#MyHelper("This is my Title") {
<p>Here is my custom markup</p>
}
Then, when it renders, it would inject the title passed in through the parameter between the <h3></h3> and the custom markup in the divs. The custom markup could be anything from test, to form controls, to a partial view. Is this something that is possible?

There's also the other way, without disposable trick, which also requires a little less work, great for little helpers.
#helper MyHelper(string title, Func<object, object> markup) {
<div class="module">
<h3>Title</h3>
<div>
<p>#markup.DynamicInvoke(this.ViewContext)</p>
</div>
</div>
}
Usage of this helper looks like this:
#MyHelper("This is my Title",
#<p>Here is my custom markup</p>
)
Or with multiple lines:
#MyHelper("This is my Title",
#<text>
<p>More than one line</p>
<p>Of markup</p>
</text>
)
Telerik MVC controls used this trick for example to let you add your javascript code at the document load.
Here's also a nice example. There's also some information here.

Well, here's a "standard" way of doing something close to it, using an HTML helper extension. A very simple version of what Html.BeginForm() does.
Approach: Simply return an IDisposable, and let the using statement take care of the rest.
This is just an example of the concept (although it works). Not intended for immediate reuse. Written quickly with lots of shortcuts, not production code, plenty of opportunities for improvement and optimization, may have silly mistakes, could use TagBuilder etc. etc. Could easily be modified to reuse the Wrapper class for different... wrappings (there may even be a generic one already in ASP.NET MVC - haven't had a need for one).
public static class WrapHelper
{
private class Wrapper : IDisposable
{
private bool disposed;
private readonly TextWriter writer;
public Wrapper(HtmlHelper html)
{
this.writer = html.ViewContext.Writer;
}
public void Dispose()
{
if (disposed) return;
disposed = true;
writer.WriteLine(" </div>");
writer.WriteLine("</div>");
}
}
public static IDisposable Wrap(this HtmlHelper html, string title)
{
TextWriter writer = html.ViewContext.Writer;
writer.WriteLine("<div class=\"module\">");
writer.WriteLine(" <h3>" + html.Encode(title) + "</h3>");
writer.WriteLine(" <div>");
return new Wrapper(html);
}
}
Usage:
#using (Html.Wrap("Title"))
{
<p>My very own markup.</p>
}

#TheKaneda, Thanks for the insight. I took your idea and extended it, such that you supply a PartialView name and it knows how to parse it.
<Extension()> _
Public Function UseTemplate(ByVal html As HtmlHelper, ByVal PartialView As String) As IDisposable
Return New TemplateWrapper(html, PartialView)
End Function
Public Class TemplateWrapper
Implements IDisposable
Private _HtmlHelper As HtmlHelper
'Index 0 is header
'Index 1 is footer
Private _TemplateWrapper As String()
Public Sub New(ByVal Html As HtmlHelper, ByVal PartialView As String)
_TemplateWrapper = Html.Partial(PartialView).ToHtmlString.Split("##RenderBody()")
_HtmlHelper = Html
_HtmlHelper.ViewContext.Writer.Write(_TemplateWrapper(0))
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
_HtmlHelper.ViewContext.Writer.Write(_TemplateWrapper(1).Substring(12))
End Sub
End Class
Use the same usage as #TheKaneda's example. In your partial view, instead of calling #RenderBody(), just put ##RenderBody() which acts as a flag for the middle part of your content. Sorry for the VB translation.
Uses an example of my usage.
Using Html.UseTemplate("ContentWrapper")
#Html.EditorFor(Function(m) m.Var1, "TemplateHint")
#Html.EditorFor(Function(m) m.Var2, "TemplateHint")
#Html.EditorFor(Function(m) m.Var3)
End Using
My Partial looks like this...
<div class="content">
##RenderBody()
</div>

If you are using Razor and MVC 3 it's real easy to write a quick helper method that would go inside the app_code folder, (I'll name it MyHtmlHelpers)
I'd try something a little different and a little easier such as:
#helper myTemplate(string title, string markup){
<div class="module">
<h3>#title</h3>
#markup
</div>
}
And the way you use it from a .cshtml file is as followed:
#MyHtmlHelpers.myTemplate('title','markup')
It should work, if I understand you correctly.

Related

ASP.NET MVC3 HtmlHelper extension method like BeginForm that uses a partial view?

I created an extension method based on this answer to the SO question c# - How can I create a Html Helper like Html.BeginForm - Stack Overflow and it works fine.
Can I move the embedded HTML in the extension method into a partial view and use that partial view in the method while preserving it's current behavior? In particular, I want to be able to 'wrap' a block of arbitrary HTML.
I ask not out of any pressing need, but simply out of a desire to maintain HTML consistently, e.g. as views and partial views. I imagine it will be a lot easier to spot any problems with the HTML if it's in a view or partial view too.
Here's the HtmlHelper extension method:
public static IDisposable HelpTextMessage(this HtmlHelper helper, bool isHidden, string heading)
{
TextWriter writer = helper.ViewContext.Writer;
writer.WriteLine(
String.Format(
"<div class=\"help-text {0}\">",
isHidden ? "help-text-hidden" : ""));
writer.WriteLine(
String.Format(
"<div class=\"help-text-heading\">{0}</div>",
heading));
writer.Write("<div class=\"help-text-body\">");
return new HelpTextMessageContainer(writer);
}
Here's the HelpTextMessageContainer class:
private class HelpTextMessageContainer : IDisposable
{
private readonly TextWriter _writer;
public HelpTextMessageContainer(TextWriter writer)
{
_writer = writer;
}
public void Dispose()
{
_writer.Write("</div></div>");
}
}
In a view, I can use the extension method like this:
#using(Html.HelpTextMessage(Model.HelpText.IsHelpTextHidden(Model.SomeHelpMessage), "Something"))
{
#:To do something, first do something-more-specific, then do another-something-more-specific.
}
Or I could use it like this too:
#using(Html.HelpTextMessage(Model.HelpText.IsHelpTextHidden(Model.SomeHelpMessage), "Something"))
{
<p>To do something, first do something-more-specific, then do another-something-more-specific.</p>
<p>Also, keep in mind that you might need to do something-else-entirely if blah-blah-blah.</p>
}
I haven't found any way to move the "embedded HTML" into a partial view exactly, but a slightly more-friendly way to encapsulate the HTML in a way that provides HTML and Razor syntax highlighting and Intellisense is to move into a view helper function in a file under the app App_Code folder as described in this post on "Hugo Bonacci's Blog".
Here's my helper function:
#helper HelpTextMessage(bool isHidden, string heading, Func<object, object> content)
{
<div class="help-text #(isHidden ? "help-text-hidden" : "")">
<div class="help-text-heading">
#heading
</div>
<div class="help-text-body">
#content(null)
</div>
</div>
}
Assuming the above helper function is in a file named ViewHelpers.cshtml in the App_Code folder, it can be called in a view like this:
#ViewHelpers.HelpTextMessage(false, "Something",
#:To do something, first do something-more-specific, then do another-something-more-specific.
)
or this:
#ViewHelpers.HelpTextMessage(false, "Something",
<p>To do something, first do something-more-specific, then do another-something-more-specific.</p>
<p>Also, keep in mind that you might need to do something-else-entirely if blah-blah-blah.</p>
)
I like having the embedded HTML in a view more than I do being able to use the #using(Html.HelpTextMessage(...){ ... } syntax, so I'm pretty happy with this as a solution.

Link in validation summary message

Is it possible to put a HTML link in validation summary message? For example I want to put a link to another page in case there is validation error:
#Html.ValidationSummary(False, "read more")
or
#Html.ValidationSummary(False, "read " &
Html.ActionLink("more", "helpforerror").ToHtmlString)
But in the browser the tag is escaped so it doesn't form a link.
I know you have accepted an answer, but i think my solution is more simple and will require less rewriting if you want to add links to existing validation summaries.
You need to put a {0} type format item in your validation message like below, which will be replaced by your link.
ModelState.AddModelError("", "Some error message with a link here {0}.");
then in your view call your validation summary like so:
#string.Format(Html.ValidationSummary().ToString(), Html.ActionLink("Click Here", "Action_To_Link_To")).ToHtmlString()
In this case i have used an extension method I added to the string object .ToHtmlString() that basically just converts the string to an HtmlString preventing any of the markup being escaped. it looks like this:
public static HtmlString ToHtmlString(this String str)
{
return new HtmlString(str);
}
Finally I chose another way to do it: create a div containing the link etc. outside of validation summary, and add the div only if modelstate is not valid:
#If Not ViewData.ModelState.IsValid Then
#<div>read more</div>
End If
This is inspired by an answer to similar question.
The validation text is encoded before the ValidationSumary or ValidationFor, etc...
you just need tu decode the html, then create an MvcHtmlString ...
Exemple :
#HttpUtility.HtmlDecode(Html.ValidationSummary().ToString()).ToMvcHtmlString()
this is an extension i have made to make MvcHtmlString :
namespace System
{
public static class StringExtension
{
public static System.Web.Mvc.MvcHtmlString ToMvcHtmlString(this string value)
{
return System.Web.Mvc.MvcHtmlString.Create(value);
}
}
}
or you can create an HtmlHelper if you plan to reuse this:
namespace System.Web.Mvc.Html
{
public static class FormHelper
{
public static MvcHtmlString ValidationSummaryEx(this HtmlHelper htmlHelper, bool excludePropertyErrors)
{
var original = htmlHelper.ValidationSummary(excludePropertyErrors);
var decoded = HttpUtility.HtmlDecode(original.ToString());
return decoded.ToMvcHtmlString();
}
}
}
Hope it help you or future viewer.
Note: it work for all validations Summary and ValidationFor ...
No, the default behaviour doesn't allow it, but you can make your own. This is what you need: Html raw in validationsummary
You can check if form is valid by jquery and update div with link text:
<div id="divToUpdate">
</div>
$('form').submit(function(){
if(!this.valid())
$('#divToUpdate').html("read <a href='anotherpage.html'>more</a>");
});
If you're sending back HTML in the ModelStateError, you can use this one liner:
#Html.Raw(HttpUtility.HtmlDecode(Html.ValidationSummary().ToHtmlString()))
It's very similar to what #Benoit had suggested, just without needing the extension.

How to make Html.DisplayFor display line breaks?

Embarrassingly newbie question:
I have a string field in my model that contains line breaks.
#Html.DisplayFor(x => x.MultiLineText)
does not display the line breaks.
Obviously I could do some fiddling in the model and create another field that replaces \n with <br/>, but that seems kludgy. What's the textbook way to make this work?
A HtmlHelper extension method to display string values with line breaks:
public static MvcHtmlString DisplayWithBreaksFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var model = html.Encode(metadata.Model).Replace("\r\n", "<br />\r\n");
if (String.IsNullOrEmpty(model))
return MvcHtmlString.Empty;
return MvcHtmlString.Create(model);
}
And then you can use the following syntax:
#Html.DisplayWithBreaksFor(m => m.MultiLineField)
i recommend formatting the output with css instead of using cpu consuming server side strings manipulation like .replace,
just add this style property to render multiline texts :
.multiline
{
white-space: pre-wrap;
}
then
<div class="multiline">
my
multiline
text
</div>
newlines will render like br elements, test it here https://refork.codicode.com/xaf4
In your view, you can try something like
#Html.Raw(Html.Encode(Model.MultiLineText).Replace("\n", "<br />"))
The display template is probably the best solution but there is another easy option of using an html helper if you know you're just displaying a string, e.g.:
namespace Shaul.Web.Helpers
{
public static class HtmlHelpers
{
public static IHtmlString ReplaceBreaks(this HtmlHelper helper, string str)
{
return MvcHtmlString.Create(str.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None).Aggregate((a, b) => a + "<br />" + b));
}
}
}
And then you'd use it like:
#using Shaul.Web.Helpers
#Html.ReplaceBreaks(Model.MultiLineText)
You create a display template for your data.
Here's a post detailing how to do it.
How do I create a MVC Razor template for DisplayFor()
In that template you do the actual translating of newlines into and whatever other work needs to be done for presentation.
Inspired by DisplayTemplates for common DataTypes,
I override (introduce?) a default DisplayTemplate for DataType.MultilineText, /Views/Shared/DisplayTemplates/MultilineText.cshtml containing just this line:
<span style="white-space: pre-wrap">#this.Model</span>
(Of course you could replace this style, by a css-class, or replace newlines inside the view, if you prefer that.)
I guess this template is automatically resolved, because I had no need for UIHint or any other reference or registration.
Using the DisplayTemplate instead of introducing a HtmlHelper-method has the advantage, that it trickles down to properties and views that are not explicitly defined.
E.g. DisplayFor(MyClassWithMultilineProperties) will now also correctly display MyClassWithMultilineProperties.MyMultilineTextProperty, if the property was annotated with [DataType(DataType.MultilineText)].
Try using
#Html.Raw("<p>" + Html.LabelFor(x => x.Name) + "</p>")
Here's another extension method option.
public static IHtmlString DisplayFormattedFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, string>> expression)
{
string value = Convert.ToString(ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).Model);
if (string.IsNullOrWhiteSpace(value))
{
return MvcHtmlString.Empty;
}
value = string.Join("<br/>", value.Split(new[] { Environment.NewLine }, StringSplitOptions.None).Select(HttpUtility.HtmlEncode));
return new HtmlString(value);
}
I had this problem with ASP.NET Core 6. The previous answers here did not work with a linq expression in Html.DisplayFor. Instead I was constantly getting the <br/> tag escaped out in the output HTML. Trying HtmlString helper methods suggestions did not work.
The following solution was discovered through trial and error. The InfoString had CRLF replaced with the <br/> tags as shown in the property code.
Works
#Html.Raw(#Convert.ToString(item.InfoString))
Did not work
#Html.DisplayFor(modelItem => item.InfoString)
FYI - my Info String property:
public string InfoString
{
get { return MyInfo.Replace(Environment.NewLine,"<br />"); }
}

Multiple if statements in an html attribute - MVC razor

If I wanted multiple if statements in an html attribute I might do something like this:
<input type="button" value="Bad, the title has a lot of excess spacing" title="#if(SomeModel.condOne) {
<text>this</text>
}
#if (SomeModel.CondTwo)
{
<text> is</text>
}
#if (SomeModel.CondThree)
{
<text> a title</text>
}
" />
But that creates a lot of empty spaces that need truncating. So this works:
<input type="button" value="Good, the title is condenced" title="#if(SomeModel.condOne) {<text>this</text>}#if (SomeModel.CondTwo){<text> is</text>}#if (SomeModel.CondThree){<text> a title</text>}" />
The same principle can be applied to an element with multiple classes (e.g. class="oddrow class1" -> class="evenrow class2")
But that might be hard to read if it's a long line. And visual studio has a habit of breaking that statement into multiple lines if you touch the bracket or Ctrl-K,Ctrl-D (which any next developer is likely to do).
Is there a better or more fullproof way to implement multiple attribute conditions in a line for MVC razor?
I suggest creating a small helpers method that returns the text you need.
You'd have to pass it SomeModel and inside that method check for your condition that way you'd have something nicer to look at and easier to maintain.
For example:
public static class HtmlHelpers
{
public static string FetchTitle(this HtmlHelper helper, SomeModel model)
{
//Your logic here.
}
}
You can read all about Html Helper methods here on Jon Galloway's blog.
That's where I learned how to use them.
Why not just do it as:
title="#if(SomeModel.condOne) { <text>this</text> }
#if (SomeModel.CondTwo) { <text> is</text> }
#if (SomeModel.CondThree) { <text> a title</text> }
" />
Helper makes sense if you do use this same logic alot, especially assuming the same model, but you may want to also consider a helper that uses a Func<> expression or Action<> expression. This way, it wouldn't be tied to one model.

Rewriting Html.BeginForm() in MVC 3.0 and keeping unobtrusive javascript

This is going to seem like a bit of a silly endeavor, but it's something I want to learn nonetheless.
Right now, in ASP.NET MVC 3.0, you need to use the syntax #using (Html.BeginForm()) { and then later, the } to close a form block to get the fancy new 'unobtrusive javascript', lest you want to write all of it by hand (which is fine).
For some reason (Read: *OCD*) I don't like that. I'd really rather do this..
#Html.BeginForm()
<div class="happy-css">
</div>
#Html.EndForm()
Seem stupid yet? Yeah, well to each their own. I want to understand why it is working how it is and mold it to my liking. So I thought the first place I would start digging is the MVC 3.0 source itself. So I jumped into codeplex to find the BeginForm Extension method.
( http://aspnet.codeplex.com/SourceControl/changeset/view/63452#288009 )
So now I am a little confused as to how to begin achieving my goal. Reading through the code, I discovered that they all go down to a root method (not surprising, as most extension methods seem to be hierarchical methods all reaching down into a single one to avoid redundancy).
private static MvcForm FormHelper(this HtmlHelper htmlHelper, string formAction, FormMethod method, IDictionary<string, object> htmlAttributes) {
TagBuilder tagBuilder = new TagBuilder("form");
tagBuilder.MergeAttributes(htmlAttributes);
// action is implicitly generated, so htmlAttributes take precedence.
tagBuilder.MergeAttribute("action", formAction);
// method is an explicit parameter, so it takes precedence over the htmlAttributes.
tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);
HttpResponseBase httpResponse = htmlHelper.ViewContext.HttpContext.Response;
httpResponse.Write(tagBuilder.ToString(TagRenderMode.StartTag));
return new MvcForm(htmlHelper.ViewContext.HttpContext.Response);
}
What I am not seeing here is how this method relates to the unobtrusive javascript. If I simply type out ..
<form action="/Controller/Action" method="post">
and then put in my validation like so...
#Html.ValidationSummary(false)
I do not get the unobtrusive javascript. But if I use
#using (Html.BeginForm()) { then I do. I've even examined the generated markup and I really can't find the difference.
Now then it gets strange. If I just type in ...
#Html.BeginForm() and then put all of my form code, the form works and I get the unobtrusive javascript, but I have to manually type in </form> at the end. #Html.EndForm() doesn't work. But ontop of that, I now get the text System.Web.Mvc.Html.MvcForm written to the output stream right beneath the <form action="/Controller/Action" method="post"> html.
Can someone enlighten and/or help me?
The answer to your underlying question (i.e. how to use BeginForm/EndForm syntax) is to do it in the following manner:
#{ Html.BeginForm(...); }
<div> content</div>
#{ Html.EndForm(); }
Unfortunately the Razor syntax right now is a bit more verbose when invoking helpers that write to the output (as opposed to the majority of helpers which just return an html snippet). You could probably make this easier by writing your own extension methods like so:
public static IHtmlString FormBegin(this HtmlHelper helper, ...) {
helper.BeginForm(...);
return new HtmlString("");
}
public static IHtmlString FormEnd(this HtmlHelper helper) {
helper.EndForm();
return new HtmlString("");
}
The reason it works I believe is that the BeginForm method returns an MvcForm object and not html, it writes the html directly to the page. When the using block ends it is disposed and it writes the closing end tag. That's the reason you see the text System.Web.Mvc.Html.MvcForm appear in your output. You have to put the closing tag in there manually because the MvcForm object isn't disposed.
The using syntax is like doing this:
#{ MvcForm mf = Html.BeginForm(); /* writes directly to html stream */ }
<div class="happy-css">
</div>
#{ mf.Dispose(); /* like calling mf.EndForm() */ }

Resources