Thursday, 25 August 2016

Validating for uniqueness with EPiServer

This is just a short post describing a method found for validating that the value provided for field within a page or block is unique across other instances of that content type. The specific need was for a poll block that provided a question and set of answers for users to pick from, with the results being saved to the dynamic data store. I had an "identifier" field that the editors had to complete, the value of which being unique across all polls. And rather than relying on them to remember that, it's obviously better to validate it.

Here was the solution. Firstly I created an extension method that gets a list of all blocks of a given type other than the current block, looking like this:

    public static IEnumerable<T> GetOtherContentData<T>(this T instance) where T : IContentData
    {
        var contentTypeRepository = ServiceLocator.Current.GetInstance<IContentTypeRepository>();
        var contentModelUsage = ServiceLocator.Current.GetInstance<IContentModelUsage>();
        var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
        var blockType = contentTypeRepository.Load<T>();
        var existingBlocks = contentModelUsage.ListContentOfContentType(blockType);
        return existingBlocks
            .Where(x => contentLoader.Get<IContent>(x.ContentLink).ContentGuid != ((IContent)instance).ContentGuid)
            .Select(x => contentLoader.Get<T>(x.ContentLink));
    }

With that available validation can be wired up for the block like this:

    public class PollBlock : BlockBase, IValidate<PollBlock>
    {
        ...
        
        [Required]
        [Display(
            Name = "Identifier",
            Description = "This value should be unique across all polls to ensure recorded data is correctly stored",
            Order = 10)]
        [StringLength(10)]
        public virtual string Identifier { get; set; }
        
        ...

        public IEnumerable<ValidationError> Validate(PollBlock instance)
        {
            var errors = new List<ValidationError>();

            // Check whether the identifier is unique across all instances of the PollBlock
            if (AreThereOtherBlocksWithSameIdentifier(instance))
            {
                errors.Add(new ValidationError
                {
                    ErrorMessage = "The identifier provided has been used on another poll block instance.  Please choose a unique one.",
                    PropertyName = instance.GetPropertyName(x => x.Identifier),
                    Severity = ValidationErrorSeverity.Error,
                    ValidationType = ValidationErrorType.StorageValidation,
                });
            }

            return errors.AsEnumerable();
        }

        private static bool AreThereOtherBlocksWithSameIdentifier(PollBlock instance)
        {
            var otherBlockInstances = instance.GetOtherContentData();
            return otherBlockInstances
                .Any(x => x.Identifier == instance.Identifier);
        }
    }

Monday, 22 August 2016

Redirect to HTTP behind load balancer (apart from localhost) with URL rewrite

Just a short "note to self for future" post as found a few variations for part of this online but needed to pull them together.

Requirement is:

  • Ensure all HTTP requests are redirected to HTTPS
  • Handle the fact that the web server is behind a load balancer that terminates HTTPS
  • Allow for local HTTP requests via a custom port

Monday, 15 August 2016

Personalisation and caching with Umbraco

A little while ago I put together a package for Umbraco called Personalisation Groups as a means of providing personalisation features to the Umbraco CMS. One addition I've been considering is how to add better support for use of caching for a web application utilising personalisation features.

In any context, not just Umbraco, this isn't straightforward. Normally caching, at least on a page level, is going to be varied by the request URL. But of course with personalisation in place, for a given page, we are displaying different content to different users and so we don't want the cached version of a page customised to particular user being displayed to the next.

There are a few ways to potentially tackle this. Some personalisation engines would look to do this client-side, using JavaScript to make the page modifications. Donut caching might be another approach if you are able to construct the templates to match it's requirements. I've looked though to try to find a method using the standard way of applying page level caching with ASP.Net MVC, via output caching.

Output caching is available to Umbraco websites, at least those using their own controllers and hijacked routes. It's used then just as it is in any ASP.Net MVC application, though there's a need to ensure that, given a single controller may be responsible for a number of pages, that the cache is varied across the URL. The way to do this is nicely described in this blog post and should be handled in addition to that I describe below.

When setting up an output cache as well as the time to cache for it's possible to configure various things to vary the cached page by - querystring paramenters, HTTP headers or custom values such as the URL as described above. It's this latter support for custom values we can make use of here for caching personalised versions of pages.

With the personalisation groups package you create one or more group definitions - things like "Weekday morning visitors", "Overseas visitors" etc. and then associate them with pieces of content. To help with caching invalidation I've not set up a helper method as an extension to the Umbraco helper that generates a hash string for all the available groups for the current visitor. It checks each group, determines if the current visitor matches the group and stores the result. This is available from version 0.1.6.

