Home » Validators

Category Archives: Validators

Use the Factory Method Pattern for Object Creation in Sitecore

This post is a continuation of a series of posts I’m putting together around using design patterns in Sitecore, and will show a “proof of concept” around using the Factory Method pattern — a creational pattern whereby client code obtain instances of objects without knowing the concrete class types of these objects. This pattern promotes loose coupling between objects being created and the client code that use them.

In this “proof of concept”, I am using an Item Validator to call a factory method to obtain a “fields comparer” object to ascertain whether one field contains a value greater than a value in another field, and will show this for two different field types in Sitecore.

I first defined an interface for objects that will compare values in two Sitecore fields:

using Sitecore.Data.Fields;

namespace Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers
{
    public interface IFieldsComparer
    {
        bool IsFieldOneLessThanOrEqualToFieldTwo(Field fieldOne, Field fieldTwo);
    }
}

Instances of classes that implement the IFieldsComparer interface above will ascertain whether the value in fieldOne is less than or equal to the value in fieldTwo.

I then defined a class that implements the IFieldsComparer interface to compare integer values in two fields:

using System;

using Sitecore.Data.Fields;
using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers
{
    public class IntegerFieldsComparer : IFieldsComparer
    {
        public bool IsFieldOneLessThanOrEqualToFieldTwo(Field fieldOne, Field fieldTwo)
        {
            Assert.ArgumentNotNull(fieldOne, "fieldOne");
            Assert.ArgumentNotNull(fieldTwo, "fieldTwo");
            return ParseInteger(fieldOne) <= ParseInteger(fieldTwo);
        }

        protected virtual int ParseInteger(Field field)
        {
            int fieldValue;
            int.TryParse(field.Value, out fieldValue);
            return fieldValue;
        }
    }
}

There isn’t much to see in the class above. The class parses the integer values in each field, and checks to see if the value in fieldOne is less than or equal to the value in fieldTwo.

Now, let’s create a another class — one that compares DateTime values in two different fields:

using System;

using Sitecore.Data.Fields;
using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers
{
    public class DateFieldsComparer : IFieldsComparer
    {
        public bool IsFieldOneLessThanOrEqualToFieldTwo(Field fieldOne, Field fieldTwo)
        {
            Assert.ArgumentNotNull(fieldOne, "fieldOne");
            Assert.ArgumentNotNull(fieldTwo, "fieldTwo");
            return ParseDateTime(fieldOne) <= ParseDateTime(fieldTwo);
        }

        protected virtual DateTime ParseDateTime(Field field)
        {
            return DateUtil.IsoDateToDateTime(field.Value);
        }
    }
}

Similarly to the IFieldsComparer class for integers, the class above parses the field values into DateTime instances, and ascertains whether the DateTime value in fieldOne occurs before or at the same time as the DateTime value in fieldTwo.

You might now be asking “Mike, what about other field types?” Well, I could have defined more IFieldsComparer classes for other fields but this post would go on and on, and we both don’t want that 😉 So, to account for other field types, I’ve defined the following Null Object for fields that are not accounted for:

using Sitecore.Data.Fields;

namespace Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers
{
    public class NullFieldsComparer : IFieldsComparer
    {
        public bool IsFieldOneLessThanOrEqualToFieldTwo(Field fieldOne, Field fieldTwo)
        {
            return true;
        }
    }
}

The Null Object class above just returns true without performing any comparison.

Now that we have “fields comparers”, we need a Factory method. I’ve defined the following interface for objects that will create instances of our IFieldsComparer:

using Sitecore.Data.Fields;

namespace Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers
{
    public interface IFieldsComparerFactory
    {
        IFieldsComparer GetFieldsComparer(Field fieldOne, Field fieldTwo);
    }
}

Instances of classes that implement the interface above will return the appropriate IFieldsComparer for comparing the two passed fields.

The following class implements the IFieldsComparerFactory interface above:

using System;
using System.Collections.Generic;
using System.Xml;

