Trouble with Validating form inputs (Client Side)

Topics: Troubleshooting, Writing modules
Nov 4, 2011 at 4:18 PM
Edited Nov 4, 2011 at 4:30 PM

I get that client side validation has not been worked out with 1.3.

However, it appears Orchard's own validation system is simply messing my head up with some forms I am creating in a custom module.  I have given up on validation because of the headache it has caused trying to implement (me being a non-developer).  But now it seems Orchard's validation system is taking over in an unexpected way: if I click through on "[Required]" inputs in my wizard form (where in the past I would have expected the client-side validation to fire) and I get to the end of a wizard  Orchard takes over and (properly) gives me the validation summary error for FirstName and Email (which I purposefully left blank).  When I fill FirstName and Email in and click through the wizard to the submit page it goes BACK to the FirstName and Email (which I populated) and shows it blank with the validation summary error that those fields are required.

I have given up. I am not sure if I am doing something wrong.

I am using an MVC Wizard, a controller, and originally had enable client-side/unobtrusive in my module's web.config.

Any advice would be greatly appreciated.

Nov 4, 2011 at 4:30 PM

What is an "MVC Wizard"? Is this an official part of MVC or a third-party addon? I'd guess the problem is there in the way it's implemented, it may be doing complex things that aren't designed to be compatible with Orchard; but without seeing any code I can't offer any further advice.

Nov 4, 2011 at 5:03 PM
Edited Nov 4, 2011 at 5:19 PM

I was using a wizard implemented here: http://afana.me/post/create-wizard-in-aspnet-mvc-3.aspx

I had previously used this wizard without any problems in an MVC 3 app, outside of Orchard. I am suspecting you are correct about it not being compatible with Orchard, but I am not sure where (if that's the case). For ease I am posting the javascript and wizard code here from that link:

This is the Index.cshtml

@model MyWizard.DomainModel.User

@{
    ViewBag.Title = "Wizard";
}

<h2>Create an Account Wizard</h2>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

<script type="text/javascript">
    $(function ()
    {
        $(".wizard-step:first").fadeIn(); // show first step
        // attach backStep button handler
        // hide on first step
        $("#back-step").hide().click(function ()
        {
            var $step = $(".wizard-step:visible"); // get current step
            if ($step.prev().hasClass("wizard-step")) { // is there any previous step?
                $step.hide().prev().fadeIn();  // show it and hide current step

                // disable backstep button?
                if (!$step.prev().prev().hasClass("wizard-step")) {
                    $("#back-step").hide();
                }
            }
        });
        // attach nextStep button handler       
        $("#next-step").click(function ()
        {
            var $step = $(".wizard-step:visible"); // get current step
            var validator = $("form").validate(); // obtain validator
            var anyError = false;
            $step.find("input").each(function ()
            {
                if (!validator.element(this)) { // validate every input element inside this step
                    anyError = true;
                }
            });
            if (anyError)
                return false; // exit if any error found
            if ($step.next().hasClass("confirm")) { // is it confirmation?
                // show confirmation asynchronously
                $.post("/wizard/confirm", $("form").serialize(), function (r)
                {
                    // inject response in confirmation step
                    $(".wizard-step.confirm").html(r);
                });
            }
            if ($step.next().hasClass("wizard-step")) { // is there any next step?
                $step.hide().next().fadeIn();  // show it and hide current step
                $("#back-step").show();   // recall to show backStep button
            }
             else { // this is last step, submit form
                $("form").submit();
            }
        });
    });
</script>
<style type="text/css">
.wizard-step
{
	display: none;
}
.display-field
{
	font-weight: bold;
}
</style>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
        <fieldset>
        <div class="wizard-step">
            <h3>
                Step 1: Username and email address.</h3>
            <div class="editor-label">
                @Html.LabelFor(model => model.UserName)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.UserName)
                @Html.ValidationMessageFor(model => model.UserName)
            </div>
            <div class="editor-label">
                @Html.LabelFor(model => model.Email)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.Email)
                @Html.ValidationMessageFor(model => model.Email)
            </div>
        </div>
        <div class="wizard-step" >
            <h3>
                Step 2: First name and last name.</h3>
            <div class="editor-label">
                @Html.LabelFor(model => model.FirstName)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.FirstName)
                @Html.ValidationMessageFor(model => model.FirstName)
            </div>
            <div class="editor-label">
                @Html.LabelFor(model => model.LastName)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.LastName)
                @Html.ValidationMessageFor(model => model.LastName)
            </div>
        </div>
        <div class="wizard-step" >
            <h3>
                Step 3: Age and biography.</h3>
            <div class="editor-label">
                @Html.LabelFor(model => model.Age)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.Age)
                @Html.ValidationMessageFor(model => model.Age)
            </div>
            <div class="editor-label">
                @Html.LabelFor(model => model.Biography)
            </div>
            <div class="editor-field">
                @Html.TextAreaFor(model => model.Biography)
                @Html.ValidationMessageFor(model => model.Biography)
            </div>
        </div>
        <div class="wizard-step confirm">
        </div>
        <p>
            <input type="button" id="back-step" name="back-step" value="< Back" />
            <input type="button" id="next-step" name="next-step" value="Next >" />            
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

