This Blog Has Been Moved to blog.petersondave.com

This blog has been moved to http://blog.petersondave.com.

My new blog is running under Jekyll hosted on Github Pages.

Check it out and let me know what you think!


Examining a Multisite Sitecore xDb Configuration

With the introduction of The Experience Database (xDb) in Sitecore 7.5, MongoDB hosts the primary repository of web activity across Sitecore backed websites. Web visitors, now known as contacts, are captured along with each page view (Interactions in xDb) generated in a given browsing session. Much like its predecessor, DMS, the new xDb separates web activity by site.

When building upon xDb in a multi-site implementation, being aware of how Sitecore captures and processes this information is essential for a successful multisite configuration.

A Quick Look at Sitecore 7.5 with xDb

Contact Creation

Contacts are identified just as they were in Sitecore DMS.

A cookie, SC_ANALYTICS_GLOBAL_COOKIE, is created with a Guid uniquely identifying the contact. From there, a contact record is created. This contact will be referenced for the lifetime of the cookie as interactions are recorded against the contact.

Interactions

Site activity is captured as documents within Interactions. High level data points of Interactions include:

  • Site Name
  • Pages viewed with URL, item Guid
  • Visit page count total
  • Browser type
  • Screen resolution
  • Geo location data

While the structure of the data differs from DMS, as we’re now storing data as documents, commonality exists between the data points captured in DMS vs the new xDb structure.

Contact Merging

The main takeaway with xDb is Sitecore’s ability to find matching contacts and merge those contacts given a predefined value uniquely identifying visitors. In previous versions, visitors identified by their Global Session cookie were maintained as unique visitor records with DMS. Upon processing of analytics data during the SessionEnd event in xDb, contacts are merged, creating a single consolidated view of the customer.

Contact merging is useful in two specific areas:

  1. Multiple browsing sessions for a single site – Such as sharing a shopping cart between a session on a PC and transferring that session to a mobile device. For more information on this approach, See Nick Wesselman’s series of in-depth Sitecore 7.5 posts.
  2. Multisite Sitecore Implementation – Two sites sharing the same membership model, both uniquely identifying contacts in the same way.

A Multisite Example

Suppose we have a multisite implementation, Launch Sitecore and Launch SkyNet (our fictitious Launch Sitecore evil twin). Both sites follow Sitecore’s best practice recommendations for configuration in IIS, while also sharing MongoDB, Collection and Reporting databases.

For the purpose of this example, while the two sites share membership, Single Sign-On is not implemented, requiring the user to identify themselves on both sites. Having such a setup will show how the xDb implementation handles contact merging and the importance of a common contact identification strategy shared across all sites.

multisite

Browsing Session #1: Launch Sitecore

Browsing the site for the first time results in the creation of a Global Analytics cookie. If you’re familiar with DMS, this works in the same way as previous versions. The cookie is what xDb will use to tie contacts together for unique browsing sessions.

Session1

While browsing Launch Sitecore, suppose we login, recognizing the current browsing session as a single customer within our membership model. At the point in which the user is identified, the contact, who previously was anonymous is now labelled using the unique identifier. In this example, we’re using the username from the extranet domain.

session1-details

Notice how the previous page views (xDb interactions) are now tagged with the contact id of the logged in user. Launch Sitecore programmatically identifying the contact is below. The line of code we’re most interested in is Tracker.Current.Session.Identify(domainUser).

string name = Sitecore.Context.User.Profile.FullName;
if (name == String.Empty) name = Sitecore.Context.User.LocalName;
Tracker.Current.Contact.Tags.Add("Username", domainUser);
Tracker.Current.Contact.Tags.Add("Full name", name);

Tracker.Current.Contact.Identifiers.AuthenticationLevel = AuthenticationLevel.PasswordValidated;
Tracker.Current.Session.Identify(domainUser);

Browsing Session #2: Launch SkyNet

Upon browsing Launch SkyNet, we have a completely different Global Session Guid in our cookie.

Session2

To Launch SkyNet, we’re anonymous and in no way connected to the user identified in Launch Sitecore. As soon as we login on Launch SkyNet, using the same logic to uniquely identify the contact (extranet domain username), Sitecore will flush the contact to xDb, updating the interactions with the contact id of the recognized user.

Any updates to facets, tags, counters, automation states, and contact attributes will be auto-merged within the MergeContacts pipeline processors.

Key takeaway: Contact consolidation occurs at the point in which the current tracking session is identified via Tracker.Current.Session.Identify().

session2-details

Summary

Regardless of how many sites you have running through a single instance of Sitecore, xDb processing and contact merging will consolidate contacts while maintaining the page interactions of each site. It is through this process we’re able to maintain a single view of the customer and maintain the customer lifetime value as seen by the Sitecore experience database.

Risks of splitting separate Sitecore instances to separate instances of xDb processing will result in a partial view of the customer and their relative engagement value for each site instance.


Sitecore 8 at a Glance

With the release of the Sitecore 8 MVP Technical Preview, many of the features showcased during Sitecore Symposium 2014 were made available for review. The focus of this post is to detail high-level changes between Sitecore 7.5 and the technical preview of Sitecore 8.

Many new components were delivered in the new version of Sitecore, as well as, renaming of existing features and packaging of popular modules used in pre-Sitecore 8 versions.

Renamed Components

  • Page Editor = Experience Editor
  • Marketing Center = Marketing Control Panel
  • Email Campaign Manager = Email Experience Manager

New Components

  • Experience Analytics
  • Experience Profile
  • Experience Optimization
  • List Manager
  • Path Analyzer
  • App Center
  • Executive Dashboard

New Features

  • Versioned Layouts
  • Web API Services (SPEAK components and building applications dependent upon Sitecore data)