using Sitecore.Configuration;
using Sitecore.Data.Fields;
using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers
{
    public class FieldsComparerFactory : IFieldsComparerFactory
    {
        private static volatile IFieldsComparerFactory current;
        private static object locker = new Object();

        public static IFieldsComparerFactory Current
        {
            get
            {
                if (current == null)
                {
                    lock (locker)
                    {
                        if (current == null)
                        {
                            current = CreateNewFieldsComparerFactory();
                        }
                    }
                }

                return current;
            }
        }

        private static IDictionary<string, XmlNode> FieldsComparersTypes { get; set; }

        private IFieldsComparer NullFieldsComparer { get; set; }

        static FieldsComparerFactory()
        {
            FieldsComparersTypes = new Dictionary<string, XmlNode>();
        }

        public IFieldsComparer GetFieldsComparer(Field fieldOne, Field fieldTwo)
        {
            Assert.IsNotNull(NullFieldsComparer, "NullFieldsComparer must be set in configuration!");
            if (!AreEqualIgnoreCase(fieldOne.Type, fieldTwo.Type) || !FieldsComparersTypes.ContainsKey(fieldOne.Type))
            {
                return NullFieldsComparer;
            }

            XmlNode configNode = FieldsComparersTypes[fieldOne.Type];
            if(configNode == null)
            {
                return NullFieldsComparer;
            }

            IFieldsComparer comparer = Factory.CreateObject(configNode, false) as IFieldsComparer;
            if (comparer == null)
            {
                return NullFieldsComparer;
            }

            return comparer;
        }

        private static bool AreEqualIgnoreCase(string stringOne, string stringTwo)
        {
            return string.Equals(stringOne, stringTwo, StringComparison.CurrentCultureIgnoreCase);
        }

        protected virtual void AddFieldsComparerConfigNode(XmlNode configNode)
        {
            if(configNode.Attributes["fieldType"] == null || string.IsNullOrWhiteSpace(configNode.Attributes["fieldType"].Value))
            {
                return;
            }

            if (configNode.Attributes["type"] == null || string.IsNullOrWhiteSpace(configNode.Attributes["type"].Value))
            {
                return;
            }

            FieldsComparersTypes[configNode.Attributes["fieldType"].Value] = configNode;
        }

        private static IFieldsComparerFactory CreateNewFieldsComparerFactory()
        {
            return Factory.CreateObject("factories/fieldsComparerFactory", true) as IFieldsComparerFactory;
        }
    }
}

The AddFieldsComparerConfigNode() method above is used by the Sitecore Configuration Factory to add configuration-defined Xml nodes that define field types and their IFieldsComparer — these are placed into the FieldsComparersTypes dictionary for later look-up and instantiation.

The GetFieldsComparer() factory method tries to figure out which IFieldsComparer to return from the FieldsComparersTypes dictionary. If an appropriate IFieldsComparer is found for the two fields, the method uses Sitecore.Configuration.Factory.CreateObject() — this is defined in Sitecore.Kernel.dll — to create the instance that is defined in the type attribute of the XmlNode that is stored in the FieldsComparersTypes dictionary.

If an appropriate IFieldsComparer cannot be determined for the passed fields, then the Null Object IFieldsComparer — this is injected into the NullFieldsComparer property via the Sitecore Configuration Factory — is returned.

As a quick and dirty solution for retrieving an instance of the class above, I’ve used the Singleton pattern. An instance of the class above is created by the Sitecore Configuration Factory via the CreateNewFieldsComparerFactory() method, and is placed into the Current property.

I then defined all of the above in the following Sitecore patch configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <factories>
      <fieldsComparerFactory type="Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers.FieldsComparerFactory, Sitecore.Sandbox">
        <NullFieldsComparer type="Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers.NullFieldsComparer, Sitecore.Sandbox" />
        <fieldComparers hint="raw:AddFieldsComparerConfigNode">
          <fieldComparer fieldType="Datetime" type="Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers.DateFieldsComparer, Sitecore.Sandbox" />
          <fieldComparer fieldType="Integer" type="Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers.IntegerFieldsComparer, Sitecore.Sandbox" />
        </fieldComparers>
      </fieldsComparerFactory>
    </factories>
  </sitecore>
</configuration>

Now that we have our factory in place, we need an Item Validator to use it:

using System.Runtime.Serialization;

using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Data.Validators;