This is the WizardController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MyWizard.DomainModel;

namespace MyMvcWizard.Controllers
{
    public class WizardController : Controller
    {
        //
        // GET: /Wizard/
        [HttpGet]
        public ActionResult Index()
        {
            return View();
        }
        //
        // POST: /Wizard/
        [HttpPost]
        public ActionResult Index(User Person)
        {
            if (ModelState.IsValid)
                return View("Complete", Person);

            return View();
        }
        //
        // POST: /Wizard/Confirm
        [HttpPost]
        public ActionResult Confirm(User Person)
        {           
            return PartialView(Person);            
        }
    }
}

This is the Confirm.cshtml referenced in the Controller

@model MyWizard.DomainModel.User

@if (!ViewContext.ViewData.ModelState.IsValid)
{
    <h2>The following errors were found:</h2>
    @Html.ValidationSummary()
}
else
{
    <h2>
    Confirm</h2>
<h3>
    Please confirm that your information is correct:</h3>
    <fieldset>
        <div class="display-label">
            UserName</div>
        <div class="display-field">@Model.UserName</div>
        <div class="display-label">
            FirstName</div>
        <div class="display-field">@Model.FirstName</div>
        <div class="display-label">
            LastName</div>
        <div class="display-field">@Model.LastName</div>
        <div class="display-label">
            Age</div>
        <div class="display-field">@Model.Age</div>
        <div class="display-label">
            Email</div>
        <div class="display-field">@Model.Email</div>
        <div class="display-label">
            Biography</div>
        <div class="display-field">@Model.Biography</div>
    </fieldset>
} 

In the web.config of the module (same as web.config in the above sample), I have

    <appSettings>
        <add key="ClientValidationEnabled" value="true" />
        <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    </appSettings>

Note: If I put the above <appSettings> in the web.config under the Orchard.Web root, then I get REALLY weird behavior.  All of the data with [Required] attributes in the model do not fire, however, ALL of the data without [Required] attributes do fire the validation errors.

For my purposes, I've just changed some of the html above to suit my purposes, so nothing that would break that logic.

FURTHER EDIT TO OP: None of the other [Required] inputs will fire validation errors if left blank or not selected (in the case of Radio Buttons).

If I knew of an alternative I'd use it if it worked. :)

Thank you in advance for any advice/direction.

Coordinator
Nov 4, 2011 at 6:36 PM

Have you included all the necessary javascript libraries ? Orchard won't do it for you.

Also the Orchard views might not use the Html.EditorFor syntax and thus might not render the appropriate attributes for this feature.

Nov 4, 2011 at 8:50 PM

Not sure which are necessary other than the two that were included at the top of the Index.cshtml file, except that in my module I have it thusly:

@{
    Script.Include("jquery.validate.min.js").AtFoot();
    Script.Include("jquery.validate.unobtrusive.min.js").AtFoot();
}

This appears to work as when I use the Google Chrome Developer Tools it's listed as a downloaded resource and shows at the end of the html. I did not add them through the Orchard.Jquery module, if that's what you meant.  I will try that to see if it helps.

I will also try your suggestion regarding Html.EditorFor and see if that helps.

Thanks for the response.

Nov 4, 2011 at 10:18 PM

Is the version of jquery.validate you're using targetting at the correct version of jQuery? You should also put Script.Require("jQuery") above those lines, to make sure it's even there (although usually it will be). Are you seeing any javascript errors at all?

Nov 9, 2011 at 10:30 PM
Edited Nov 9, 2011 at 10:36 PM

I've tried every variation and then started from scratch just to see if I missed something. Nothing has worked. This was the first wizard I was using, and since I have tried one other wizard (for which you were helpful on another topic). I am now on my third wizard and am experiencing weird behavior. Bear with me if you can.

Here is my new scenario. I tired using the Nuget package MVCWizard.Wizard. The author was kind enough to send me a sample project. It works fine standalone. Instead of jQuery it uses Partials for each "Step" in the Wizard, so it's several different .cshtml files wired up.