Existing Features Packaged with Sitecore 8

General Look and Feel

The look and feel of the client is much improved. After the initial load, performance feels much quicker than Sitecore 7.5 and previous versions. The Launch Pad icon at the top of the page comes in very handy when wanting to switch between the new Sitecore 8 components and content editing, or experience editing; features that you’re already used to using in pre-8 installations.

Moving through the various steps of editing content, publishing, workflow, etc. match that of previous versions — just in a different layout. Everything is pretty much where you would expect it, but with a cleaner look and feel.

Obviously, with the addition of new Sitecore 8 features, you’ll rely heavily on the Launch Pad to access these components.

Sitecore Client Enhancements

Experience Editor

The experience editor is accessible from the Sitecore Experience Platform, the start menu or directly within the content editor ribbon. Just as before, the only difference being the rename of Page Editor to Experience Editor.

Experience Optimization

Testing is now exposed as Optimization in the experience editor section of the ribbon. Also accessible from the main Sitecore Experience Platform launch pad.
sitecore8-1

Content editors can establish tests, set goals and review performance reports.

sitecore8-2

Any tests created through Experience Optimization are saved under the Marketing Center Test Lab item buckets.

An example path for a home page test:

  • Item: /sitecore/system/Marketing Center/Test Lab/2014/10/01/01/11/Home 20141001T011146Z
  • Template: /sitecore/templates/System/Analytics/Testing/Test Definition

The Experience Optimization dashboard exposes reports enhancing new gamification concepts to the content editing and testing experience:

sitecore8-3

Workflow

When moving content changes through Workflow, new “Approve with Test” and “Approve without Test” are available, consistently reminding content editors and decision makers to consider A/B testing at a content level.
sitecore8-4

Email Experience Manager

The layout of the email campaign manager has changed, allowing for message creation and list importing available from the same menu

sitecore8-5

List Manager

The List manager allows for creation of lists from files, or manual entry from an empty list

sitecore8-6

Experience Analytics

New to Sitecore 8, the Experience Analytics feature brings together multivariate testing results, engagement scoring and overall site tracking statistics together in one location. The result is a powerful, new array of reports coupled with date range filtering and reporting facets.

sitecore8-7

Path Analyzer

One of the new features I’ve had the most fun with so far is the Path Analyzer. Clicking on the map, zooming in and out, selecting successful and least effective paths is really quite fun. The example below is leveraging Analytics data collected from a Launch Sitecore instance:

sitecore8-8

Selecting a path by clicking on the map, leading from a mail campaign to a login page yields the results below. Clicking on an element within the funnel of a selected path visit shows the exit path from that particular page:

sitecore8-9

You can also select a path from the lists in the right navigation, narrowing down on a particular path of high importance. For instance, the selected path below is one of the most efficient full paths:

sitecore8-10

Showing the funnels of the select path:

sitecore8-11

Social

Social is delivered out of the box with Sitecore 8. Previously, this required a separate installation of the Socially Connected module from SDN.

Available from the Page Editor, the “Messages” button under “Social” allowing content editors to create, edit and post a message on a target network. Take note of the new “Social” node in the content tree directly under the Sitecore root node.

sitecore8-13

Pipeline Changes

While new pipeline processors have been added to accommodate the new Sitecore 8 features, others have been moved with Content Testing and Experience Editing in mind. Below are the main areas of change with regards to pipeline processing:

  • Social related pipelines come configured out of the box.
  • FXM related hooks
  • Everything Analytics
  • Moving of existing pipelines, such as:
    • SPEAK components
    • Experience Editor
    • Web API request handling (higher up in the httpRequestBegin pipeline)
    • RenderField (immediately after httpRequestBegin)

Content Testing

Outside of Analytics, Sitecore.ContentTesting.config changes dominate the difference in Sitecore 8. Take a look at the config patch file. Content tests need to insert themselves in areas to override existing Sitecore rendering and processing handlers, such as:

  • Insert Renderings
  • RenderLayout
  • GetChromeData
  • Data Aggregation
  • Database Agent (background processing to determine if a test has reached statistical relevancy)
  • Content Testing provider (used by ItemManager)

The ItemManager’s default item provider is now the content testing item provider, which is essentially a wrapper of the existing Sitecore.Data.Managers.ItemProvider overriding GetItem(). It is here where other versions/variations of items used via Content Testing are obtained for rendering.

For more information regarding ItemManager and providers, check out this post on overall Sitecore data architecture.

Settings

One final interesting remark regarding configuration. You can now override the standard server time zone if needed via the ServerTimeZone setting.

In Closing…

Sitecore 8 offers a much better editing experience. It’s faster, cleaner and easier to work with. It’ll be interesting to see the various modifications to content tests and how, as developers, we can leverage this data to build modules and further enhance the client.


Verifying Sitecore Azure Deployments Against Specified Path or File Name Too Long Error

When running a Sitecore Azure deployment, ever see this error?

azure-build-error

“The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters”

This is not an error specific to Sitecore.

In an attempt to better understand the Sitecore Azure API and find the files/paths preventing a successful deploy, I built the Sitecore Azure Build Verifier. This tool will execute a dry-run of a Sitecore Azure deploy, alerting you of files and folders which fail validation.

Follow the instructions within the project ReadMe to install.

Configuring a Build

What I found interesting about this problem was the fact that I had installed a test version of Sitecore 7.2 with Azure 7.2 (rev 140411) under the path C:\inetpub\wwwroot_sandbox\Azure72 — not an overly verbose instance name or root path location.

For those of you new to Azure in Sitecore, specifying the target location for builds is located within an Environment item, under the field Build Folder.

