Rewriting Html.BeginForm() in MVC 3.0 and keeping unobtrusive javascript - asp.net-mvc-3

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() */ }

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.

How can I add a class attribute to Html.BeginForm() in MVC3/4 preserving the minimal overload

I'm in the process of upgrading my app to Bootstrap 2.1.1 and I need to add a class attribute (class="form-horizontal") to many of the form tags. The problem is, most of the forms use the rather beautiful
#using (Html.BeginForm()) {
format which I'd rather keep. The obvious way to get the class attribute in there is to use the following overload, for example:
#using (Html.BeginForm("Register", "Account", FormMethod.Post, new { #class = "form-horizontal" })) {
But I'd rather not do that. What is the best way to add a class attribute to my form?
I've thought about using a jQuery document ready handler to .addClass("form-horizontal") to forms, but am worried that users would see the dreaded 'flash of unstyled content'.
Another approach might be to use a less-style mix-in, to add the .form-horizontal class to all form style rules but I haven't used less yet, so I'm not sure about this one.
Anyone know the best way to solve this problem?
Following StanK's advice, and the advice of some other answers on SO, I created an extension method specifically tailored to that Bootstrap form layout:
public static MvcForm BeginHorizontalForm(this HtmlHelper helper) {
return helper.BeginForm(null, null, FormMethod.Post, new { #class = "form-horizontal" });
}
which allows me to use
#using (Html.BeginHorizontalForm()) {...
which is nice and elegant.
What about writing a custom helper method which calls the overload required, passing in 'form-horizontal'?
Then, you views would use #using (Html.BootstrapBeginForm()) {, leaving you views nice and tidy.

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.

Lost: Getting a fully rendered view via Ajax in ASP.NET MVC3 / jQuery

All,
I am very new to MVC3 / jQuery combo and have been reading tutorials. While I kind of get the concept, razor syntax etc. I'm still a bit confused on how to implement a basic concept that I'm trying to.
I have a textarea and when someone enters some text into it and hits enter, I want to trigger a ajax call to the server with the content of the text area, and get back a fully formed HTML blurb that I can put in a div. Now as I understand in MVC3 this would be a view, so in a sense I'm rendering a view on the server and sending it back so I can put it in HTML.
Is this possible? Any examples that I can look up to see how this done? I know how to capture keystrokes, get the value etc., it's this partial rendering of a fully formed HTML via ajax that I'm struggling to understand.
Thanks,
You can do with jQuery. This is how it works. you listen for the keydown event of the text area and when there is a keydown, check what key it is.if it is enter key, then make a jQuery ajax post call to a server page (action method in your controller with the data).Save the data there to your tables and return the markup of what you want and return that. in your script load it to your relevant div.
HTML
//Load jQuery library in your page
<textarea id="txtComment" > </textarea>
<div id="divComment"></div>
Javascript
$(function(){
$("#txtComment").keydown(function (event) {
if (event.keyCode == 10 || event.keyCode == 13) {
var comment=$("#txtComment").val();
comment=encodeURIComponent(comment);
$.post("yourcontroller/actionmethod?data="+comment,function(response){
$("#divComment").html(response);
});
}
});
});
and your controller action method
public ActionResult actionmethod(string data)
{
//Do some sanitization on the data before saving.
// Call your method to save the data to your tables.
CommentViewModel objCommentVM=new CommentViewModel();
objCommentVM.Comment=data;
return View("PartialCommentView",objCommentVM);
}
You should have a ViewMolde class called "CommentViewModel" like this
public class CommentViewModel
{
public string Comment{ set; get; }
}
and you should have a View called PartialCommentView which is strongly typed to CommentViewModel
#model FlashRack.ViewModel.RackViewModel
#{
Layout = null;
}
<div>
#Model.Comment
</div>
If you are simply returning a string, instead of returning a View, you can simply return the string using Return Content("your string here") method as well. But i prefer returning the ViewModel via View because it is more scalable and clean approach to me.
Your action method will return the markup you have in your PartialCommentView with the data.
Keep in mind that you have to take care of the special characters and escaping them properly.

MVC 3 Razor, Helpers with custom markup/section

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.

Resources