When I try to integrate it with Orchard it loads up fine, but depending on what is done by user input, the weird behavior takes three (3) routes (this is a three (3) "Step" Wizard, each with two textboxes, for a total of six):

1) Enter all info on all three steps, clicking "next", "next", "finish" > see a confirmation page with what I entered. However, if I click a link to start "anew" or if I simply restart WebMatrix and the site, I can enter all three steps again and in the end, I see some of the data from the first instance I entered information. For example, the first time I simply enter an "a" each time. The second time I enter "b" in all the textboxes, and the confirm shows me four "a"'s and two "b"'s. I think its the way the wiring is done with Orchard. NOTE: This behavior does not occur in the standalone (entering "a" once, starting over and entering "b" shows proper confirmation and fires off validations correctly). Below is the Controller code.

2) I do not enter any information on the first step, but instead click "next", and it fires a ValidationSummary error, but only shows the error for the first textbox. If I enter the information required, I can get to the next steps, but then when I click "Finish", it goes back to the first step, clears all the inputs and throws the error for the one textbox (again, both textboxes are required).

3) Pretty much the same as #2 above, but sometimes when I am on the second step going to the third, instead it goes back to the first and shows that error for the one textbox.

I am really stumped. I am going to try to copy some of the logic from other modules to see if i can get validation working.

I know Orchard handles things a bit differently, but I was overly hopeful that if it was MVC3 out of Orchard and working that for the most part I'd just be able to plug it in and make some obvious/minor changes to get it to work.

Here is the ExampleWizardController.cs:

    [Themed]
    public class ExampleWizardController : WizardController<Employ>
    {
        public ExampleWizardController() : base()
        {
            this.WizardKeyName = "myWizard";
            this.EnableSimpleMerge = true;
        }

        public ActionResult Step1(Employ e)
        {
            return PartialView(Wizard().WizardModel);
        }

        public ActionResult Step2(Employ e)
        {
            return PartialView(Wizard().WizardModel);
        }

        public ActionResult Step3(Employ e)
        {
            // change the city value to uppercase
            if (!string.IsNullOrEmpty(e.City)) Wizard().WizardModel.City = e.City.ToUpper();
            return PartialView(Wizard().WizardModel);
        }

        public ActionResult ConfirmWizard(Employ e)
        {
            if (Wizard().IsConfermed)
            {
                // TODO Execute persistence job
                return View(Wizard().WizardModel);
                
            }
            else 
            { 
                // redirect to another page
                return RedirectToAction("ActionStep");
            }
        }

        override public bool ValidateWizardModel(Employ model)
        {
            if ("1".Equals(Wizard().CurrentStep.StepName))
            {
                if (string.IsNullOrEmpty(model.Name))
                {
                    ModelState.AddModelError("nullval", "Enter a value for Name");
                    return false;
                }
                if (string.IsNullOrEmpty(model.Surname))
                {
                    ModelState.AddModelError("nullval", "Enter a value for Surname");
                    return false;
                }
            }
            if ("2".Equals(Wizard().CurrentStep.StepName))
            {
                if (string.IsNullOrEmpty(model.Address))
                {
                    ModelState.AddModelError("nullval", "Enter a value for Address");
                    return false;
                }
                if (string.IsNullOrEmpty(model.City))
                {
                    ModelState.AddModelError("nullval", "Enter a value for City");
                    return false;
                }
            }
            if ("3".Equals(Wizard().CurrentStep.StepName))
            {
                if (string.IsNullOrEmpty(model.Telephone))
                {
                    ModelState.AddModelError("nullval", "Enter a value for telephone");
                    return false;
                }
                if (string.IsNullOrEmpty(model.Company))
                {
                    ModelState.AddModelError("nullval", "Enter a value for Company");
                    return false;
                }
                // if the wizard is valid the set confermation
                Wizard().IsConfermed = true;
            }

            return true;
        }

        /// <summary>
        /// Initialize the wizard manager.
        /// </summary>
        /// <returns></returns>
        protected override WizardManager<Employ> InitWizard()
        {
            // create the wizard manager
            WizardManager<Employ> wm = new WizardManager<Employ>() { ConfirmAction = "ConfirmWizard" };
            
            // add wizard step
            WizardStep ws1 = new WizardStep("1", "Step1") { StepTitle = "Insert employ data", StepSubTitle = "Insert Name and Surname" };
            wm.AddStep(ws1);
            WizardStep ws2 = new WizardStep("2", "Step2") { StepTitle = "Insert employ data", StepSubTitle = "Insert Address" }; ;
            wm.AddStep(ws2);
            WizardStep ws3 = new WizardStep("3", "Step3") { StepTitle = "Insert employ data", StepSubTitle = "Insert Company" }; ;
            wm.AddStep(ws3);

            // set the wizard model
            Employ optIn = new Employ();
            wm.WizardModel = optIn;
            return wm;
        }
    }