environment-item
As you can see, the default is C:\inetpub\wwwroot_sandbox\Azure72\Data\AzurePackages. Sitecore Azure will, also by default, append additional folders to this path for each build instance executed from within the Azure module interface resulting in a build path such as:

C:\inetpub\wwwroot_sandbox\Azure72\Data\AzurePackages\(19) DevFabric\DevFabricLCd01Role01ScBaf20140816030333.cspkg\roles\SitecoreWebRole\approot

Quickly looking at the path above, you’ll notice references to:

  • Build instance
  • Environment name
  • Role instance and name
  • DNS host name

Quick Fix

Simply change the build folder to something with a shorter path. For example, set your path to C:\AzureBuild and you’re past this issue.

Verifying The Build

If you’re interested in which files/folders are preventing a successful deploy, install the Azure Build Verifier module. After installation, select an Azure Deployment item and right-click. Select the “Verify Deployment” option:

context-menu

Upon making the selection, you’ll be presented with a dialog box displaying the list of files and paths which exceed the limits previously seen in the failed deployment:

dialog

If you’re interested in the implementation, check out the GitHub repo. Dig into the artifacts processor or the main verify class leveraging the Sitecore.Azure library to resolve its sources prior to processing.


Working With The Sitecore Azure API: Settings and Content

Details within this post outline how to obtain basic settings, global variables and Azure module items from Sitecore’s Azure API. Included are also some pitfalls to be aware of when working with the library.

Settings

The Settings object allows for access to the different settings defined within Sitecore.Azure.config.

<settings>
<setting name="EnableEventQueues">
<patch:attribute name="value">true</patch:attribute>
</setting>
<setting name="Media.DisableFileMedia">
<patch:attribute name="value">true</patch:attribute>
</setting>
<setting name="Azure.EnvironmentsPath" value="/App_Data/AzureEnvironments" />
<setting name="Azure.HostedServicePropertiesUpdateTime" value="00:00:20" />
<setting name="Azure.DefaultUpdateCacheInterval" value="00:00:20" />
<setting name="Azure.VendorsBlobContainer" value="http://cloudsettings.sitecore.net/vendors" />
<setting name="Azure.VendorsStorage" value="/App_Config/AzureVendors" />
<setting name="Azure.Package.NoEncryptPackage" value="false" />
<setting name="Azure.RoleName" value="SitecoreWebRole" />
<setting name="Azure.UiRefreshInterval" value="00:00:05" />
<setting name="Azure.GetEnvironmentFileInfoBlobContainer" value="http://cloudsettings.sitecore.net/{version}-{locale}/GetEnvironmentFileInfo.html" />
<setting name="Azure.HttpRequestRetries" value="3" />
<setting name="Azure.TranslationsPath" value="/temp/AzureTranslations" />
<setting name="Azure.ManagerDisabled" value="false" />
<setting name="Azure.PublishTargetsContainer" value="publishtargets" />
<setting name="Azure.TrafficManager.Enable" value="true" />
<!-- Setup Logging level of Log trace of Sitecore Azure App.
Info - Show only common messages
Debug - Exception will be shown too. -->
<setting name="Azure.LoggingSettings.LogLevel" value="Debug" />
</settings>
view raw azure-config.xml hosted with ❤ by GitHub

Obtaining EnvironmentsPath, for example, is exposed through the following getter:

Sitecore.Azure.Configuration.Settings.EnvironmentDefinitionsPath;

Global Variables

The GlobalVariables collection allows for retrieval of any sc.variable defined within the site’s web.config.

<sitecore database="SqlServer">
<sc.variable name="dataFolder" value="C:\inetpub\wwwroot_sandbox\Azure72\Data"/>
<sc.variable name="mediaFolder" value="/upload"/>
<sc.variable name="tempFolder" value="/temp"/>

Obtaining a global setting:

var dataFolder = Settings.GlobalVariables["dataFolder"];

Configuration settings is not the only data exposed through the Settings class, you can also obtain:

  • Environment Definitions – A collection of all environments defined under the Azure module root item.
  • Environments Root – The Azure module root item.
  • Vendors – Collection of all vendors under the Azure module root item.

Obtaining Azure Item Wrappers

Looking up specific Azure items using the standard Sitecore.Data.Items.Item object is fine, however, the Sitecore Azure API offers an ORM of sorts to access this information. You can instantiate these objects by passing in their related Sitecore item.

The example below shows how you can obtain an Azure deployment item and walk up the tree by accessing each item’s parent until we reach the environment item root.

var db = Database.GetDatabase("master");
// Azure Deployment
var azureDeploymentDataItem = db.Items[deploymentId];
AzureDeploymentItem = new AzureDeploymentItem(azureDeploymentDataItem);
// Web Role
var roleDataItem = AzureDeploymentItem.InnerItem.Parent;
WebRoleItem = new WebRoleItem(roleDataItem);
// Farm
var farmDataItem = WebRoleItem.InnerItem.Parent;
FarmItem = new FarmItem(farmDataItem);
// Location
var locationDataItem = FarmItem.InnerItem.Parent;
LocationItem = new LocationItem(locationDataItem);
// Environment
var environmentDataItem = LocationItem.InnerItem.Parent;
EnvironmentItem = new EnvironmentItem(environmentDataItem);
view raw azure-wrapper.cs hosted with ❤ by GitHub

Obtaining Specific Azure Items

To obtain Azure specific objects, follow the patterns below to access. Take note of the pitfalls section when considering this approach.

Environment

There are multiple ways to obtain an environment from the API. You can get all environment definitions:

var environments = Settings.EnvironmentDefinitions;

Explicitly target an enviornment type. Using a local emulator as an out-of-the-box example:

