ASP.NET MVC 3: Generate unobtrusive validation when BeginForm is on the layout
Asked Answered
S

5

7

I just realized that when I place a form tag on my layout page, surrounding the RenderBody section, the unobtrusive validation is not being generated. Something like this:

@using (Html.BeginForm())
{
    <input type="submit" value="save" />

    <div>
        @RenderBody()
    </div>
}

As you might have guessed I want to generate buttons over my content. Is this the correct unobtrusive's behavior?

BTW, If I place the form inside a particular page, everything works like a charm: the data-val* attributes are well generated.

I'll appreciate your valuable help.

best regards

Rodrigo

Statutable answered 14/2, 2011 at 21:6 Comment(0)
S
1

Thanks for your help, I tried it but I found a solution not as "grotesque" (as you said) as you suggested :D

I simply put a BeginForm method inside my page and also a BeginForm method on the layout:

@* On the layout page *@
@using (Html.BeginForm())
{
    <input type="submit" value="save" />

    <div>
        @RenderBody()
    </div>
}


@* On the content page *@
@using(Html.BeginForm())
{
  @* Content *@
}

so, at the end I have two BeginForm methods: ASP.NET MVC engine is using the one located on the layout page, so the data-val* attributes are being rendered correctly and the form is placed just where I wanted so any submit button on the layout can submit my particular page with the validations rendered

It works pretty well

Thanks a lot

regards, Rodrigo

Statutable answered 15/2, 2011 at 14:17 Comment(1)
If this puts extra <form> tags in a page (as noted by @AidanBoyle), it could break the page functionality. Forms should not be nested according to the HTML standard.Abney
G
12

You could apply a grotesque hack inside your view:

@{
    var originalContext = ViewContext.FormContext;
    ViewContext.FormContext = new FormContext();
}

<!-- This will generate proper HTML5 data-* validation attributes -->
@Html.TextBoxFor(x => x.Prop1)
@Html.ValidationMessageFor(x => x.Prop1)

@Html.TextBoxFor(x => x.Prop2)
@Html.ValidationMessageFor(x => x.Prop2)

@{
    ViewContext.FormContext = originalContext;
}
Geanticlinal answered 14/2, 2011 at 22:17 Comment(2)
This saved me quite a bit of work, it is really stupid the framework cannot handle this to begin with. I mean building dynamic form elements from ajax is a pretty common practice, and if you can pass in a "mock" new form context just to get an editor template to fire, there is no reason why they couldn't do this at the top of the function that processes the editor templates.Thermoelectricity
I had a similar issue with generating the inputs inside a template tag. Had to apply the same hack in order to make the unobtrusive validation attributes to get generated.Gyroplane
V
4

While putting @using (Html.BeginForm()) into the content page fixes the validation problem it also puts an extra set of <form> tags into the output. I created a small extension that fixes the problem without writing anything to the output.

Use it as @using (Html.BeginSubForm())

public static class FormExtensions
{
    public static MvcSubForm BeginSubForm(this HtmlHelper html)
    {
        return new MvcSubForm(html.ViewContext);
    }
}


public sealed class MvcSubForm : IDisposable
{
    private readonly ViewContext _viewContext;
    private readonly FormContext _originalFormContext;

    public MvcSubForm(ViewContext viewContext)
    {
        _viewContext = viewContext;
        _originalFormContext = viewContext.FormContext;

        viewContext.FormContext = new FormContext();
    }


    public void Dispose()
    {
        if (_viewContext != null)
        {
            _viewContext.FormContext = _originalFormContext;
        }
    }
}
Veasey answered 5/1, 2012 at 21:36 Comment(0)
S
1

Thanks for your help, I tried it but I found a solution not as "grotesque" (as you said) as you suggested :D

I simply put a BeginForm method inside my page and also a BeginForm method on the layout:

@* On the layout page *@
@using (Html.BeginForm())
{
    <input type="submit" value="save" />

    <div>
        @RenderBody()
    </div>
}


@* On the content page *@
@using(Html.BeginForm())
{
  @* Content *@
}

so, at the end I have two BeginForm methods: ASP.NET MVC engine is using the one located on the layout page, so the data-val* attributes are being rendered correctly and the form is placed just where I wanted so any submit button on the layout can submit my particular page with the validations rendered

It works pretty well

Thanks a lot

regards, Rodrigo

Statutable answered 15/2, 2011 at 14:17 Comment(1)
If this puts extra <form> tags in a page (as noted by @AidanBoyle), it could break the page functionality. Forms should not be nested according to the HTML standard.Abney
E
1

I've just run into the same problem, but possibly a better solution based on Darin Dimitrov's answer.

The trick is to create a page base type, based on the WebViewPage<T> class, the default base class for views and do the FormContext swap there.

abstract public class FormFixWebViewPage : FormFixWebViewPage<object>
{
}

abstract public class FormFixWebViewPage<T> : WebViewPage<T>
{
    override public void Write(System.Web.WebPages.HelperResult result)
    {
        var originalFormContext = ViewContext.FormContext;
        ViewContext.FormContext = new FormContext();

        base.Write(result);

        ViewContext.FormContext = originalFormContext;
    }
}

And then in the Web.config file under the ~/Views/ folder, alter the pageBaseType attribute under pages element, which can be found in the system.web.webPages.razor section:

<system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="<YOUR-NAMESPACE>.FormFixWebViewPage">
    <!--pages pageBaseType="System.Web.Mvc.WebViewPage"-->
        <namespaces>
            <add namespace="System.Web.Mvc" />
            <add namespace="System.Web.Mvc.Ajax" />
            <add namespace="System.Web.Mvc.Html" />
            <add namespace="System.Web.Helpers" />
            <add namespace="System.Web.Routing" />
        </namespaces>
    </pages>
</system.web.webPages.razor>
Execrate answered 11/5, 2011 at 8:28 Comment(1)
Hi Kieron, I'm afraid Darin Dimitrov's solution could not help me, because in the order of execution, there comes the specific page first, then comes the layout page, so FormContext object is null the first time, and I can't generate my validators. Thanks for your comment because I implemented your suggestion and I could modify the output replacing the inner form for a empty string. I must say that placing a BeginForm on the particular page generates an additional form element which shows a blank space on IE.Statutable
U
1

Just add below code at top of the child view file...

@{
   Layout = "~/Views/Shared/_Layout.cshtml";
   this.ViewContext.FormContext = new FormContext();
}

its working fine for me.

i hope this will help you....

Ulric answered 24/5, 2012 at 6:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.