Nov 10, 2011 at 12:45 PM
Edited Nov 10, 2011 at 12:45 PM

Can you try disabling javascript altogether in your browser and see if you still get weird behaviour? This will confirm whether it's some kind of js library conflict, or an MVC / Orchard server-side conflict.

Nov 10, 2011 at 1:31 PM

I had worked variations of commenting out the layout.cshtml @{ Script.Require.... } and my own .cshtml  @{ Script.Include.... } but actually had not thought to physically disable js in the browser; but still no luck.

Google Chrome turned off Javascript: Still getting weird behavior (click "next" without filling in and first textbox throws error; try to fill in and click "next" textbox emptied error thrown).

IE9 turned of Javascript: same.

So it appears server-side as the ValidationError is not supported by an js.

I am looking at the code in the controller. I commented out all of the secondary "if" statements in the ValidateWizardModel section (not the top level ones with "1", "2" and "3", just the "if's" inside and the wizard went through (obviously) without a hitch (irrespective of whether a textbox was filled or not (again, obvious), and I get to the confirm page. I just have to test whether any code I would use after the confirm will work.

I am looking at how to validate in the Orchard way by inspecting Orchard.Startup module code. I also looked at some 3rd party modules for samples, but they appear more complicated. With Orchard.Startup I notice custom "Annotations" were used, so I am going to try to emulate those to see if I can get my code to work.  

Nov 10, 2011 at 3:10 PM

I'm wondering about two things:

- Can you check the debug/error logs (in App_Data/Logs) and see if anything is being thrown up there, even things in parts of Orchard that might not look immediately related?

- Can you show me the directory structure of your module and its views?

Nov 10, 2011 at 6:01 PM
Edited Nov 10, 2011 at 6:06 PM

My error log:

2011-11-10 12:44:46,438 [7] Contrib.KeepAlive.Services.KeepAliveExecutor - Could not ping keep alive service at url [http://localhost:18103/keepalive]System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException: No connection could be made because the target machine actively refused it 127.0.0.1:18103   at System.Net.Sockets.Socket.DoConnect(EndPoint endPointSnapshot, SocketAddress socketAddress)   at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Socket s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state, IAsyncResult asyncResult, Int32 timeout, Exception& exception)   --- End of inner exception stack trace ---   at System.Net.HttpWebRequest.GetResponse()   at Contrib.KeepAlive.Services.KeepAliveExecutor.Sweep()

Note: Recall that I commented out the validation parts in the controller.  I uncommented them to run the weird behavior and see if anything popped up in the error log.  The KeepAlive error I believe is because I am working locally on development server (downloaded from live) - using Publish/Download from WebMatrix. I could be wrong about the way that works.

My directory structure for module:

  • ModuleName
    • bin (empty)
    • Controllers
      • ExampleWizardController.cs
    • Filters
      • ModuleNameLayoutFilter.cs
    • Models
      • Models.cs
    • Scripts
      • jquery-1.5.1.min.js (commented out of ActionStep.cshtml)
      • modernizr-1.7.min.js (commented out of ActionStep.cshtml)
    • Styles (only contains web.config placed by Orchard command line)
    • Views
      • ExampleWizard
        • ActionStep.cshtml
        • ConfirmWizard.cshtml
        • Step1.cshtml
        • Step2.cshtml
        • Step3.cshtml
      • Shared (empty)
      • Layout-ModuleName.cshtml
      • Web.config
    • ModuleName.csproj
    • Module.txt
    • Placement.info
    • Routes.cs
    • Web.config

Thanks for helping out.

Nov 10, 2011 at 6:17 PM

I've notice that when I comment out the validation portions that although I can get to the end of the wizard, the confirmation only shows the last step's inputs (two textboxes), so I don't know if its a problem with persisting steps 1 and 2.

Dec 3, 2011 at 7:52 AM

I've also got problem with client side validation on custom controller in my module since 1.3. In 1.2 everythoing was correct. The problem is that unobtrusive data attributes are not rendered anymore in html

Example

 

Rendered in Orchard 1.2

<input data-val="true" data-val-length="The field Miasto must be a string with a maximum length of 100." data-val-length-max="100" data-val-required="Prosze poda&#263; nazw&#281; miasta" id="Company_AccomodationAddress_City" name="Company.AccomodationAddress.City" type="text" value="Warszawa Wawer" />

Rendered in 1.3

<input id="Company_RegistrationAddress_City" name="Company.RegistrationAddress.City" type="text" value="Warszawa Wawer" />

 

I think that is the reason.

Dec 3, 2011 at 9:10 PM

I have no idea why Orchard would be stripping such things; what is your code to generate the html?

Dec 4, 2011 at 11:23 AM
Edited Dec 4, 2011 at 11:24 AM

I've got some clues. Vote please on this issue

http://orchard.codeplex.com/workitem/18269


meantime I will try to find out some workaround because this is blocker for my project.

Dec 4, 2011 at 5:16 PM

I've completely redesigned my wizard. Rather than rely on client side validation, I simply just rely on the validation summary on top of each step in the wizard. Example: Step 1 - validation triggered for required or other rules on top of step; correct errors, click "Next" and it moves on to next step. Step 2 - same. Confirm and then "Submit" work. I enjoyed the client side validation outside of Orchard, but I can live with this solution.

Dec 4, 2011 at 9:00 PM

I've found a workaroud. There are cleared all ModelValidatorProviders in Orchard 1.3 in OrchardStartup.cs .. like this

// Register localized data annotations
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new LocalizedModelValidatorProvider());

dirty fix is to readd default providers. it can be done in your custom module in autofac module like this.

    public class ContainerModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            base.Load(builder);

            ModelValidatorProviders.Providers.Clear();
            ModelValidatorProviders.Providers.Add(new DataAnnotationsModelValidatorProvider());
            ModelValidatorProviders.Providers.Add(new DataErrorInfoModelValidatorProvider());
            ModelValidatorProviders.Providers.Add(new ClientDataTypeModelValidatorProvider());
        }
    }