using Sitecore.Sandbox.Data.Validators.ItemValidators.FieldComparers;

namespace Sitecore.Sandbox.Data.Validators.ItemValidators
{
    public class FieldOneValueLessThanOrEqualToFieldTwoValueValidator : StandardValidator
    {
        public override string Name
        {
            get
            {
                return Parameters["Name"];
            }
        }

        private string fieldOneName;
        private string FieldOneName
        {
            get
            {
                if (string.IsNullOrWhiteSpace(fieldOneName))
                {
                    fieldOneName = Parameters["FieldOneName"];
                }

                return fieldOneName;
            }
        }

        private string fieldTwoName;
        private string FieldTwoName
        {
            get
            {
                if (string.IsNullOrWhiteSpace(fieldTwoName))
                {
                    fieldTwoName = Parameters["FieldTwoName"];
                }

                return fieldTwoName;
            }
        }

        public FieldOneValueLessThanOrEqualToFieldTwoValueValidator()
        {
        }

        public FieldOneValueLessThanOrEqualToFieldTwoValueValidator(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }

        protected override ValidatorResult Evaluate()
        {
            Item item = GetItem();
            if (IsValid(item))
            {
                return ValidatorResult.Valid;
            }

            Text = GetErrorMessage(item);
            return GetFailedResult(ValidatorResult.Warning);
        }

        private bool IsValid(Item item)
        {
            if (item == null || string.IsNullOrWhiteSpace(FieldOneName) || string.IsNullOrWhiteSpace(FieldTwoName))
            {
                return true;
            }

            Field fieldOne = item.Fields[FieldOneName];
            Field fieldTwo = item.Fields[FieldTwoName];
            if(fieldOne == null || fieldTwo == null)
            {
                return true;
            }

            return IsFieldOneLessThanOrEqualToFieldTwo(fieldOne, fieldTwo);
        }

        private bool IsFieldOneLessThanOrEqualToFieldTwo(Field fieldOne, Field fieldTwo)
        {
            IFieldsComparer fieldComparer = GetFieldsComparer(fieldOne, fieldTwo);
            return fieldComparer.IsFieldOneLessThanOrEqualToFieldTwo(fieldOne, fieldTwo);
        }

        protected virtual IFieldsComparer GetFieldsComparer(Field fieldOne, Field fieldTwo)
        {
            return FieldsComparerFactory.Current.GetFieldsComparer(fieldOne, fieldTwo);
        }

        protected virtual string GetErrorMessage(Item item)
        {
            string message = Parameters["ErrorMessage"];
            if (string.IsNullOrWhiteSpace(message))
            {
                return string.Empty;
            }

            message = message.Replace("$fieldOneName", FieldOneName);
            message = message.Replace("$fieldTwoName", FieldTwoName);

            return GetText(message, new[] { item.DisplayName });
        }

        protected override ValidatorResult GetMaxValidatorResult()
        {
            return base.GetFailedResult(ValidatorResult.Suggestion);
        }
    }
}

The real magic of the class above occurs in the IsValid(), IsFieldOneLessThanOrEqualToFieldTwo() and GetFieldsComparer() methods.

The IsValid() method gets the two fields being compared, and passes these along to the IsFieldOneLessThanOrEqualToFieldTwo() method.

The IsFieldOneLessThanOrEqualToFieldTwo() method passes the two fields to the GetFieldsComparer() — this returns the appropriate IFieldsComparer from the GetFieldsComparer() factory method on the FieldsComparerFactory Singleton — and uses the IFieldsComparer to ascertain whether the value in fieldOne is less than or equal to the value in fieldTwo.

If the value in fieldOne is less than or equal to the value in fieldTwo then the Item has passed validation. Otherwise, it has not, and an error message is passed back to the Sitecore client — we are replacing some tokens for fieldOne and fieldTwo in a format string to give the end user some information on the fields that are in question.

I then set up the Item Validator for Integer fields:

integer-comparer-item-validator

I also set up another Item Validator for Datetime fields:

datetime-item-validator

Let’s take this for a spin!

I entered some integer values in the two integer fields being compared:

integer-fields-item

As you can see, we get a warning.

I then set some Datetime field values on the two Datetime fields being compared:

datetime-fields-item

Since ‘Datetime One’ occurs in time after ‘Datetime Two’, we get a warning as expected.

If you have any thoughts on this, please share in a comment.

Warn Content Authors on Having Too Many Sub-items Under an Item in Sitecore

In my previous post, I shared two field validators that will warn content authors/editors when they link to Items without presentation in Internal and General Link fields.

When I was building those two validators, I came up with another validator idea: how about warning content authors/editors when they have too many sub-items under an Item?

To accomplish this, I came up with the following class that serves as an Item validator:

using System;
using System.Runtime.Serialization;

using Sitecore.Buckets.Managers;
using Sitecore.Data.Items;
using Sitecore.Data.Validators;

namespace Sitecore.Sandbox.Data.Validators.ItemValidators
{
    [Serializable]
    public class ItemHasTooManySubitemsValidator : StandardValidator
    {
        public override string Name
        {
            get
            {
                return Parameters["Name"];
            }
        }

        private int MaxNumberOfSubitems
        {
            get
            {
                int maxNumberOfSubitems;
                if (!int.TryParse(Parameters["MaxNumberOfSubitems"], out maxNumberOfSubitems))
                {
                    return 0;
                }

                return maxNumberOfSubitems;
            }
        }

        public ItemHasTooManySubitemsValidator()
        {
        }

        public ItemHasTooManySubitemsValidator(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }

        protected override ValidatorResult Evaluate()
        {
            Item item = GetItem();
            if(IsValid(item))
            {
                return ValidatorResult.Valid;
            }

            Text = GetErrorMessage(item);
            return GetFailedResult(ValidatorResult.Suggestion);
        }

        protected virtual bool IsValid(Item item)
        {
            return MaxNumberOfSubitems < 1
                   || item == null 
                   || IsBucket(item)
                   || !item.HasChildren
                   || item.Children.Count <= MaxNumberOfSubitems;
        }

        protected virtual bool IsBucket(Item item)
        {
            if(item == null)
            {
                return false;
            }

            return BucketManager.IsBucket(item);
        }

        protected virtual string GetErrorMessage(Item item)
        {
            string message = Parameters["ErrorMessage"];
            if (string.IsNullOrWhiteSpace(message))
            {
                return string.Empty;
            }

            return GetText(message, new[] { item.DisplayName });
        }

        protected override ValidatorResult GetMaxValidatorResult()
        {
            return base.GetFailedResult(ValidatorResult.Suggestion);
        }
    }
}

The class above inherits from Sitecore.Data.Validators.StandardValidator in Sitecore.Kernel.dll — this is the base class which most validators in Sitecore inherit from — and ascertains whether the Item being validated has too many sub-items underneath it (the maximum number of allowed sub-items is passed to the class’ instance via the MaxNumberOfSubitems parameter set on the Validation Rule item — these have the /sitecore/templates/System/Validation/Validation Rule template — in Sitecore which is shown later in this post).

If the Item being validated has more sub-items than is allowed and isn’t an Item Bucket, the validator’s error message is set on the Text property of the class instance — the error message is passed via a parameter on the Validation Rule item — and a ValidatorResult instance is returned to the caller.

I then wired up the above class in Sitecore on a Validation Rule item, and set the maximum number of allowed sub-items to be four for testing (no, I’m not going to create a gazillion Items to test this):

create-validator-in-sitecore

Now that we have the Validation Rule Item in place, we should probably give content authors/editors the ability to remedy having too many sub-items under an Item.

How?

Let’s give them the ability to convert the Item into an Item Bucket. I created the following Menu item — this has the template of /sitecore/templates/System/Menus/Menu item — to empower content authors/editors on making this conversion:

covert-to-item-bucket-menu-item

I then had to set up my Sample Item template to be bucketable since we are giving the ability to bucket Items with this template:

sample-item-bucketable

I then mapped the Item validator to the Standard Values item of my Sample Item template:

item-validator-on-standard-values

For testing, I created some Items underneath another Item:

item-with-four-items

As you can see, we haven’t exceeded the maximum number of 4 quite yet.

I then created a fifth item, and was presented with a validation warning:

item-with-five-subitems

