开发者

MVC2 Client-Side Validation for injected Ajax content

I am making an Ajax ca开发者_开发百科ll and adding content to a form inside a MVC2 app. I need to update the Client Validation Metadata with the validation for my new content.

 <script type="text/javascript"> 
//<![CDATA[
if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
window.mvcClientValidationMetadata.push({"Fields":[{"
...
</script>

Is there a way to generate this metadata for a partial view ?

Thanks in advance.


I was banging my head against a wall for a few days on this too and was going to go down the route of removing the form tag, but have just got it working in a slightly less hacky way if you are still interested. My scenario was similar in that I have a form with a collection of elements to validate initially, but users can dynamically add new rows via ajax.

I'll break it down so hopefully it'll be easier to see what is going on. Looking at the MVC source code, the form and validation works roughly as so:

Html.BeginForm() outputs the opening form tag then creates and returns a new instance of MvcForm, which doesn't outwardly do much except make the scope of the form easier to manage for you. It does however create a new FormContext and stores this within ViewContext.FormContext. It is this FormContext that tracks the client validation.

The last thing Html.BeginForm() does is set the FormId property of the new FormContext, using the id of the form tag. This is required so the client script can match up forms and validation rules.

Html.EndForm() disposes the MvcForm. This Dispose method outputs the form closing tag and then calls ViewContext.OutputClientValidation() which is resposible for outputting the javascript. Lastly it removes the current FormContext and sets it back to the parent FormContext or null if there isn't one.

So to not output the form tag we somehow need to take some of the FormContext management out of the MvcForm constructor/destructor.

So within my Partial View I did the following:

At the top I check if the ViewContext.FormContext has a value. If so we we are in the initial load so no need to mess around. If not, it is an ajax call, so I enable client validation, create a new MvcForm directly (not with BeginForm) - this causes a FormContext to be created - and set the FormContext.FormId to the same as my parent page

At the end of the view, I check if I have a form instance and if so, call ViewContext.OutputClientValidation() and reset the ViewContext.FormContext to null. I do not Dispose() the MvcForm as this would output the closing tag and MvcForm does not contain disposable objects.

The skeleton of the view looks as so:

<%
MvcForm dummyForm = null;
if (this.ViewContext.FormContext == null)
{
    Html.EnableClientValidation();
    dummyForm = new MvcForm(this.ViewContext);
    this.ViewContext.FormContext.FormId = "mainform";
}
%>

// standard partial view markup goes here

<%
if (dummyForm != null)
{
    this.ViewContext.OutputClientValidation();
    this.ViewContext.FormContext = null;
}
%>

You could quite easily wrap this up into an extension method

Phil


Finally got it to work.

The answer is simple: don't waist time with MicrosoftMvcValidation.js. It is generated with Script# which makes it difficult to extend.

Switch to xVal and jQuery Validation. It doesn't need a form to generate the client validation metadata. Also in order to load validation for a AJAX request all you have to do is to call the following after you have the new Html:

lForm.find("#placeholder").empty();                     
lForm.valid();
lForm.find("#placeholder").html(responseHtml);   

That does it. First you remove the old content. Than re-run validation to get rid of potentially obsolete validation errors. Than add the new content. Works like a cham.

Also jQuery Validation makes it really easy to enable or disable validation for a certain field (conditional validation).


I have the same problem and resolve using the Future files, and in MicrosoftMvcJQueryValidation.js I change the and of file, this:

$(document).ready(function () {
    var allFormOptions = window.mvcClientValidationMetadata;
    if (allFormOptions) {
        while (allFormOptions.length > 0) {
            var thisFormOptions = allFormOptions.pop();
            __MVC_EnableClientValidation(thisFormOptions);
        }
    }
});

for:

function chargeValidation() {
    var allFormOptions = window.mvcClientValidationMetadata;
    if (allFormOptions) {
        while (allFormOptions.length > 0) {
            var thisFormOptions = allFormOptions.pop();
            __MVC_EnableClientValidation(thisFormOptions);
        }
    }
}

and in content after close form using I call the 'chargeValidation()', this resolve for me the problem I have using $.get(action) containing a form validation.

I hope to help you!


Finally found it. After content is loaded in dynamically you will need to register the new form.

Since I am using Facebox, I added it to the facebox code, however you can add it wherever you need, or in a callback if your modal or whatever you are loading into has an afterLoaded event.

I wrapped them in a try/catch just in case i ever use facebox without the validation stuff.

Just run these two lines AFTER your content has been loaded:

try {
        Sys.Application.remove_load(arguments.callee);
        Sys.Mvc.FormContext._Application_Load();
} catch (err) {/* MVC Clientside framework is likely not loaded*/ }


I made some progress but I am not quite happy.

Problem #1: The client validation metadata is not generated unless you have a Html.BeginForm() in your partial. Which in my case is false because I do not update the entire form, I update portions of it.

Solution for Problem #1: Add a form in the partial view, let MVC generate the client validation MetaData and remove the form tags with a action filter. Let's call this Hack #1.

public class RemoveFormFilterAttribute : ActionFilterAttribute
{       

    private static readonly MethodInfo SwitchWriterMethod = typeof(HttpResponse).GetMethod("SwitchWriter", BindingFlags.Instance | BindingFlags.NonPublic);

    private TextWriter _OriginalWriter;

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        _OriginalWriter = (TextWriter)SwitchWriterMethod.Invoke(HttpContext.Current.Response, new object[] {new HtmlTextWriter(new StringWriter())});
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        if (_OriginalWriter != null)
        {
            HtmlTextWriter lTextWriter =(HtmlTextWriter) SwitchWriterMethod.Invoke(HttpContext.Current.Response, new object[] {_OriginalWriter});

            string lOriginalHTML = lTextWriter.InnerWriter.ToString();

            string lNewHTML =  RemoveFormTags(lOriginalHTML);

            filterContext.HttpContext.Response.Write(lNewHTML);
        }
    }

Problem #2: The initial client validation metadata for the page is gone by the time I have the metaData for the new content.

Solution for Problem #2: Store the initial metadata (hard copy) and update it with the new fieds, than call the methods you mentioned to let MVC know new stuff arrived. Let's call this Hack #2.

<script type="text/javascript">

    var pageMvcClientValidationMetadata;

    $(document).ready(function() {

        $("input[name='PaymentTypeName']").change(PaymentTypeChanged);

        //create a back-up of the ValidationMetadata
        pageMvcClientValidationMetadata = JSON.parse(JSON.stringify(window.mvcClientValidationMetadata));
    });

    function PaymentTypeChanged() {

        var selectedPaymentType = $("input[name='PaymentTypeName']:checked").val();

        $.ajax(
            {
                url: 'PersonalData/GetPaymentTypeHtml?&paymentType=' + selectedPaymentType,
                type: "GET",
                cache: false,
                success: GetPaymentTypeHtml_Success
            });
    }

    function GetPaymentTypeHtml_Success(result) {

        $('#divPaymentTypeDetails').html(result);

        UpdateValidationMetaData();
    }

    function UpdateValidationMetaData() {

        //update the ValidationMetadata
        for (i = 0; i < window.mvcClientValidationMetadata[0].Fields.length; i++) {
            pageMvcClientValidationMetadata[0].Fields.push(window.mvcClientValidationMetadata[0].Fields[i]);
        }

        //restore the ValidationMetadata
        window.mvcClientValidationMetadata = JSON.parse(JSON.stringify(pageMvcClientValidationMetadata));

        //Notify the Validation Framework that new Metadata exists
        Sys.Application.remove_load(arguments.callee);          
        Sys.Mvc.FormContext._Application_Load();            
    }

Now. Any improvements would be appreciated.

Hack #1: How can I generate the client validation metadata without having an actual form ?

HAck #2: How can I appent to the page validation metadata ?

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