views:

192

answers:

2

I find myself pasting this code over and over on many views that deal with forms. Is there a simple approach to refactor the following markup from a view in MVC2? The only changing part is the route for the cancel link (LocalizedSaveButton and LocalizedCancelLink are helper methods I created). I tried extracting it to a PartialView but I lose the BeginForm functionality. Any suggestions?

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<div class="span-14">
    <% Html.EnableClientValidation(); %>
    <%using (Html.BeginForm())
      {%>
    <fieldset>
        <legend>Edit Profile</legend>
        <%=Html.AntiForgeryToken() %>
        <%=Html.EditorForModel() %>
        <div class="span-6 prepend-3">
            <%=Html.LocalizedSaveButton() %>
            <%=Html.LocalizedCancelLink(RoutingHelper.HomeDefault()) %>                
        </div>
    </fieldset>
    <%}%>
</div>

+1  A: 

You could wrap your code in an Html-Extension like the BeginForm. From your code call the BeginForm on the correct place.

You should return an object that implements IDisposable. In the dispose you call the Dispose of the stored result to BeginForm.

You end up with:

<% using (Html.MyBeginForm()) { %>
    <%=Html.LocalizedSaveButton() %> 
    <%=Html.LocalizedCancelLink(RoutingHelper.HomeDefault()) %>   
<% } %>

The trick is not to return a string or MvcHtmlString, but directly write the output using:

htmlHelper.ViewContext.Writer.Write(....);

It would do something like:

public class MyForm : IDisposable {
    private MvcForm _form;
    private ViewContext _ctx; 

    public MyForm(HtmlHelper html, /* other params */) {
       _form = html.BeginForm();
       _ctx = html.ViewContext;
    }

    public Dispose() {
        _form.Dispose();
        _ctx.Writer.Write("html part 3 => closing tags");
    }
}

and the extension:

public static MyForm MyBeginForm(this HtmlHelper html /* other params */) {
    html.ViewContext.Writer.Write("html part 1");
    var result = new MyForm(html);
    html.ViewContext.Writer.Write("html part 2");
    return result;
}

Disclaimer: This is untested code.

GvS
Awesome! Let me give it a try.
robDean
A: 

Here is what I came up with: It mostly works but I haven't been able to get the Html.EnableClientValidation() to cooperate with the rendered form.

namespace System.Web.Mvc
{
        public class MyForm : IDisposable
        {
            private bool _disposed;
            private readonly HttpResponseBase _httpResponse;

        public MyForm(HttpResponseBase httpResponse)
        {
            if (httpResponse == null)
            {
                throw new ArgumentNullException("httpResponse");
            }
            _httpResponse = httpResponse;
        }

        public void Dispose()
        {
            Dispose(true /* disposing */);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                _disposed = true;
                _httpResponse.Write("</form>");
            }
        }

        public void EndForm()
        {
            Dispose(true);
        }
    }
}

public static class MyFormExtensions
{
    public static MyForm FormHelper(
        this HtmlHelper htmlHelper, 
        string formAction, 
        FormMethod method, 
        IDictionary<string, object> htmlAttributes, 
        string formLegendTitle)
    {
        TagBuilder tagBuilder = new TagBuilder("form");

        tagBuilder.MergeAttributes(htmlAttributes);

        tagBuilder.MergeAttribute("action", formAction);
        tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);
        HttpResponseBase httpResponse = htmlHelper.ViewContext.HttpContext.Response;
        httpResponse.Write(tagBuilder.ToString(TagRenderMode.StartTag));

        return new MyForm(httpResponse);
    }



    public static MyForm MyBeginForm(this HtmlHelper html, string formLegendTitle) {

        string formAction = html.ViewContext.HttpContext.Request.RawUrl;
        var result = FormHelper(html,formAction, FormMethod.Post, null, formLegendTitle );

            html.ViewContext.Writer.Write("<fieldset>");
            html.ViewContext.Writer.Write(String.Format("<legend>{0}</legend>", formLegendTitle));
            html.ViewContext.Writer.Write("<div class=\"span-14\">");
            html.ViewContext.Writer.Write(html.AntiForgeryToken());
            html.ViewContext.Writer.Write(html.EditorForModel());
            html.ViewContext.Writer.Write("<div class=\"span-6 prepend-3 buttons\">");
            html.ViewContext.Writer.Write(html.LocalizedSaveButton());
            html.ViewContext.Writer.Write("</div>");
            html.ViewContext.Writer.Write("</div>");
            html.ViewContext.Writer.Write("</fieldset>");

        return result;

    }

    public static void EndForm(this HtmlHelper htmlHelper)
    {
        HttpResponseBase httpResponse = htmlHelper.ViewContext.HttpContext.Response;
        httpResponse.Write("</form>");
    }
}
robDean