I right clicked on the square in the Validation Bar, and was presented with some options:

item-with-five-subitems-convert-to-bucket

I clicked on “Convert to Item Bucket”, and then saw a magical progress dialog followed by this:

item-is-now-a-bucket

If you have any thoughts on this, or ideas for other Item validators, please drop a comment.

Warn Content Authors of Linking to Items With No Presentation using Custom Field Validators in Sitecore

The other day John West, CTO of Sitecore USA, published his 500th blog post — quite an epic feat if you ask me — where he built a custom field validator that checks whether external links in the Rich Text field resolve:

This got me thinking: what other types of field validators might be useful?

I pondered over this for the past couple of days, and couldn’t think of anything useful but finally did come up with an idea this morning (out of the blue I might add): how about field validators that check to see whether Items linked in General and Internal Link fields have presentation?

After searching through the library of field validators available in Sitecore — I did this to make sure I wouldn’t be wasting my time given that Sitecore offers a lot of field validators “out of the box” (these live under /sitecore/system/Settings/Validation Rules/Field Rules in the master database), so I suggest having a look through these before building a custom one — I came up with the following solution that employs the Template method design pattern:

using System;
using System.Runtime.Serialization;

using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Data.Validators;
using Sitecore.Diagnostics;
using Sitecore.Pipelines.HasPresentation;

namespace Sitecore.Sandbox.Data.Validators.FieldValidators
{
    public abstract class ReferencedItemHasPresentationValidator : StandardValidator
    {
        public override string Name
        {
            get
            {
                return Parameters["Name"];
            }
        }

        public ReferencedItemHasPresentationValidator()
        {
        }

        public ReferencedItemHasPresentationValidator(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }

        protected override ValidatorResult Evaluate()
        {
            Item linkedItem = GetReferencedItem();
            if (linkedItem == null || HasPresentation(linkedItem))
            {
                return ValidatorResult.Valid;
            }

            Text = GetErrorMessage();
            return GetFailedResult(ValidatorResult.Error);
        }

        protected virtual bool HasPresentation(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return HasPresentationPipeline.Run(item);
        }

        protected abstract Item GetReferencedItem();

        protected virtual string GetErrorMessage()
        {
            string message = Parameters["ErrorMessage"];
            if (string.IsNullOrWhiteSpace(message))
            {
                return string.Empty;
            }

            return GetText(ExpandTokens(message), new[] { GetFieldDisplayName() });
        }

        protected override ValidatorResult GetMaxValidatorResult()
        {
            return GetFailedResult(ValidatorResult.Error);
        }

        protected virtual string ExpandTokens(string value)
        {
            if(string.IsNullOrWhiteSpace(value))
            {
                return value;
            }

            string valueExpanded = value;
            Field field = GetField();
            if(field != null)
            {
                valueExpanded = valueExpanded.Replace("$fieldName", field.Name);
            }

            return valueExpanded;
        }
    }
}

The above abstract class inherits from Sitecore.Data.Validators.StandardValidator in Sitecore.Kernel.dll — this is the base class which most validators in Sitecore inherit from — and checks to see if the Item referenced in the field has presentation (this check is done in the HasPresentation() method which basically delegates to the Run() method on the Sitecore.Pipelines.HasPresentation.HasPresentationPipeline class).

The referenced Item is returned by the GetReferencedItem() method which must be defined by subclasses of the above class.

Further, I’m passing in the validator’s name and error message through parameters (the error message allows for $fieldName as a token, and the ExpandTokens() method replaces this token with the name of the field being validated).

I then created a subclass of the above to return the Item referenced in an Internal Link field:

using System;
using System.Runtime.Serialization;

using Sitecore.Data.Fields;
using Sitecore.Data.Items;

namespace Sitecore.Sandbox.Data.Validators.FieldValidators
{
    [Serializable]
    public class InternalLinkItemHasPresentationValidator : ReferencedItemHasPresentationValidator
    {
        public InternalLinkItemHasPresentationValidator()
        {
        }

        public InternalLinkItemHasPresentationValidator(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }

        protected override Item GetReferencedItem()
        {
            InternalLinkField internalLinkField = GetField();
            if (internalLinkField == null)
            {
                return null;
            }
            
            return internalLinkField.TargetItem;
        }
    }
}