Feb 12, 2013 at 5:29 PM
One year later and there is still such an issue :(
Took me one hour to find it out. Waste of time :(
Coordinator
Feb 12, 2013 at 5:30 PM
Well, do a pull request then.
Feb 12, 2013 at 5:45 PM
What is it? Is it something like to post updates to Orchard source code base in the context of Git?
I'm just poor TFS/SVN guy :)
Feb 12, 2013 at 5:47 PM
Basically. But you can also create a work item here in codeplex, and attach a patch. Most source control systems let you create .patch files.
Feb 16, 2013 at 2:20 AM
I had to look into this exact issue. After investigating, I found that the issue is related to the base DataAnnotationsModelValidatorProvider class using attribute.GetType() to identify which adapter to use. Well in LocalizedModelValidatorProvider the attribute types are changed from their original type to a new localized version (ie. StringLengthAttribute becomes LocalizedStringLengthAttribute ). So when the attribute.GetType() is performed, it cannot match the new types to any of the existing defined attribute type -> adapter dictionary mappings. To resolve this, I found that I could register the new attribute types to the existing adapters in the static constructor of the LocalizedModelValidatorProvider class:
    public class LocalizedModelValidatorProvider : DataAnnotationsModelValidatorProvider {
        private static readonly Dictionary<Type, Func<ValidationAttribute, Localizer, ValidationAttribute>> _validationAttributes;

        static LocalizedModelValidatorProvider() {
            _validationAttributes = new Dictionary<Type, Func<ValidationAttribute, Localizer, ValidationAttribute>> {
                { typeof(RequiredAttribute),            (attribute, t) => new LocalizedRequiredAttribute((RequiredAttribute)attribute, t)},
                { typeof(RangeAttribute),               (attribute, t) => new LocalizedRangeAttribute((RangeAttribute)attribute, t)},
                { typeof(StringLengthAttribute),        (attribute, t) => new LocalizedStringLengthAttribute((StringLengthAttribute)attribute, t)},
                { typeof(RegularExpressionAttribute),   (attribute, t) => new LocalizedRegularExpressionAttribute((RegularExpressionAttribute)attribute, t)}
            };

            //Register Orchard's localized version of the validation attributes to the existing adapters
            DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(LocalizedRequiredAttribute), typeof(RequiredAttributeAdapter));
            DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(LocalizedRangeAttribute), typeof(RangeAttributeAdapter));
            DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(LocalizedStringLengthAttribute), typeof(StringLengthAttributeAdapter));
            DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(LocalizedRegularExpressionAttribute), typeof(RegularExpressionAttributeAdapter));
        }
        ..
        ..
Coordinator
Feb 16, 2013 at 2:30 AM
Thanks. Can you please file a bug and attach your patch, or do a pull request?
Feb 16, 2013 at 10:48 AM
Connect to this issue please

http://orchard.codeplex.com/workitem/18269
Aug 23, 2013 at 2:50 AM
kmulder - nice job fixing this. We ran into the same issue and your fix works!