var environmentDefinition = Settings.EnvironmentDefinitions.GetEnvironment("Local Emulator");
var environment = Sitecore.Azure.Deployments.Environments.Environment.GetEnvironment(environmentDefinition);
view raw azure-env-type.cs hosted with ❤ by GitHub

Local Emulator is defined in the Azure Environment configuration file under App_Data\AzureEnvironments\

<?xml version="1.0" encoding="utf-16"?>
<EnvironmentDataStorage xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<EMail>devfabric@sitecore.net</EMail>
<EnvironmentId>fe6d565e-289b-4076-a089-3588321a836c</EnvironmentId>
<EnvironmentType>Local Emulator</EnvironmentType>
...
view raw azure-env-config.xml hosted with ❤ by GitHub

Location

Once an environment is obtained, getting an instance of a location is rather simple. You can Explicitly get a location:

var location = environment.GetLocation("localhost");

Iterate over a collection of locations:

var locations = environment.Locations;

Farm

Similar to the approaches above, a single farm can be obtained by deployment type:

var farm = location.GetFarm("Delivery01", DeploymentType.ContentDelivery);

as a collection:

var farms = location.Farms;

Web Role

Obtain a single instance by name:

var role = farm.GetWebRole("Role01");

or assuming Role01 exists for a common Azure configuration:

var role = farm.WebRole01;

as a collection:

var roles = farm.WebRoles;

Azure Deployment

Obtain a single instance by deployment type:

var deployment = role.GetDeployment(DeploymentSlot.Production);

as a collection:

var deployments = role.Deployments;

Deployment Settings

The deployment object exposes deployment settings from the API as well. In the example below, we leverage the FilePathFilter object to access deployment item settings:

public string GetExcludedDirectories()
{
var environment = Sitecore.Azure.Deployments.Environments.Environment.GetEnvironment(Settings.EnvironmentDefinitions.GetEnvironment("Local Emulator"));
var location = environment.GetLocation("localhost");
var farm = location.GetFarm("Delivery01", DeploymentType.ContentDelivery);
var role = farm.WebRole01;
var deployment = role.GetDeployment(DeploymentSlot.Production);
// get values from field "Exclude Directories"
return deployment.FilePathFilter.ExcludeDirectories;
}

Exposing these fields:

azure-excludes-ref

Pitfalls

Item Creation on Object Instantiation

While the API itself is easy to use, it’s essential to understand what’s happening behind the scenes. Whenever instantiating an object deriving from the abstract class AzureEntity, if the requested item does not exist, the framework will automatically create the item for you. I’ve seen this happen specifically for location and farm items. Make sure your name matches the entry you’re looking for, otherwise, you’ll have extra items created under the Azure module branch in the content tree.

Consider a scenario of requesting a location of MyLocation. The Azure location item does not exist. I’ll run the following code:

public Location GetNonExistingLocation()
{
var environment = Sitecore.Azure.Deployments.Environments.Environment.GetEnvironment(Settings.EnvironmentDefinitions.GetEnvironment("Local Emulator"));
return environment.GetLocation("MyLocation");
}

Suppose I then request the farm MyFarm which also does not exist in content:

public Farm GetNonExistingFarm()
{
var location = GetNonExistingLocation();
return location.GetFarm("MyFarm", DeploymentType.ContentDelivery);
}

This results in those items being created by the API:

azure-api-ref


Hacking Sitecore Web Forms with Razor Views for Marketers and Blade

Web Forms for Marketers provides flexibility on part of content editors to create and manipulate simple forms for collecting user data. While there’s a great deal of flexibility on the back-end, customizing the format and layout of the form can sometimes be a more difficult task.

Knowing what some of the limitations are regarding the rendered markup of Web Forms for Marketers, I wondered how difficult it would be to override the rendering of the form to allow drastic changes to the standard look-and-feel of the out-of-the-box implementation. Sure, content edits can add CSS classes to content, but I wanted to push the envelope and see how far we could go.

Disclaimer

Now, I’ll be the first to admit when you’re looking to use a feature like Web Forms for Marketers, it’s absolutely necessary to know the strengths and limitations prior to recommending a specific implementation plan. Working closely with clients and designers is critical to a successful implementation. One that can be managed well over time by content editors, but remains on the upgrade path for future updates by Sitecore.

Introducing Razor Views for Marketers

The goal of this project is simple. Override the rendering of Web Forms for Marketers forms to take full control of the rendered markup.

Purely out of research, Razor Views for Marketers was built to extend the rendering of the form. Content remains the same, as well as the structure and templates within Sitecore. Razor Views for Marketers simply replaces how forms are rendered and how validation is conducted against view models. Page editor also works.

Why Blade?

Razor Views for Marketers uses Blade to take advantage of MVC-style razor view templating, allowing us to leverage MVC editor templates and dynamic model binding. The razor views give complete control over how fields are rendered, as well as razor views for field sections and the form itself.

Example Form

As a proof of concept, I set out to replace the “Leave a Message” form. Out-of-the-box implementation renders the form as:

leave-a-message

With a Single-Line Text field rendering as:

<div id="main_0_centercolumn_0_form_EC97FD637B414CA48CEF55F2D4EA1916_field_1C1014623802498484904C0DACF7D6FB_scope" class="scfSingleLineTextBorder fieldid.%7b1C101462-3802-4984-8490-4C0DACF7D6FB%7d name.Your+name">
<label for="main_0_centercolumn_0_form_EC97FD637B414CA48CEF55F2D4EA1916_field_1C1014623802498484904C0DACF7D6FB" id="main_0_centercolumn_0_form_EC97FD637B414CA48CEF55F2D4EA1916_field_1C1014623802498484904C0DACF7D6FB_text" class="scfSingleLineTextLabel">Your name</label>
<div class="scfSingleLineGeneralPanel">
<input name="main_0$centercolumn_0$form_EC97FD637B414CA48CEF55F2D4EA1916$field_1C1014623802498484904C0DACF7D6FB" type="text" maxlength="256" id="main_0_centercolumn_0_form_EC97FD637B414CA48CEF55F2D4EA1916_field_1C1014623802498484904C0DACF7D6FB" class="scfSingleLineTextBox">
<span class="scfSingleLineTextUsefulInfo" style="display:none;"></span>
<span id="main_0_centercolumn_0_form_EC97FD637B414CA48CEF55F2D4EA1916_field_1C1014623802498484904C0DACF7D6FB6ADFFAE3DADB451AB530D89A2FD0307B_validator" class="scfValidator trackevent.%7bF3D7B20C-675C-4707-84CC-5E5B4481B0EE%7d fieldid.%7b1C101462-3802-4984-8490-4C0DACF7D6FB%7d inner.1" style="color:Red;display:none;">Your name must have at least 0 and no more than 256 characters.</span><span id="main_0_centercolumn_0_form_EC97FD637B414CA48CEF55F2D4EA1916_field_1C1014623802498484904C0DACF7D6FB070FCA141E9A45D78611EA650F20FE77_validator" class="scfValidator trackevent.%7b844BBD40-91F6-42CE-8823-5EA4D089ECA2%7d fieldid.%7b1C101462-3802-4984-8490-4C0DACF7D6FB%7d inner.1" style="color:Red;display:none;">The value of the Your name field is not valid.</span>
</div>
<span class="scfRequired">*</span>
</div>
view raw wffm-field.html hosted with ❤ by GitHub

Replacement Form
The Razor Views for Marketers Implementation, using the following set of Razor views:

@using System.Web.Mvc
@using System.Web.Mvc.Html
@using RazorViewsForMarketers.Helpers
@inherits BladeRazorRendering<RazorViewsForMarketers.Models.RazorViewForMarketersFormModel>
<h1>@Model.Form.Title</h1>
@if (Model.Form.ShowIntroduction)
{
<p>@Model.Form.Introduction</p>
}
@using (Html.BeginForm(null, null, FormMethod.Post, new { id = "razorViewForMarketers" }))
{
@Html.DisplayTextFor(model => model.SubmitMessage)
<div class="form">
@Html.EditorFor(model => model.Form.Sections)
</div>
<button type="submit" value="Submit" name="Command">Submit Form</button>
}
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
view raw rvfm-form.cshtml hosted with ❤ by GitHub

Sections, implemented with editor templates:

@using System.Web.Mvc.Html
@model RazorViewsForMarketers.Models.WffmSection
<div class="section">
@Html.EditorFor(model => model.Fields)
</div>
view raw rvfm-sections.cshtml hosted with ❤ by GitHub

and fields also have editor templates. Example of a Single-Line Text:

@using System.Web.Mvc.Html
@using RazorViewsForMarketers.Helpers
@inherits BladeRazorRenderingEditorTemplate<RazorViewsForMarketers.Models.Fields.SingleLineTextField>
<div class="field">
<!-- model binding fields -->
@Html.HiddenFor(model => model.Id)
@Html.HiddenFor(model => model.IsRequired)
@Html.Hidden("ModelType", Model.ModelType)
<!-- model binding fields -->
@BladeHtmlHelper.SitecoreLabel(Html, model => model)
@Html.TextBoxFor(model => model.Response)
@BladeHtmlHelper.RequiredIndicator(model => model)
<p>@Model.Information</p>
@Html.ValidationMessageFor(model => model.Response)
</div>
view raw rvfm-field.cshtml hosted with ❤ by GitHub

Through helper methods, we’re able to maintain Page Editor functionality while rendering Web Forms for Marketers fields. Hidden fields are necessary for dyanmic model binding on postback for the form.

Updated rendered output of the Single-Line Text field from the razor view above:

<div class="field">
<!-- model binding fields -->
<input id="Form_Sections_0__Fields_0__Id" name="Form.Sections[0].Fields[0].Id" type="hidden" value="1c101462-3802-4984-8490-4c0dacf7d6fb">
<input id="Form_Sections_0__Fields_0__IsRequired" name="Form.Sections[0].Fields[0].IsRequired" type="hidden" value="True">
<input id="Form_Sections_0__Fields_0__ModelType" name="Form.Sections[0].Fields[0].ModelType" type="hidden" value="RazorViewsForMarketers.Models.Fields.SingleLineTextField">
<!-- model binding fields -->
<label for="Form_Sections_0__Fields_0__field_Response">Your name</label>
<input id="Form_Sections_0__Fields_0__Response" name="Form.Sections[0].Fields[0].Response" type="text" value=""> *
</div>
view raw rvfm.html hosted with ❤ by GitHub

Implementation Notes

The Razor Views for Marketers core logic wraps the existing SimpleForm to perform operations such as:

  • Submitting the form
  • Collecting WFFM save actions
  • Collecting form field control results
  • Expose the form’s FormItem

The wrapper is essentially an empty shell of the form, allowing us to leverage the existing WFFM framework for submitting forms through the standard Sitecore WFFM pipelines. 

A field decorator encapsulates existing WFFM field control results for form processing.

Want More?

If you’re interested in this approach, check out the repo.

Recommendations on how to contribute are included. The framework supports model binding of all existing Web Forms for Marketers fields, however, views and validators have been created to accommodate only those fields within “Leave a Message” form.


How Do I Check if a Web Forms for Marketers Field is Required on Validation?

When within the context of a WFFM field custom validator, determining whether or not a field is required may not be a straight-forward task. Understand that within the context of the validator, there are two ways of obtaining an instance of a form:

1. Obtain an instance of the current SimpleForm control, find your field and check if any MarkerLabels are present