Nothing magical is happening in the above class. The GetReferencedItem() method is casting the field to a Sitecore.Data.Fields.InternalLinkField instance, and returns the value of its TargetItem property.

Now that we have a class to handle Items referenced in Internal Link fields, we need another for General Link fields:

using System;
using System.Runtime.Serialization;

using Sitecore.Data.Fields;
using Sitecore.Data.Items;

namespace Sitecore.Sandbox.Data.Validators.FieldValidators
{
    [Serializable]
    public class GeneralLinkItemHasPresentationValidator : ReferencedItemHasPresentationValidator
    {
        public GeneralLinkItemHasPresentationValidator()
        {
        }

        public GeneralLinkItemHasPresentationValidator(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }

        protected override Item GetReferencedItem()
        {
            LinkField linkField = GetField();
            if (linkField == null)
            {
                return null;
            }
            
            return linkField.TargetItem;
        }
    }
}

The GetReferencedItem() method in the GeneralLinkItemHasPresentationValidator class above does virtually the same thing as the same method in the InternalLinkItemHasPresentationValidator class. The only difference is the GetReferencedItem() method in the class above is casting the field to a Sitecore.Data.Fields.LinkField instance, and returns the value of its TargetItem property.

I then had to map the above field validator classes to Validation Rule Items — these have the /sitecore/templates/System/Validation/Validation Rule template — in Sitecore:

The Internal Link field validator:

internal-link-validator

The General Link field validator:

general-link-validator-item

I then added two Internal Link fields on my Sample item template, and mapped the Internal Link field validator to them:

set-validator-on-internal-link-fields

I also created two General Link fields on my Sample item template, and mapped the General Link field validator to them:

set-validator-on-general-link-fields

Once I had the validators mapped to their specific fields, I went ahead and removed presentation from one of my test items:

no-presentation-bing

Before, the above item had these presentation components on it:

presentation-on-google-item

I then linked to my test items in my test fields. As you can see, there are errors on the “Bing” item which does not have presentation:

invalid-field-links

If you have any thoughts on this, or ideas for other field validators, please share in a comment.

Until next time, have a Sitecorelicious day! 😀

Restrict Certain Files from Being Attached to Web Forms for Marketers Forms in Sitecore

Last week I was given a task to research how to prevent certain files from being attached to Web Forms for Marketers (WFFM) forms: basically files that have certain extensions, or files that exceed a specified size.

I have not seen this done before in WFFM, so I did what comes naturally to me: I Googled! 🙂

After a few unsuccessful searches on the internet — if you have some examples on how others have accomplished this in WFFM, please share in a comment — I decided to dig into the WFFM assemblies to see what would be needed to accomplish this, and felt using custom WFFM field validators would be the way to go.

I thought having a custom validator to check the attached file’s MIME type would be a better solution over one that checks the file’s extension — thanks to Sitecore MVP Yogesh Patel for giving me the idea from his post on restricting certain files from being uploading into Sitecore by checking their MIME types — since a malefactor could attach a restricted file with a different extension to bypass the extension validation step.

That thought lead me to build the following custom WFFM field validator:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI.WebControls;

using Sitecore.Form.Core.Validators;
using Sitecore.Form.Web.UI.Controls;

namespace Sitecore.Sandbox.Form.Core.Validators
{
    public class RestrictedFileTypes : FormCustomValidator
    {
        public string MimeTypesNotAllowed
        {
            get
            {
                if (string.IsNullOrWhiteSpace(base.classAttributes["mimeTypesNotAllowed"]))
                {
                    return string.Empty;
                }

                return base.classAttributes["mimeTypesNotAllowed"];
            }
            set
            {
                base.classAttributes["mimeTypesNotAllowed"] = value;
            }
        }

        public RestrictedFileTypes()
        {
        }

