Sunday, May 29, 2016

Sitecore WFFM MVC FileUploadField restrictions

Sitecore WFFM FileUploadField

There are already some blogpost around about custom FileUploadFields, but as things have changed a bit in the latest Web Forms for Marketers releases and certainly with the introduction of MVC in WFFM I thought it could be useful to share the custom field we created on a Sitecore 8.1 update-1 environment with MVC forms.

Let's start by creating some validation attributes...

Validation attributes


Validation for file size


We inherit from DynamicValidationBase and override ValidateFieldValue.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class LimitFileSizeAttribute : DynamicValidationBase
{
    private const string FileSizeLimitKey = "filesizelimit";
    private const string ErrorMessageKey = "filesizelimiterror";

    protected override ValidationResult ValidateFieldValue(IViewModel model, object value, ValidationContext validationContext)
    {
        // Required attribute should handle null value
        if (value == null)
        {
            return ValidationResult.Success;
        }

        var fileBase = value as HttpPostedFileBase;
        if (fileBase == null)
        {
            return new ValidationResult(ErrorMessage);
        }

        var fileUploadField = validationContext.ObjectInstance as FileUploadField;
        var limit = GetLimit(fileUploadField);

        if (limit == -1) // Limit not set
        {
            return ValidationResult.Success;
        }

        if (fileBase.ContentLength > limit)
        {
             return new ValidationResult(GetErrorMessage(fileUploadField));
        }

        return ValidationResult.Success;
    }
 
    private static int GetLimit(IViewModel field)
    {
        if (field == null || !field.Parameters.ContainsKey(FileSizeLimitKey))
        {
            return -1;
        }

        var parameter = field.Parameters[FileSizeLimitKey];
        int intValue;
        if (int.TryParse(parameter, out intValue))
        {
            return intValue;
        }

        return -1;
    }

    private string GetErrorMessage(IViewModel field)
    {
        if (field != null && field.Parameters.ContainsKey(ErrorMessageKey))
        {
            return field.Parameters[ErrorMessageKey];
        }

        return ErrorMessage;
    }
}

The size limit and the error message are retrieved from the parameters on the field.

Validation for allowed extensions

We can create a similar class to validate the extension by using the Path.GetExtension(fileBase.FileName). Of course, other validations are also possible.


MVC Restricted FileUploadField

We inherit from the base FileUploadField in Sitecore.Forms.Mvc.ViewModels.Fields and override the "Value" property, just to add the custom created attributes.

public class RestrictedUploadField : Sitecore.Forms.Mvc.ViewModels.Fields.FileUploadField
{
    [LimitAllowedExtensions]
    [LimitFileSize]
    public override HttpPostedFileBase Value { get; set; }
}



Register in Sitecore

The new custom field needs to be registered in Sitecore:



Usage
Now we can start using our field in a mvc form. Do not forget to fill in the necessary parameters and/or Localized parameters - in case of our example code with the file size limit these are:
  • Parameters : <FileSizeLimit>10485760</FileSizeLimit>
  • Localized parameters : <FileSizeLimitError>File size must be under 10 mb</FileSizeLimitError>

I did not present you a complete FileUpload module here, but the given examples should provide enough information to get you started in your quest to create a customized file upload field in Sitecore WFFM with MVC.

Thursday, May 26, 2016

Sitecore item buckets view settings

Item buckets and the content editor

You have probably heard and seen item buckets already. The basic information can be found on Sitecore's documentation site. In the content editor, the items in a bucket are hidden by default (the documentation shows you how to view them anyway if you want) and the editor is encouraged to use the search. After searching, the results are displayed and the editor can pick an item to edit. By default, this item opens in a tab:

The tab... nested

Once your editor has selected an item, he will get a tab with a nested view. Inside the tab is the ribbon for the current item. Although this all works, we had some editors that got confused. They used the top ribbon to start a publish, hereby publishing the whole bucket. 
So we went looking for options to tweak this, and we found them. In the box.  

Item bucket settings

At "/sitecore/system/Settings/Buckets/Item Buckets Settings" you will find a field called "Show Search Results In". The default value is "New Tab".
The options are:
  • New Tab : [default value] a selected items opens in a tab
  • New Tab Not Selected: as new tab without selection
  • New Content Editor: this might be the one you want.. it will open the selected item not in a new tab, but in a new editor window. If you are in the desktop mode, you will get this window full size and can switch between the windows through the bottom bar. In the content editor mode the new window will not be full sized when created, and will look more like an overlay.
Note that these settings are for all buckets in the system and for all users, so you might need to talk to multiple editors to ask there preference.

Enjoy, you just might have made your editors happy!

Friday, May 20, 2016

WFFM SendMail message routing

Sitecore WFFM SendMail SaveAction

People who have been working with Sitecore WebForms for Marketers probably all know the SendMail save action to send an email after submitting the form. The mail can contain data entered in the mail and can be send to a fixed address or an address provided in the form. But.. sometimes you do want/need your form to send this email to a different email address based on the value chosen in a dropdown list (e.g. the department the visitor wants to contact).

Custom routing

The code I will share here will demonstrate this routing scenario, where the administrators could define in the list of departments the corresponding email address(es). It can be used as a base for similar situations as well. [We are using Sitecore 8.1 update 1]

The implementation

Our implementation works on a value entered in the "To" field of the mail template. If that value contains a fixed string and a guid pointing towards the dropdown field to identify the recipient we check if we can find the value of that field in a defined folder. The found item will give us the recipients' email-address(es) in one of the fields.

The code

Lets have a look at the code (stripped a bit, I removed the logging to just keep the main points here):

public void Process(ProcessMessageArgs args)
{
 if (args == null || args.To == null) { return; }

 var toEmail = args.To.ToString();
 if (!toEmail.Contains(EmailKey)) { return; }

 var fieldIdValue = toEmail.Replace(EmailKey, string.Empty);
 Guid fieldId;
 if (!Guid.TryParse(fieldIdValue, out fieldId)) { return; }

 var field = args.Fields.GetEntryByID(new ID(fieldId));
 if (field == null) { return; }

 var formDataItem = settingsRepository.GetSettingItem("Forms.FormDataRoot");
 if (formDataItem == null) { return; }

 var result = Sitecore.Context.Database.SelectSingleItem(string.Format(CultureInfo.InvariantCulture, "fast:{0}//*[@@name='{1}']", formDataItem.Paths.FullPath, field.Value));
 if (result == null) { return; }

 var toAddress = result["To"];
 if (string.IsNullOrEmpty(toAddress)) { return; }

 args.To.Clear();
 args.To.Append(toAddress);
}

The steps we perform in this function are:

  1. Check the arguments, especially the "To" value as that is our target
  2. We check if the "To" value contains a constant value EmailKey (if not, we skip)
  3. We check if the remainder of the "To" field is a Guid (if not, we skip)
  4. We try to find a field in the form with this Guid as ID
  5. We (try to) get the root of the values (using our SettingsRepository which is a custom piece of code - it just comes down to fetching an item from Sitecore)
  6. We do a fast query in that root to get the item that corresponds with the selected item (value) in the defined field (if not found, we skip)
  7. We get the value of the "To" field in the found item and use this as the new recipient for the mail by clearing and assigning the "To" field of the arguments. 

The config


<pipelines>
  <processMessage>
    <processor patch:before="processor[@method='SendEmail']" type="xx.Wffm.Pipelines.RouteMessage, xx.Wffm" />
  </processMessage>
</pipelines>

We add our class to the processMessage pipeline just before the mail is send.
And that's it. Nothing more to it.