2. Obtain an instance of the WFFM form itself, find your field item and check if its Required field is checked

Approach 1: Obtain an Instance of the current SimpleForm

protected override bool OnServerValidate(string value)
{
var validatorSettings = GetValidatorSettingsItem();
Sitecore.Diagnostics.Assert.IsNotNull(validatorSettings, ValidationSettingsItemMissingMessage + ValidationSettingsItem);
// obtain an instance of the SimpleForm
var form = WebUtil.GetParent<SimpleForm>(this);
// obtain an instance of our field
var countryField = (CountryDropDownList)WebUtil.FindFirstOrDefault(form, c => c is CountryDropDownList && string.Compare((c as CountryDropDownList).Result.FieldName, validatorSettings.CountryFieldName) == 0);
if (countryField != null)
{
// is the field required?
bool isRequired = countryField.Requred.Any();
// continue handling further processing here...
}
return base.OnServerValidate(value);
}
view raw gistfile1.cs hosted with ❤ by GitHub

Here we’re getting an instance of the SimpleForm using WebUtil.GetParent<SimpleForm>(). From there, we’re searching for our field, also via WebUtil. One very important thing to keep in mind is that the instance of the form we’re getting is the rendered output of the form itself and not the actual form item in Sitecore content. This makes it quite difficult to determine the checked state of the required checkbox on our form field. Instead, we’re dependent upon doing an .Any() on the “Requred” property of our field to see if any RequiredWithMarkerValidator items exist.

Shout out to Mike Reynolds for the idea of running .Any() on the Requred property.

Approach 2: Obtain an Instance of the current WFFM Item

protected override bool OnServerValidate(string value)
{
var validatorSettings = GetValidatorSettingsItem();
Sitecore.Diagnostics.Assert.IsNotNull(validatorSettings, ValidationSettingsItemMissingMessage + ValidationSettingsItem);
// obtain an instance of the SimpleForm
var form = WebUtil.GetParent<SimpleForm>(this);
// lookup the actual form item
var formItem = Sitecore.Context.Database.Items[form.FormID];
// obtain the WFFM field
var countryField = formItem.Children[validatorSettings.CountryFieldId];
// get its required field value from the Checkbox field
Sitecore.Data.Fields.CheckboxField checkbox = countryField.Fields["Required"];
if (checkbox != null)
{
bool isRequired = checkbox.Checked;
// continue processing...
}
return base.OnServerValidate(value);
}
view raw gistfile1.cs hosted with ❤ by GitHub

Here we’re still getting an instance of the SimpleForm using WebUtilGetParent<SimpleForm>(), but now we’re using the FormId property on that control to get the Sitecore ID of the form. We can then use this to get the item in Sitecore content to read the “Required” field value and determine its checked state.

Obviously, you’ll want to do additional null checking and most likely use an ORM like Synthesis to get strongly type objects mapping back to our WFFM forms and field items.


Building a Single Page Application with AngularJS and Sitecore: Part 2

In the second and final in a series of posts related to AngularJS and Sitecore, we’ll explore Angular in a Sitecore context. By running Angular within a Sitecore instance, we’re pairing the speed of Angular with dynamic rendering of Sitecore, opening our discussion to multiple interesting scenarios.

SPA Integration with Sitecore

Suppose we want more than just Sitecore content in our SPA, we also want Sitecore renderings. Our previous example ran as a SPA with a direct line to Sitecore by way of the Sitecore Item Web API. Building on the previous example, lets now have the start page of our SPA as the home item of a new Sitecore site within our instance. We’ll then define content which lets us:

  1. Use renderings for content outside of our views (static content for all pages across our SPA).
  2. Use Sitecore items as views in our Angular routing.

angular-integrated

Note: Since we’re building off the previous example, we’re still using the Sitecore Item Web API. This could easily be changed to other methods, such as directly serializing objects to JSON.

Looking at Sitecore, this is our configuration:

tree

  1. Callouts – Ads displayed below our Angular views (labeled as “Heading Callout” above).
  2. Profiles – Data returned by our Web API calls.
  3. Views – It’s here where we’ll be swapping out our Angular views for Sitecore items.

Looking at the presentation details of our views, you’ll notice they don’t have the default layout used by our SPA, as this would cause a nested default layout effect within our application. Instead, we define a “PartialView”.

partial-view

The ParialView layout paired with an allprofiles rendering essentially makes this the equivalent of the allprofiles view in our previous example. The difference, however, is now our views are powered by Sitecore. We can insert other renderings, content, etc.

PartialView Layout:

The only change required is to wire everything up. We can do this rather easily by altering our routes to now point at our Sitecore views instead of html files on the file system. Since we’re in the context of a Sitecore instance, Sitecore properly handles the request serving only the content for the views requested.

function getRoutes() {
return [
{
url: '/profiles',
config: {
templateUrl: 'Views/allprofiles',
controller: 'allprofiles'
}
}, {
url: '/profiles/:profileId',
config: {
templateUrl: 'Views/modifyprofile',
controller: 'modifyprofile'
}
}, {
url: '/',
config: {
templateUrl: 'Views/main',
controller: 'main'
}
}];
}
view raw gistfile1.js hosted with ❤ by GitHub

Technical Considerations

Caching

Angular is fast because only specific areas of the DOM are manipulated, but we have to be aware of caching issues. Any personalized areas of content or special rendering rules may very well be ignored.

When publishing updates to our views, we want to be sure we’re getting the latest and greatest version. We can force the application to reload views by appending the item version number as a query string parameter to our view. This will force a refresh through our routing.