        protected override bool OnServerValidate(string value)
        {
            IEnumerable<string> mimeTypesNotAllowed = GetMimeTypesNotAllowed();
            FileUpload fileUpload = FindControl(ControlToValidate) as FileUpload;
            bool canProcess = mimeTypesNotAllowed.Any() && fileUpload != null && fileUpload.HasFile;
            if (!canProcess)
            {
                return true;
            }

            foreach(string mimeType in mimeTypesNotAllowed)
            {
                if (AreEqualIgnoreCase(mimeType, fileUpload.PostedFile.ContentType))
                {
                    return false;
                }
            }

            return true;
        }

        private IEnumerable<string> GetMimeTypesNotAllowed()
        {
            if (string.IsNullOrWhiteSpace(MimeTypesNotAllowed))
            {
                return new List<string>();
            }

            return MimeTypesNotAllowed.Split(new []{',', ';'}, StringSplitOptions.RemoveEmptyEntries);
        }

        private static bool AreEqualIgnoreCase(string stringOne, string stringTwo)
        {
            return string.Equals(stringOne, stringTwo, StringComparison.CurrentCultureIgnoreCase);
        }
    }
}

Restricted MIME types are passed to the custom validator through a parameter named MimeTypesNotAllowed, and these are injected into a property of the same name.

The MIME types can be separated by commas or semicolons, and the code above splits the string along these delimiters into a collection, checks to see if the uploaded file — we get the uploaded file via the FileUpload control on the form for the field we are validating — has a restricted MIME type by iterating over the collection of restricted MIME types, and comparing each entry to its MIME type. If there is a match, we return “false”: basically the field is invalid.

If no MIME types were set for the validator, or no file was uploaded, we return “true”: the field is valid. We do this for the case where the field is not required, or there is a required field validator set for it, and we don’t want to interfere with its validation check.

I then mapped the above validator in Sitecore:

restricted-file-types-validator

After saving the above validator Item in Sitecore, I built the following validator class to check to see if a file exceeds a certain size:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI.WebControls;

using Sitecore.Form.Core.Validators;
using Sitecore.Form.Web.UI.Controls;

namespace Sitecore.Sandbox.Form.Core.Validators
{
    public class RestrictedFileSize : FormCustomValidator
    {
        public int MaxFileSize
        {
            get
            {
                int maxSize;
                if (int.TryParse(base.classAttributes["mimeTypesNotAllowed"], out maxSize))
                {
                    return maxSize;
                }

                return 0;
            }
            set
            {
                base.classAttributes["mimeTypesNotAllowed"] = value.ToString();
            }
        }

        public RestrictedFileSize()
        {
        }

        protected override bool OnServerValidate(string value)
        {
            FileUpload fileUpload = FindControl(ControlToValidate) as FileUpload;
            if (!fileUpload.HasFile)
            {
                return true;
            }

            return fileUpload.PostedFile.ContentLength <= MaxFileSize;
        }
    }
}

Just as we had done in the other validator, we grab the FileUpload from the form, and see if there is a file attached. If there is no file attached, we return “true”: we don’t want to say the field is invalid when the field is not required.

We then return whether the uploaded file is less than or equal to the specified maximum size in bytes — this is set on a parameter named MaxFileSize which gets injected into the MaxFileSize property of the validator instance.

I then registered the above validator in Sitecore:

restricted-file-size-validator

I then decided to create a custom WFFM field type for the purposes of mapping our validators above, so that we don’t enforce these restrictions on the “out of the box” WFFM “File Upload” field type:

restricted-file-upload-field-type

I then set the new field type on a file field I added to a test WFFM form:

set-custom-field-type

Let’s see how we did!

Let’s try to upload an executable that exceeds the maximum upload size limit:

big-exe-wffm-upload

As you can see both validators were triggered for this file:

big-exe-wffm-upload-errormessage

How about a JavaScript file? Let’s try to attach one:

js-wffm-upload

Nope, we can’t attach that file either:

js-wffm-upload-errormessage

How about an image that is larger than the size limit? Let’s try one:

big-image-wffm-upload

Nope, we can’t upload that either:

big-image-wffm-upload-errormessage

Let’s try an image that is under 100KB:

allowed-image-wffm-upload

Yes, we can attach that file since it’s not restricted, and the form did submit:

allowed-image-wffm-upload-no-errormessage

If you have any thoughts on this, or other ideas around preventing certain files from being attached to WFFM form submissions, please share in a comment.