How To Add Bootstrap Row Validation Within Your Episerver Content Areas

When you work with Bootstrap within EpiServer, your HTML will still need to work on a row and column format in order for your pages to render correctly whenever a site visitor comes to look at it.  In the rules of Bootstrap, pages should be made up of rows, each containing items with column widths that add up to 12.  Episerver, on the other hand, provides a really flexible and configurable backend that allows content editors to arrange page layouts in new and creative ways that weren't possible 5 years ago.   These two statements, however, create a bit of friction between Bootstrap and Episever.  Content editors need to have an understanding of Bootstrap grid layouts when they work with Episerver, otherwise, they might break the grid layout that bootstrap expects and your web pages will start to behave in unexpected ways.

There are a number of ways of configuring Episerver to work with Bootstrap nicely and, in today's tutorial, I'm going to cover all the code that you need in order to create a content area, that only accepts a row block.  The row block will then be configured with a custom Episerver validation attribute that will prevent content editors adding blocks with widths that are greater than 12 columns.  This approach isn't the only way of configuring EPiserver to work with Bootstrap but I've personally used it on a few projects and this approach does give content editors the flexibility to add blocks onto a page and set the widths how they see fit, all while being forced to work within the Bootstrap grid system correctly, so let's begin:

How Do I Restrict A Content Area To Only Content Editors To Add Certain Blocks

This bit is very simple when you define your pages/block you use the  'AllowedTypes' attribute, like so:

        [Display(
            Name = "Main Area",
            Description = "Main Area",
            GroupName = SystemTabNames.Content,
            Order = 100)]
        [AllowedTypes(typeof(RowBlock))]
        public virtual ContentArea MainArea { get; set; }

 Using this approach, now ensures that your HTML will always have a wrapping row block.  The row blocks HTML simply looks like this:

<div class="row">
@Html.PropertyFor(m => m.CurrentBlock.RowContentArea)
</div>

And the Row definition:

 
 [ContentType(DisplayName = "Row Block",
        GUID = "dd579c53-b562-4f0d-a872-c3d8f2318cf1",
        Description = "Row Block")]
    public class RowBlock : BlockData
    {

        [Display(
            Name = "Content Area",
            Description = "Content Area",
            GroupName = SystemTabNames.Content,
            Order = 400)]
        [ColumnValidation]
        public virtual ContentArea RowContentArea { get; set; }
    }

The only issue we have now is that the content editors can still add as many blocks they want to inside the row block, breaking the maximum of 12 column per row rule.  To get around this we can create a custom validation attribute.  To pre-warn you now, you'll need a lot of boilerplate code to enable this.  You need your display options set.  I'll cover the validation attribute first as that's the clever bit.  I'll add the boilerplate code afterwards, however, as there's so much, I've uploaded a Display option Sample Project into my Github account here (just in case you get stuck)

   [AttributeUsage(AttributeTargets.Property |
                        AttributeTargets.Field |
                        AttributeTargets.Parameter)]
        public class BootstrapRowValidationAttribute : ValidationAttribute
        {

        public override bool IsValid(object value)
        {
            var contentArea = value as ContentArea;
            var noItems = contentArea?.Items == null;

            if (noItems)
                return false;

            var count = 0;
            foreach (var item in contentArea.Items)
            {
                var displayOption = item.LoadDisplayOption();

                if (displayOption == null)
                    continue;

                var optionAsEnum = GetDisplayOptionTag(displayOption.Tag);

                count = count + (int) optionAsEnum;

                if (count > 12)
                    return false;
            }

            return true;
        }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var result = base.IsValid(value, validationContext);

            if (!string.IsNullOrWhiteSpace(result?.ErrorMessage))
                result.ErrorMessage = "Too Many Items In Content Area";

            return result;
        }

        public static DisplayOptionEnum GetDisplayOptionTag(string tag)
        {
            DisplayOptionEnum displayOptionEnum;
            Enum.TryParse(tag, out displayOptionEnum);

            return displayOptionEnum;
        }
    }

In the example above, we inherit from ValidationAttribute and implement the IsValid method. In here, we get all the content area items (usually blocks) within content area and iterate through each one. For each block, we get the display option that has been associated with it, get its width and add that number to a total width count. If that count gets higher than 12, we return false which will prevent the content editor from adding it.

    
    public enum DisplayOptionEnum
    {
        Unknown,

        [BootstrapClass(Name = "col-md-12")]
        Full = 12,

        [BootstrapClass(Name = "col-md-9")]
        ThreeQuaters = 9,

        [BootstrapClass(Name = "col-md-6")]
        Half = 6,

        [BootstrapClass(Name = "col-md-3")]
        Quater = 3,
    }

Whenever I enable Display Options, I recommend using a custom DisplayOptionEnum enum. In this way, you can have one thing that defines the display option name, the bootstrap class name and the import bit in our case an integer value indicating the columns width. If you are wondering how you attach the attribute to your pages or blocks, you simply decorate your content area property with it, like so:

       [Display(
            Name = "Main Content Area",
            Description = "Main Content Area",
            GroupName = SystemTabNames.Content,
            Order = 400)]
        [BootstrapRowValidation]
        public virtual ContentArea MainContentArea { get; set; }

Now, if you run Episerver and try and drop too many blocks that exceed the bootstrap column limit you will see this error:

Sample Code

If you want a working sample site to demo this concept then have a look at my Github project, Episerver Display Options

 

Episerver Bootstrap Validation

As someone said in a Marvel film once, "with great content editing power comes more headaches for developers".  The aim of this tutorial is to help you think in terms of structuring the EPiserver backend in a way that makes it easy for content editors to use, but still enforcing the HTML that the designers will need.  This approach isn't the only way of enforcing the Bootstrap markup is outputted correctly.  In this approach, you are creating extra blocks and button clicks for content editors when creating pages and blocks, but it does mean you can 100% ensure the end row and columns HTML structure is rendered in the way you need it to be, enjoy!

submit to reddit

Jon D Jones

Software Architect, Programmer and Technologist Jon Jones is founder and CEO of London-based tech firm Digital Prompt. He has been working in the field for nearly a decade, specializing in new technologies and technical solution research in the web business. A passionate blogger by heart , speaker & consultant from England.. always on the hunt for the next challenge

Back to top