I currently have a div
container for all of the input fields in my form, similar to:
<div class="ux-single-field ui-widget-content ui-corner-all">
@Html.LabelFor(m => m开发者_JAVA百科.Name)
@Html.TextBoxFor(m => m.Name)
</div>
I would like to know how could I encapsulate this using a templated razor delegate (or any other trick), so just like we use:
@using (Html.BeginForm()) {
}
I could simply wrap my elements like:
@using (Html.ContentField()) {
@Html.LabelFor(m => m.Name)
@Html.TextBoxFor(m => m.Name)
}
Using the Razor View Engine, here is what works:
namespace MyProject.Web.Helpers.Extensions
{
public static class LayoutExtensions
{
public static ContentField BeginContentField(this HtmlHelper htmlHelper)
{
return FormHelper(htmlHelper, new RouteValueDictionary());
}
public static ContentField BeginContentField(this HtmlHelper htmlHelper, RouteValueDictionary htmlAttributes)
{
return FormHelper(htmlHelper, htmlAttributes);
}
public static void EndContentField(this HtmlHelper htmlHelper)
{
htmlHelper.ViewContext.Writer.Write("</div>");
}
private static ContentField FormHelper(this HtmlHelper htmlHelper, IDictionary<string, object> htmlAttributes)
{
TagBuilder tagBuilder = new TagBuilder("div");
tagBuilder.MergeAttributes(htmlAttributes);
tagBuilder.MergeAttribute("class", "ux-single-field ui-widget-content ui-corner-all");
htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag));
return new ContentField(htmlHelper.ViewContext.Writer);
}
}
public class ContentField : IDisposable
{
private bool _disposed;
private readonly TextWriter _writer;
public ContentField(TextWriter writer)
{
if (writer == null)
throw new ArgumentNullException("writer");
_writer = writer;
}
[SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
public void Dispose()
{
Dispose(true /* disposing */);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
_writer.Write("</div>");
}
}
public void EndForm()
{
Dispose(true);
}
}
}
FYI: Using the old ASPX engine, here's how to do it.
The accepted answer was very helpful. I changed and updated it a bit for my project, and I feel that this version will be slightly more clear for people who just want to jump in and get things done.
Changes include:
- Upgraded to use anonymous types instead of IDictionary, since that seems to be the standard now.
- Removed the Begin/End... syntax, since I will only ever use this with the using() syntax and for that purpose I feel this is more clear.
- Tweaked naming for clarity.
- Added a headerText argument, which my panel uses to create a seperate div header. This is easily removed if you don't need/want it.
- Refactored a few methods out.
If you happen to be looking for a panel helper for KendoUI -- well, that happens to be what this is. I have a class called panel that this references, and that just adds margin and width to the KendoUI tags.
using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Web.Mvc;
namespace MyProject.Web.HtmlHelpers.Extensions { public static class LayoutExtensions { public static StyledPanel Panel(this HtmlHelper htmlHelper, object htmlAttributes = null, string headerText = null) { return GetStyledPanel(htmlHelper, headerText, htmlAttributes); }
private static StyledPanel GetStyledPanel(this HtmlHelper htmlHelper, string headerText, object htmlAttributes) { if (!string.IsNullOrWhiteSpace(headerText)) RenderHeading(htmlHelper, headerText); RenderDiv(htmlHelper, htmlAttributes); return new StyledPanel(htmlHelper.ViewContext.Writer); } private static void RenderHeading(HtmlHelper htmlHelper, string headerText) { TagBuilder tagBuilder = new TagBuilder("div"); tagBuilder.Attributes.Add("class", "panelHead"); tagBuilder.SetInnerText(headerText); htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.Normal)); } private static void RenderDiv(HtmlHelper htmlHelper, object htmlAttributes) { TagBuilder Tag = new TagBuilder("div"); Tag.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); Tag.MergeAttribute("class", "panel k-block k-shadow"); htmlHelper.ViewContext.Writer.Write(Tag.ToString(TagRenderMode.StartTag)); } } public class StyledPanel : IDisposable { private bool m_Disposed; private readonly TextWriter m_Writer; public StyledPanel(TextWriter writer) { if (writer == null) throw new ArgumentNullException("Writer was null. This should never happen."); m_Writer = writer; } [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")] public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!m_Disposed) { m_Disposed = true; m_Writer.Write("</div>"); } } public void EndForm() { Dispose(true); } }
}
Usage could be like this:
@using (Html.Panel(headerText: "My Header",
htmlAttributes: new { style = "width: 800px;" }))
{
<table>
<tr>
<td class="label">First Name:</td>
<td class="content"><input name="thing" class="k-textbox" /></td>
<td class="label">Last Name:</td>
<td class="content"><input name="thing" class="k-textbox" /></td>
</tr>
</table>
}
Or just:
@using (Html.Panel())
{
<table>
<tr>
<td class="label">First Name:</td>
<td class="content"><input name="thing" class="k-textbox" /></td>
<td class="label">Last Name:</td>
<td class="content"><input name="thing" class="k-textbox" /></td>
</tr>
</table>
}
Let's take a look (or guess) what does Html.BeginForm()
do. From the "render point of view", it generally just renders start form tag into the html output. it's disposable because in this case it knows when the inner html content for form has finished rendering and it can render end </form>
tag in its Dispose()
method. With all these, you get - first, open form
tag is rendered, than custom html content that you wish, and after that comes the end tag. Result - you get full html form in the output.
<form>
...contents(Result of Html.TextBoxFor, etc. helpers)
</form>
I think your situation would be best solved like it's in case of form. At the moment i don't have much time to write the full code, but if you take a look at FormExtensions.BeginForm
via taking look at source code (Thanks @druttka) reflector(if you've got old version or purchased licence) or http://wiki.sharpdevelop.net/ilspy.ashx and explanations above, you can get the great point where from to start. Remove unneccessary code from the BeginForm method, create your MvcContentField : IDisposable class instead of MvcForm, change the Dispose()
on it to render the end div tag and you'll get exactly what you need.
精彩评论