function getRoutes() {
return [
{
url: '/profiles',
config: {
templateUrl: 'Views/allprofiles?v=4',
controller: 'allprofiles'
}
}, {
url: '/profiles/:profileId',
config: {
templateUrl: 'Views/modifyprofile?v=6',
controller: 'modifyprofile'
}
}, {
url: '/',
config: {
templateUrl: 'Views/main?v=2',
controller: 'main'
}
}];
}
view raw gistfile1.js hosted with ❤ by GitHub

DMS/Tracking

Out of the box configuration, DMS will track with every page request. Since our SPA remains on the same page for each request, we’re not going to be actively tracking each action by the user. Of course, we could implement a solution where we’re firing client-side events to our DMS solution.

Page Editor

Simply won’t work. Manipulating content or rendering placement within the SPA is not possible through the page editor.

Workflows

Tests using basic workflows failed on HTML validation. The extra Angular directives caused failures upon attempting to submit for approval.

Views in Sitecore Content

Our views are publically accessible with a PartialView layout. We want to be sure these don’t make their way out to be indexed, as they don’t have a full page layout associated with them. We want to be sure these are not crawled and no pages link back to these outside the context of our SPA.

Just Because You Can, Doesn’t Mean You Should

While we all love to push the boundaries of what Sitecore can offer and building solutions while incorporating other technologies, it’s important to be aware of the pros versus the cons. Building a SPA within the context of a Sitecore instance, while an interesting discussion, may bring about more issues than problems its solving.

For me at least, if someone were to suggest a similar approach, it would have to be quite convincing. In almost all cases, all we’re going to need is access to Sitecore content. If all you need is content, you may want to take a different route (no pun intended).

Alternate Approach: Angular Views in Sitecore

If all we’re after is Sitecore renderings used in conjunction with Angular, why not simply use Angular views? Why add the complexity of a SPA, if we can achieve our goal by building dynamic forms with Angular. Using Angular views, without Angular routing, we now regain control over the issues listed above under Technical Considerations.

Conclusion

Walking through each of these posts and scenarios using Angular in a Sitecore context was fun. I encourage anyone interested in using Angular to take into account all points covered in both posts. Hopefully these experiments assist the community and other developers looking to leverage Angular in future projects.

For the complete solution, including routing, Sitecore field mappings and all views and controllers, go here: https://github.com/PetersonDave/SinglePageAppDemo/tree/with-sitecore-integrated.


Building a Single Page Application with AngularJS and Sitecore: Part 1

During the November meetup of the Philadelphia Area Sitecore User Group, we explored the possibilities of using Angular with Sitecore. Two options were explored. The first, building a SPA with Angular and Sitecore Item Web API and finally, Integrating Angular into a Sitecore instance. This post, the first in a series of two related posts, discusses the Sitecore Item Web API implementation.

SPA with Sitecore Item Web API

Building a Single Page Application where we need content from Sitecore, the Sitecore Item Web API is a perfect option. We’re able to access our Sitecore instance via GET requests, obtaining Sitecore items serialized as JSON. While adding a dependency on our SPA, we can contain the dependency as single point of entry and distribute the content across view models.

angular-web-api

In our SPA demo site, we use an Angular factory as the entry point into our Sitecore instance by way of the Sitecore item Web API. Angular factories allow us to obtain data and reuse it across multiple controllers and routes. The data is saved in an array through get() and made available via getProfiles():

app.factory('profileservice', function ($http) {
var items = {};
var myProfileService = {};
myProfileService.addItem = function (item) {
items.push(item);
};
myProfileService.removeItem = function (item) {
var index = items.indexOf(item);
items.splice(index, 1);
};
myProfileService.getProfiles = function () {
return items;
};
myProfileService.update = function (itemid, params) {
console.log(params);
var url = '-/item/v1/?sc_itemid=' + itemid;
$http.put(url, params);
};
myProfileService.get = function (callback) {
var url = '-/item/v1/?scope=s&query=/sitecore/content/Home/Repository/*';
$http.get(url)
.then(function (res) {
items = res.data.result.items;
callback();
});
};
return myProfileService;
});
view raw gistfile1.js hosted with ❤ by GitHub

Note: For this demo, the get request is calling the current site, as the current site is running as the Sitecore instance. This can easily be changed to a non-relative URL path.

The factory, profileService, then serves content to our controller allprofiles. There are two methods to take note of here.:

  1. vm.load() – Makes the GET request to our Sitecore instance, populating the profiles array within our profileService.
  2. populateRepository() – Our callback method which profileService calls upon successfully loading profiles from the Sitecore Item Web API. A repository property of our model is populated here.
(function () {
'use strict';
// Controller name is handy for logging
var controllerId = 'allprofiles';
// Define the controller on the module.
// Inject the dependencies.
// Point to the controller definition function.
angular.module('app').controller(controllerId,
['$scope', '$http', 'profileservice', allprofiles]);
function allprofiles($scope, $http, profileservice) {
var vm = this;
vm.newprofile = {};
vm.profileService = profileservice;
vm.load = function () {
profileservice.get(populateRepository);
};
function populateRepository() {
vm.repository = profileservice.getProfiles();
}
}
})();
view raw gistfile1.js hosted with ❤ by GitHub

Finally, within our view, we access the data and use Angular’s awesome databinding to populate our view from our view model:

<div data-ng-controller="allprofiles as vm">
<p><a class="btn btn-primary btn-lg" ng-click="vm.load();">Load Data</a></p>
<div class="row" ng-repeat="profile in vm.repository">
<div class=" col-md-1">
<a class="btn btn-danger btn-small" href="#profiles/{{profile.ID}}">Edit</a>
</div>
<div class="col-md-1">{{profile.DisplayName}}</div>
<div class="col-md-6">{{profile.ID}}</div>
</div>
</div>
view raw gistfile1.html hosted with ❤ by GitHub

