Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Jason Espin 368 posts 1335 karma points
    Jun 22, 2015 @ 16:20
    Jason Espin
    0

    Custom error messages using the Umbraco dictionary - Umbraco Validation Attributes plugin fails

    Hi all,

    I have been using Warren Buckley's Umbraco validation attributes plugin for as long as I remember and it is an awesome tool that should be part of the Umbraco core as Umbraco isn't truly multi-lingual at the moment without it. However, I have been having nothing but issues trying to get it to work in recent versions of Umbraco.

    I am currently using version 7.2.4 and in this version it would appear that the plugin in general, even after some bug fixes (https://github.com/warrenbuckley/Umbraco-Validation-Attributes/issues/25) just does not work.

    Part of the issues I am encountering may be because my model itself if overly complex but really this shouldn't be an issue.

    My form overall is based upon the following tutorial by Sebastiaan Janssen which aims to create a multi-stage booking process:

    http://umbraco.com/follow-us/blog-archive/2015/2/13/creating-multi-step-forms-using-a-surfacecontroller

    My model is as follows:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.ComponentModel.DataAnnotations;
    using Umbraco.Web.Models;
    using Umbraco.Web;
    using UmbracoValidationAttributes;
    
    namespace My_Project.Models
    {
        public class TourBookingModel : RenderModel
        {
            public TourBookingModel() : base(UmbracoContext.Current.PublishedContentRequest.PublishedContent) { }
    
            public bool Previous { get; set; }
            public bool Next { get; set; }
            public int StepIndex { get; set; }
    
            public TourInstanceSelection TourInstanceSelection { get; set; }
            public PassengerCountSelection PassengerCountSelection {get; set;}
        }
    
        public class TourInstanceSelection
        {
            [UmbracoRequired("Validation.Required")]
            public int? InstanceId { get; set; }
        }
    
        public class PassengerCountSelection
        {
            public int Adults { get; set; }
            public int Children { get; set; }
            public string Pickup { get; set; }
        }
    }
    

    I then have the following SurfaceController that uses this model:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using Umbraco.Web.Mvc;
    using My_Project.Models;
    using System.Configuration;
    using Newtonsoft.Json.Linq;
    
    namespace My_Project.Controllers
    {
        public class TourBookingController : SurfaceController
        {
            [ChildActionOnly]
            public ActionResult ShowBookingForm(TourBookingModel model)
            {
                model = model ?? new TourBookingModel();
    
                if (model.Previous)
                    model.StepIndex--;
    
                if (model.Next && ModelState.IsValid)
                    model.StepIndex++;
    
                return View(model);
            }
    
            [HttpPost]
            public ActionResult FormSubmit(TourBookingModel model)
            {
                //ignore validation or saving data when going backwards
                if (model.Previous)
                    return CurrentUmbracoPage();
    
                var validationStep = string.Empty;
                switch (model.StepIndex)
                {
                    case 0:
                        validationStep = "TourInstanceSelection";
                        break;
                    case 1:
                        validationStep = "PassengerCountSelection";
                        break;
                    case 2:
                        validationStep = "TermsAgreementStep";
                        break;
                }
    
                // remove all errors except for the current step
                foreach (var key in ModelState.Keys.Where(k => k.StartsWith(string.Format("{0}.", validationStep)) == false))
                {
                    ModelState[key].Errors.Clear();
                }
    
                if (!ModelState.IsValid)
                {
    
                    return CurrentUmbracoPage();
                }
    
                // It's the final step, do some saving
                if (model.StepIndex == 2)
                {
                    TempData.Add("CustomMessage", "Your booking was successfuly submitted at " + DateTime.Now);
                    return RedirectToCurrentUmbracoPage();
                }
    
                return CurrentUmbracoPage();
            }
        }
    }
    

    Finally, my form is rendered in the following view:

    @using My_Project.Controllers
    @using My_Project.Bespoke_Classes
    @using Newtonsoft.Json
    @using System.Configuration;
    @using My_Project.GroupTourWebService
    @model My_Project.Models.TourBookingModel
    @{
        string instances = Model.Content.GetPropertyValue<string>("instances");
        string jsonInstances = string.Format("[{0}]", instances);
    
        List<string> availableDates = new List<string>();
    
        List<JSONTourInstance> tourInstances = JsonConvert.DeserializeObject<List<JSONTourInstance>>(jsonInstances);
        foreach (JSONTourInstance item in tourInstances)
        {
            availableDates.Add(item.Date);
        }
    
        string jsonDates = JsonConvert.SerializeObject(availableDates); 
    
        /* Group tour web service request information */   
        ManagedUserHeader managedUserHeader = new ManagedUserHeader();
        managedUserHeader.CompanyName = ConfigurationManager.AppSettings["name"];
        managedUserHeader.RegistrationKey = new Guid(ConfigurationManager.AppSettings["guid"]);
    
        CompressionSoapHeader compressionSoapHeader = new CompressionSoapHeader();
        compressionSoapHeader.CompressionMethodUsed = CompressionMethods.None;
    }
    
    @if (TempData.ContainsKey("CustomMessage"))
    {
        <div>Hooray! - @TempData["CustomMessage"]</div>
    }
    else
    {
        using (Html.BeginUmbracoForm<TourBookingController>("FormSubmit"))
        {
            <div class="panel panel-default clearfix">
                @switch (Model.StepIndex)
                {
    
                    /* 
                    * Tour Instance Selection Stage
                    * The user selects a date and then a time that is linked to the date selected. The time itself is an instance which is then passed 
                    * to the next stage when the user clicks the next button
                    */
    
                    case 0:
                        <div class="col-md-6">
                            <h3>1. Veldu dagsettningu</h3>
                            <div class="departureDate"></div>
                        </div>
                        <div class="col-md-6" id="TourInstanceSelection_Container">
                            @Html.ValidationMessageFor(x => Model.TourInstanceSelection.InstanceId)
                            <div class="hidden control">
                                <h3>2. Veldu brottfarartima</h3>
                                <div class="selectHolder">
                                    <select id="BKG_tourInstanceSelect" name="TourInstanceSelection.InstanceId" class="btn-block">
                                        <option value="">-- Please select and option --</option>
                                        @foreach (JSONTourInstance tourInstance in tourInstances)
                                        {
                                            <option data-date="@tourInstance.Date" value="@tourInstance.Code">@tourInstance.Date @tourInstance.Time</option>
                                        }
                                    </select>
                                </div>
                            </div>     
                        </div>
                                        break;
    
                    /*
                    * Tour Availability Stage
                    * A request is sent to our web services to check the availability for the instance in question
                    * The availabilty of the tour is returned.
                    */
    
                    case 1:
                        /* Case 1 goes here */
                    break;
                }
    
                <div class="row">
                    <div class="col-md-12">
                        <div class="col-md-6">
                            @if (Model.StepIndex > 0)
                            {
                                <button type="submit" class="btn btn-img-blue btn-block" name="Previous" id="BKG_Previous" value="true">Previous</button>
                            }
                        </div>
                        <div class="col-md-6">
                            <button type="submit" class="btn btn-img-blue btn-block hidden" id="BKG_Next" name="Next" value="true">Next</button>
                        </div>
                    </div>
                </div>
                <input type="hidden" name="StepIndex" value="@Model.StepIndex" />
            </div>
        }
    }
    

    I have a dictionary value setup in the back office of "Validation.Required" that should fire on stage 1 of the booking process being invalid. This does fire successfully but the message displayed is not what I entered as a value in my dictionary. It appears it is completely ignore the Umbraco Validation attributes plugin and just using the default value. Does anyone have any ideas how to solve this as I am completely at a loss and this essentially means that implementing a proper multi-lingual Umbraco site is essentially impossible if this plugin does not work.

    Any help or advice would be greatly appreciated.

  • Shannon Deminick 1526 posts 5272 karma points MVP 3x
    Jun 22, 2015 @ 21:24
    Shannon Deminick
    0

    Attribute error messages are done in MVC based on a Model Meta Data provider. The default is the DataAnnotationsModelMetadataProvider

    As seen on the twitter discussion, the correct way to get model metadata to show a multi-lingual and dynamic error message is to implement a custom model meta data provider. A good start is seen here: http://pastebin.com/7gJ34M8x

    I haven't tested it at all, but the code is a good start and gives you an idea of how things can work. There's probably plenty of examples of custom model metadata providers on the web. This is not Umbraco stuff, this is just ASP.Net MVC stuff.

    Another thing you might want to look into is an interface called IMetadataAware (https://msdn.microsoft.com/en-us/library/system.web.mvc.imetadataaware(v=vs.118).aspx) , if you google around for that, you'll find that you can implement that interface to return custom metadata for your class from logic defined directly in your class instead of having to implement a custom model metadata provider.

    Alternatively, you can provider your own custom model metadata error messages from directly within your controller by modifying the ModelState and ensuring the correct keys are entered for the model properties.

    There's a few options you can look at and again, this is MVC stuff.

    You mentioned on twitter that "It dont work"... if you provider more specific/constructive feedback, that would be helpful.

  • Jason Espin 368 posts 1335 karma points
    Jun 23, 2015 @ 08:44
    Jason Espin
    0

    Hi Shannon,

    Thanks for the feedback. I have only been doing MVC for a year or so now and only in an Umbraco context so unfortunately at the moment this is probably a bit beyond me. Let's not forgot that standard MVC rules do not always apply to Umbraco due to its specialised routing. In all honesty because of the stage we are at with the current project I am working on I really don't have the time to go away and create my own solution when there is already something out there that should be working and in all honesty should be core. I've used this plugin multiple times on previous versions of Umbraco and kind of rely on it. After all in programming you shouldn't reinvent the wheel. I also don't know how I could be more specific. I've enclosed all of the code involved in the process above but can't describe that on Twitter with the limited characters hence why I placed it here.

    Cheers,

    Jason

Please Sign in or register to post replies

Write your reply to:

Draft