The value of this string can itself be cached per user, which you'd likely want to do for a period of time as, though not expensive, it's not necessary to calculate on every page request. However it equally shouldn't be cached for too long as visitor's status in each personalisation group may change as they use the website. For example a group targeting morning visitors would no longer match if the same visitor is still there in the afternoon.

With that method in available, it's possible to use it with output caching to ensure the cache varies by this set of matched personalisation groups, for example with a controller like this:

    public class TestPageController : RenderMvcController
    {
        [OutputCache(Duration = 600, VaryByParam = "*", VaryByCustom = "PersonalisationGroupsVisitorHash")]
        public override ActionResult Index(RenderModel model)
        {
            ...
            return CurrentTemplate(vm);
        }

    }

And code in global.asax.cs as follows (credit here for code to determine the ASP.Net session cookie):

    public class Global : UmbracoApplication
    {
        private static readonly SessionStateSection SessionStateSection =   
           (SessionStateSection)ConfigurationManager.GetSection("system.web/sessionState");

        public void Session_OnStart()
        {
            // Just set something to ensure a session is created
            Session[AppConstants.SessionKeys.PersonalisationGroupsEnsureSession] = 1;
        }

        public override string GetVaryByCustomString(HttpContext context, string custom)
        {
            if (custom.Equals("PersonalisationGroupsVisitorHash", StringComparison.OrdinalIgnoreCase))
            {
                var cookieName = SessionStateSection.CookieName;
                var sessionIdCookie = context.Request.Cookies[cookieName];
                if (sessionIdCookie != null)
                {
                    var umbracoHelper = new UmbracoHelper(UmbracoContext.Current);
                    var hash = umbracoHelper.GetPersonalisationGroupsHashForVisitor(1093,   // Would normally get the node Id from config
                        sessionIdCookie.Value, 
                        20);
                    return hash;
                }
            }

            return base.GetVaryByCustomString(context, custom);
        }
    }

More details at the GitHub repository here. Comments and feedback welcome.

Friday, 24 June 2016

Creating a Confirmation Email With EPiServer Forms

Had what probably seemed quite a straightforward request for an EPiServer site in development that's using EPiServer Forms - an add-on that allows editors to create their own forms to receive input from the site visitors and review the submissions in the back-office.  They wanted a confirmation email to go to the recipient.

Of course though, we don't know what fields the editors are going to create. There may not be one for the visitor's email address. nd even if there were, how can we know which field is the one to use?

I came up with a solution to this that requires some simple conventions for the editor to follow but is quite straightforward for them to set up.

The first step was to create a new model for the form, inheriting from the base one provided but adding additional properties for the confirmation email details:

    public class FormContainerWithConfirmationEmailBlock : FormContainerBlock, IHasConfirmationEmail
    {
        [Display(
            Name = "Tick to send a confirmation message to the user following sign-up",
            Order = 40)]
        public virtual bool SendConfirmationEmail { get; set; }

        [Display(
            Name = "Confirmation email subject",
            GroupName = ContentGroupNames.Content,
            Order = 45)]
        [StringLength(200)]
        public virtual string ConfirmationEmailSubject { get; set; }

        [Display(
            Name = "Confirmation email copy",
            Order = 50)]
        [PropertySettings(typeof(BasicTinyMceSettings))]
        public virtual XhtmlString ConfirmationEmailCopy { get; set; }

        public override void SetDefaultValues(ContentType contentType)
        {
            base.SetDefaultValues(contentType);
            AllowAnonymousSubmission = true;
        }
    }

Then to allow for that model to be rendered, I similarly created a controller delegating to the provided base controller:

    public class FormContainerWithConfirmationEmailBlockController : FormContainerBlockController
    {
    }

And a view delegating to the user control that provides the front-end and back-office rendering functionality.

    @model FormContainerWithConfirmationEmailBlock

    @Html.Partial("~/modules/_protected/EPiServer.Forms/Views/ElementBlocks/FormContainerBlock.ascx", Model)

Secondly I created a new field element type, that behaves just like a text box (I copied the user control TextboxElementBlock.ascx and saved it under a new name), but is a "special" one that the editors will need to use if they want the confirmation email to function.  This is added to the form for the collection of the visitor's email address.

    public class VisitorsEmailAddressElementBlock : TextboxElementBlock
    {
    }