The “Load Data” link invokes our vm.load() method, while div class “row” is repeated for each item returned in our view model’s repository property.

Technical Considerations

Security

When making Sitecore Item Web API calls, if you’re planning on making cross-domain requests, you may run into some issues. While I have seen some recommended solutions, I have not tried implementing any.

Accessing Sitecore content adds additional configuration to ensure content can be accessed by your application. In our example, we’re saving our credentials within $httpprovider’s default header. Any GET request to our Sitecore instance are accessed as our demo site’s Admin user:

app.config(
...
'$httpProvider', function($httpProvider) {
$httpProvider.defaults.headers.put = { 'X-Scitemwebapi-Username': 'admin' };
$httpProvider.defaults.headers.put = { 'X-Scitemwebapi-Password': 'b' };
});
view raw gistfile1.js hosted with ❤ by GitHub

Note: for obvious reasons, do not use your admin account. This is for demo purposes only.

Performance

While it might seem difficult to overload your Sitecore instance from requests coming from a SPA, be aware of how your SPA accesses your Sitecore instance. You would never want to have your method which invokes GET requests within any repeated elements, such as our ng-repeat element.

Conclusion

Accessing Sitecore content using the Sitecore Item Web API is very easy and works exceptionally well within a SPA or application using the AngularJS framework.

For the complete solution, including routing, Sitecore field mappings and all views and controllers, go here: https://github.com/PetersonDave/SinglePageAppDemo/tree/with-sitecore-webapi.


Trigger Google Analytics Events on Sitecore Web Forms for Marketers Submit Actions

Suppose we wan to record an event in Google Analytics when successfully submitting forms. Before triggering our Google Analytics event, we first need to understand the different success modes:

  1. Redirect – After successfully passing form validation, the request is redirected to a specified success page.
  2. Success Message – After successfully passing form validation, a success message is rendered on the page after the post back, keeping the user on the same page.

Whichever success mode we choose, we need a client-side event to be triggered via our Google Analytics API. One commonality between the different success modes is the rendering logic for obtaining the success message in content and rendering on our WFFM form. We’re going to take advantage of this logic to inject our client-side event at the end of the success message.

The solution involves the following steps:

  1. Defining our Google Analytics Tracking Save Action
  1. Adding the Google Analytics Tracking Save Action to a form
  2. Modifying the forms success action pipeline to inject the tracking script
  1. Let Sitecore WFFM handle the rest

Success Action

We’ll use  a custom Save Action to hold our client-side script and format it to include any appropriate attributes for our tracking code.

Save actions are saved under: /sitecore/System/Modules/Web Forms for Marketers/Settings/Actions/Save Actions/

Image

Since this is all client-side, the goal of this event is to reuse and attach the script to appropriate forms by way of a Save Action. The save action will no be doing any server-side actions. As you can see, the code for this save action is empty:

public class RecordGoogleAnalyticsSubmitAction : ISaveAction, ISubmit
{
public void Execute(ID formid, AdaptedResultList fields, params object[] data)
{
return;
}
public void Submit(ID formid, AdaptedResultList fields)
{
return;
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

Once created, add the save action to any forms which require tracking events on submit within Google Analytics.

public class RecordGoogleAnalyticsSubmitAction : ISaveAction, ISubmit
{
public void Execute(ID formid, AdaptedResultList fields, params object[] data)
{
return;
}
public void Submit(ID formid, AdaptedResultList fields)
{
return;
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

Pipeline Processor

In the WFFM config include, there are two pipeline processors for each Success Mode. We’re going to replace FormatSuccessMessage with our own to append the script to the end of the success message. The success message will be formatted for both success modes (redirect and show message).

<successAction>
<processor type="Sitecore.Form.Core.Pipelines.SuccessRedirect, Sitecore.Forms.Core"/>
<processor type="Custom.Pipelines.FormatSuccessMessage, Custom"/>
</successAction>
view raw gistfile1.xml hosted with ❤ by GitHub

The FormatSuccessMessage implementation is simple, we append our script to the success message:

public class FormatSuccessMessage
{
public void Process(SubmitSuccessArgs args)
{
var script = FormSaveActionUtilities.GetGoogleAnalyticsScriptFromSaveAction(args);
Assert.IsNotNull((object)args, "args");
if (args.Form == null)
return;
args.Result = args.Form.SuccessMessage + script;
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

The Google Analytics script is obtained by looking up the Save Action, if it exists on the form. Save actions are available in the SubmitSuccessArgs as XML. We’ll take that XML, parse it into a ListDefinition. From there, we can convert to items, commands and finally ActionItems. Once we find our action item in the list of ActionItems, we pull our script and format it properly.

public class FormSaveActionUtilities
{
public static string GetGoogleAnalyticsScriptFromSaveAction(SubmitSuccessArgs args)
{
var googleAnalyticsSubmitActionId = new ID("{617DD7D9-950B-4767-B40A-CEFA848B64C6}");
var script = string.Empty;
var lid = ListDefinition.Parse(args.Form.SaveActions).Groups[0].ListItems;
if (lid == null) return script;
var items = lid.Select(item => args.Form.Database.GetItem(item.ItemID));
var actions = items.Where(command => command != null)
.Select(command => new ActionItem(command))
.ToList();
var googleAnalyticsAction = actions.FirstOrDefault(action => action.ID == googleAnalyticsSubmitActionId);
if (googleAnalyticsAction != null)
{
script = string.Format(googleAnalyticsAction.Parameters, args.Form.Name);
}
return script;
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

On submit, the pipeline processor finds the script, formats it and appends it to the success message. Within the WFFM Simple Form, the form programmatically pulls our modified success message and renders within a literal, triggering our client-side tracking code.