Finally I needed to hook into the form submission event to process the confirmation email. The following code:

  • Checks to see if the form submission comes from a form block type that can be configured with a confirmation email
  • Checks to see if the confirmation email has been configured
  • Checks that the "special" field holding the visitor's email address is present and has been completed
  • And if all those are the case, sends the email
    [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
    public class FormSubmissionInitialization : IInitializableModule
    {
        public void Initialize(InitializationEngine context)
        {
            var eventRegistry = ServiceLocator.Current.GetInstance<FormsEvents>();
            eventRegistry.FormsSubmissionFinalized += OnFormsSubmissionFinalized;
        }

        public void Preload(string[] parameters)
        {
        }

        public void Uninitialize(InitializationEngine context)
        {
            var eventRegistry = ServiceLocator.Current.GetInstance<FormsEvents>();
            eventRegistry.FormsSubmissionFinalized += OnFormsSubmissionFinalized;
        }

        private static void OnFormsSubmissionFinalized(object sender, FormsEventArgs e)
        {
            HandleConfirmationEmail((FormsSubmittedEventArgs)e);
        }

        private static void HandleConfirmationEmail(FormsSubmittedEventArgs eventArgs)
        {
            // Even though we get back the full form block details later, do a quick cast based check here
            // to see if we are working with a form that could have confirmation emails configured
            var formContent = eventArgs.FormsContent as FormContainerWithConfirmationEmailBlock;
            if (IsConfirmationEmailConfigured(formContent))
            {
                int fieldIdForVisitorsEmailAddress;
                if (TryGetFieldIdForVisitorsEmailAddress(formContent, out fieldIdForVisitorsEmailAddress))
                {
                    string recipientEmail;
                    if (TryExtractVisitorsEmailAddress(eventArgs, fieldIdForVisitorsEmailAddress, out recipientEmail))
                    {
                        // Send the email (the component called below is a simple wrapper for populating an email sending via SMTP)
                        var confirmationEmailSender = ServiceLocator.Current.GetInstance<IConfirmationEmailSender>();
                        confirmationEmailSender.SendConfirmationEmail("editable-form-confirmation.txt",
                            formContent.ConfirmationEmailSubject,
                            formContent.ConfirmationEmailCopy, recipientEmail);
                    }
                }
            }
        }

        private static bool IsConfirmationEmailConfigured(FormContainerWithConfirmationEmailBlock formContent)
        {
            return formContent != null && formContent.SendConfirmationEmail;
        }

        private static bool TryGetFieldIdForVisitorsEmailAddress(FormContainerWithConfirmationEmailBlock formContent, out int fieldId)
        {
            fieldId = 0;
            var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
            foreach (var element in formContent.ElementsArea.Items)
            {
                VisitorsEmailAddressElementBlock visitorsEmailAddressElementBlock;
                if (contentLoader.TryGet<VisitorsEmailAddressElementBlock>(element.ContentLink,
                    out visitorsEmailAddressElementBlock))
                {
                    fieldId = element.ContentLink.ID;
                    return true;
                }
            }

            return false;
        }

        private static bool TryExtractVisitorsEmailAddress(FormsSubmittedEventArgs eventArgs, int fieldIdForVisitorsEmailAddress, out string recipientEmailAddress)
        {
            var formData = eventArgs.SubmissionData.Data;
            recipientEmailAddress = formData["__field_" + fieldIdForVisitorsEmailAddress]?.ToString();
            return recipientEmailAddress.IsValidEmailAddress();
        }
    }

Friday, 17 June 2016

Personalisation Packages in Umbraco

Had the pleasure of giving a talk at the Umbraco Codegarden 2016 festival this week, along with Theo Paraskevopoulos of Growcreate. As mentioned at the time have collected here links to the slides and various resources mentioned during the session.

Firstly the presentation itself can be viewed here.

The Personalisation Groups package can be downloaded from our.umbraco.org here and the source code is up on Github.

And you can read more about and download Pipeline CRM here.

Monday, 6 June 2016

Working with the Microsoft Bot Framework and LUIS

Again cross-posting an article I've written for my company blog on working with the Microsoft Bot Framework and LUIS.

Wednesday, 30 March 2016

Slack Slash Commands and ASP.Net Web Hooks

Cross-posting a link to a piece written for my company's blog on Slack Slash Commands and ASP.Net Web Hooks.