Home » Sitecore MVC

Category Archives: Sitecore MVC

Inject Dependencies Into Sitecore MVC Razor Views Using a SitecoreHelper Extension Method

Last week before the Christmas holiday break, I came up with a solution similar to what I am going to show here except that I had used the Simple Injector Dependency Injection framework in that solution (if you would like me to add a blog post on that approach, please let me know in a comment).

I wanted a “quick and dirty” solution — well, I guess this is really in the eye of the beholder on whether the following is a good idea or not, but I’m throwing it out there just in case it helps out someone — where I could inject a dependency into an MVC Razor view via an HtmlHelper extension method, and in that solution, I used this approach for injecting an object instance which grabs values from a Sitecore Dictionary Domain. I am going to recreate this solution here though using the Sitecore Configuration Factory (we’re all not using Simple Injector in our solutions but the Sitecore Configuration Factory is available to all of us via the Sitecore API).

I first defined the following interface for class instances which return a value for a given dictionary entry key:

using Sitecore.Globalization;

namespace Sitecore.Sandbox.Dictionaries
{
    public interface ITranslator
    {
        string Text(string key, params object[] parameters);

        string Text(TranslateOptions options, string key, params object[] parameters);
    }
}

The following class implements the interface above:

using Sitecore.Diagnostics;
using Sitecore.Globalization;

namespace Sitecore.Sandbox.Dictionaries
{
    public class DomainDictionaryTranslator : ITranslator
    {
        public string Domain { get; set; }

        public virtual string Text(string key, params object[] parameters)
        {
            AssertDomainDictionary();
            Assert.ArgumentNotNullOrEmpty(key, "key");
            return Translate.TextByDomain(Domain, key, parameters);
        }

        public virtual string Text(TranslateOptions options, string key, params object[] parameters)
        {
            AssertDomainDictionary();
            Assert.ArgumentNotNullOrEmpty(key, "key");
            return Translate.TextByDomain(Domain, options, key, parameters);
        }

        protected virtual void AssertDomainDictionary()
        {
            Assert.IsNotNullOrEmpty(Domain, "The Domain must be set!");
        }
    }
}

Client code of the class above must supply the Domain name for the Sitecore Dictionary via the Domain property on the class.

Client code can then use either Text method by supplying a key for the lookup — both methods just delegate to the corresponding static methods defined on the Sitecore.Globalization.Translate class.

We now need classes that serve as Factories. I created the following interface for such classes:

namespace Sitecore.Sandbox.Helpers.Sitecore
{
    public interface IFactory
    {
        T CreateObject<T>(string configPath, bool assert) where T : class;
    }
}

The following class implements the above interface, and basically has one method that delegates to the static CreateObject() method on the Sitecore.Configuration.Factory class:

using Sitecore.Configuration;

namespace Sitecore.Sandbox.Helpers.Sitecore
{
    public class ConfigurationFactory : IFactory
    {
        public T CreateObject<T>(string configPath, bool assert) where T : class
        {
            return Factory.CreateObject(configPath, assert) as T;
        }
    }
}

Now, we need an extension method on the Sitecore.Mvc.Helpers.SitecoreHelper class — this lives in Sitecore.Mvc.dll — so that we can leverage the code defined above (check out Sitecore MVP Kevin Brechbühl’s blog post where he talks about this approach as well as another):

using Sitecore.Configuration;
using Sitecore.Diagnostics;
using Sitecore.Mvc.Helpers;

namespace Sitecore.Sandbox.Helpers.Sitecore
{
    public static class ConfigurationFactoryHelper
    {
        private static IFactory ConfigurationFactory { get; set; }

        static ConfigurationFactoryHelper()
        {
            ConfigurationFactory = CreateFactory();
        }

        public static T CreateObject<T>(this SitecoreHelper sitecoreHelper, string configPath, bool assert) where T : class
        {
            return ConfigurationFactory.CreateObject<T>(configPath, assert);
        }

        private static IFactory CreateFactory()
        {
            IFactory factory = Factory.CreateObject("configurationFactory", true) as IFactory;
            Assert.IsNotNull(factory, "The configurationFactory must be defined in configuration!");
            return factory;
        }
    }
}

We are instantiating an instance of the ConfigurationFactory class defined above via the Sitecore Configuration Factory, and then setting it on a static property in this class — all members and methods on this class must be static since it contains extension methods.

The CreateObject() method just delegates to the ConfigurationFactory class instance’s CreateObject() method and returns its value.

I then glued everything together using the following Sitecore patch configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <configurationFactory type="Sitecore.Sandbox.Helpers.Sitecore.ConfigurationFactory, Sitecore.Sandbox" singleInstance="true" />
    <domainDictionaryTranslator type="Sitecore.Sandbox.Dictionaries.DomainDictionaryTranslator, Sitecore.Sandbox" singleInstance="true">
      <Domain>MyCoolDictionary</Domain>
    </domainDictionaryTranslator>
  </sitecore>
</configuration>

Let’s see this in action!

First, we need a Dictionary Domain. I created one with the following folder with entries:

label-one-dictionary

label-two-dictionary

Now, we new a Razor view to make this work. I created the following, and mapped it to my home page’s presentation details:

@using Sitecore.Mvc;
@using Sitecore.Sandbox.Helpers.Sitecore
@using Sitecore.Sandbox.Dictionaries

@{
    var translator = Html.Sitecore().CreateObject<ITranslator>("domainDictionaryTranslator", true);
    if (translator == null)
    {
        return;
    }

    var labelOne = translator.Text("mycooldictionary.somelabels.labelone");
    var labelTwo = translator.Text("mycooldictionary.somelabels.labeltwo");
    if (string.IsNullOrWhiteSpace(labelOne) && string.IsNullOrWhiteSpace(labelTwo))
    {
        return;
    }
}

<div>
    @if (!string.IsNullOrWhiteSpace(labelOne))
    {
        <h2>@labelOne</h2>
    }

    @if (!string.IsNullOrWhiteSpace(labelTwo))
    {
        <h2>@labelTwo</h2>
    }
</div>

After building and deploying, I navigated to my home page. As you can see, the dictionary entry values display:

dictionary-values-displayed

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

Advertisement

Render a Custom General Link Field Attribute using a Custom Glass.Mapper Controller in Sitecore

In my previous posts — please be sure to read this post followed by this post and then this post before moving forward since you’ll need some context, and some of the code below is dependent on code in these previous posts — I showed two approaches for rendering a custom attribute — I called this attribute Tag in these posts and will continue to do so here — on a link set in a General Link field which is rendered using the Sitecore ORM Glass.Mapper.

In this post, I am going to share an approach on achieving the same but using a custom GlassController coupled with a class that implements the IGlassHtml interface — the code for this class can be found in this post.

First, we need a model to experiment with. I built the following class to serve as an example for this post:

using Sitecore.Data;

using Glass.Mapper.Sc.Configuration.Attributes;

using Sitecore.Sandbox.Glass.Mapper.Sc.Fields;

namespace Sitecore.Sandbox.Models
{
    public class SampleItemModel
    {
        [SitecoreId]
        ID ItemID { get; set; }

        public string Title { get; set; }

        public string Text { get; set; }

        [SitecoreField("Link One")]
        public TagLink LinkOne { get; set; }

        [SitecoreField("Link Two")]
        public TagLink LinkTwo { get; set; }
    }
}

Next, we need a ViewModel. I built the following as well to serve as an example for this post:

using System.Web.Mvc;

using Sitecore.Data;

using Glass.Mapper.Sc.Configuration.Attributes;

namespace Sitecore.Sandbox.Models.ViewModels
{
    public class SampleItemViewModel
    {
        public MvcHtmlString Title { get; set; }

        public MvcHtmlString Text { get; set; }

        public MvcHtmlString LinkOne { get; set; }

        public MvcHtmlString LinkTwo { get; set; }
    }
}

I then built the following subclass of Glass.Mapper.Sc.Web.Mvc.GlassController:

using System;
using System.Linq.Expressions;
using System.Web;
using System.Web.Mvc;

using Sitecore.Configuration;
using Sitecore.Diagnostics;

using Glass.Mapper.Sc;
using Glass.Mapper.Sc.Web.Mvc;
using Glass.Mapper.Sc.Web;
using GlassMapperSc = Glass.Mapper.Sc;

namespace Sitecore.Sandbox.Glass.Mapper.Sc.Web.Mvc.Controllers
{
    public abstract class SandboxGlassController : GlassController
    {
        private static IGlassHtmlFactory GlassHtmlFactory { get; set; }

        static SandboxGlassController()
        {
            GlassHtmlFactory = CreateGlassHtmlFactory();
        }

        public SandboxGlassController() 
            : this(GetContextFromHttp())
        {
        }

        protected SandboxGlassController(ISitecoreContext sitecoreContext) 
            : this(sitecoreContext, GlassHtmlFactory.CreateGlassHtml(sitecoreContext), new RenderingContextMvcWrapper(), null)
        {
        }

        public SandboxGlassController(ISitecoreContext sitecoreContext, IGlassHtml glassHtml, IRenderingContext renderingContextWrapper, HttpContextBase httpContext)
            : base(sitecoreContext, glassHtml, renderingContextWrapper, httpContext)
        {
        }

        protected static IGlassHtmlFactory CreateGlassHtmlFactory()
        {
            IGlassHtmlFactory factory = Factory.CreateObject("sandbox.Glass.Mvc/glassHtmlFactory", true) as IGlassHtmlFactory;
            Assert.IsNotNull(factory, "Be sure the configuration is correct in utilities/customAttributesAdder of your Sitecore configuration!");
            return factory;
        }

        protected static ISitecoreContext GetContextFromHttp()
        {
            try
            {
                return GlassMapperSc.SitecoreContext.GetFromHttpContext(null);
            }
            catch (Exception exception)
            {
                Log.Error("Failed to create SitecoreContext", exception, typeof(SandboxGlassController));
            }

            return null;
        }

        protected virtual MvcHtmlString Editable<T>(T model, Expression<Func<T, object>> field, object parameters = null)
        {
            return ConvertToMvcHtmlString(GlassHtml.Editable(model, field, parameters));
        }

        protected virtual MvcHtmlString RenderLink<T>(T model, Expression<Func<T, object>> link, object attributes = null, bool IsEditable = false)
        {
            return ConvertToMvcHtmlString(GlassHtml.RenderLink(model, link, attributes, IsEditable));
        }

        protected virtual MvcHtmlString ConvertToMvcHtmlString(string value)
        {
            return new MvcHtmlString(value);
        }
    }
}

I declared the above class as abstract since I don’t see how it could live on its own — none of its methods return ViewResult or ActionResult instances.

When an instance of the above class is created, an instance of a IGlassHtmlFactory — this is defined in my last post — is used to create an instance of a IGlassHtml which adds a Tag attribute and value to the link attributes when applicable. The Editable and RenderLink methods delegate to methods with the same signature on the IGlassHtml instance, and then transform the returned strings into MvcHtmlString instances via the ConvertToMvcHtmlString method so that the fields work in the Sitecore Experience Editor

I then built the following subclass of the above class for testing:

using System.Web.Mvc;

using Sitecore.Sandbox.Glass.Mapper.Sc.Web.Mvc.Controllers;
using Sitecore.Sandbox.Models;
using Sitecore.Sandbox.Models.ViewModels;

namespace Sitecore.Sandbox.Web.Mvc.Controllers
{
    public class SampleItemController : SandboxGlassController
    {
        public ViewResult MainContent()
        {
            SampleItemModel model = SitecoreContext.GetCurrentItem<SampleItemModel>();
            if(model == null)
            {
                return View();
            }

            SampleItemViewModel viewModel = new SampleItemViewModel
            {
                Title = Editable(model, x => x.Title),
                Text = Editable(model, x => x.Text),
                LinkOne = RenderLink(model, x => x.LinkOne, null, true),
                LinkTwo = RenderLink(model, x => x.LinkTwo, null, true)
            };

            return View(viewModel);
        }
    }
}

The MainContent method gets an instance of a SampleItemModel from the current Item — in this example these are fields on the home Item — and transforms this into a SampleItemViewModel instance using the protected methods defined on its base class. The SampleItemViewModel instance is then sent to the View.

I then whipped up the following Razor View so we can see some data on the front-end:

@model Sitecore.Sandbox.Models.ViewModels.SampleItemViewModel

@if(Model == null)
{
    return;
}

<div id="Content">
    <div id="LeftContent">
    </div>
    <div id="CenterColumn">
        <div id="Header">
            <img src="/~/media/Default Website/sc_logo.png" id="scLogo" />
        </div>
        <h1 class="contentTitle">
            @Model.Title
        </h1>
        <div class="contentDescription">
            @Model.Text
            <div>
                @Model.LinkOne
            </div>
            <div>
                @Model.LinkTwo
            </div>
        </div>
    </div>
</div>

There isn’t much going on in the above Razor View — it’s just displaying data from the ViewModel.

I am going to omit how I wired-up the Controller above with a Controller rendering in Sitecore. If you don’t know how to wire-up Controllers with Controller renderings in Sitecore, please watch this video by Martina Welander.

Let’s see if this works (works on my machine 😉 ).

First, I ensured I had Tag atrributes set on my two General Link fields on my home Item in Sitecore:

tag-attributes-are-set-controller

Once I built and deployed everything, I navigated to my homepage Item, and saw the following rendered HTML:

tag-attribute-rendered-controller

As you can see, it worked. 😀

If you have any questions/comments/thoughts, please leave a comment.

A 2nd Approach to Render a Custom General Link Field Attribute in a Sitecore MVC View Rendering via Glass.Mapper

In my previous post, I shared an approach for customizing the Glass.Mapper Sitecore ORM to render a custom attribute on a link defined in a General Link field (I called this attribute Tag and will continue to do so in this post).

In this post, I will share a second approach — an approach that extends the “out of the box” Html Helper in Glass.

Note: be sure to read this post first followed by my last post before reading the current post — I am omitting code from both of these which is used here.

I first created a class that implements the Glass.Mapper.Sc.IGlassHtml interface:

using System;
using System.Collections.Specialized;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq.Expressions;

using Sitecore.Collections;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Diagnostics;

using Glass.Mapper.Sc;
using Glass.Mapper.Sc.Fields;
using Glass.Mapper.Sc.Web.Ui;
using Utilities = Glass.Mapper.Utilities;

using Sitecore.Sandbox.Glass.Mapper.Sc.Attributes;
using Sitecore.Sandbox.Glass.Mapper.Sc.Fields;

namespace Sitecore.Sandbox.Glass.Mapper.Sc.Web.Mvc
{
    public class SandboxGlassHtml : IGlassHtml
    {
        private ICustomAttributesAdder attributesAdder;
        private ICustomAttributesAdder AttributesAdder
        {
            get
            {
                if (attributesAdder == null)
                {
                    attributesAdder = GetCustomAttributesAdder();
                }

                return attributesAdder;
            }
        }

        private IGlassHtml InnerGlassHtml { get; set; }

        public ISitecoreContext SitecoreContext
        {
            get
            {
                return InnerGlassHtml.SitecoreContext;
            }
        }

        public SandboxGlassHtml(ISitecoreContext sitecoreContext)
            : this(new GlassHtml(sitecoreContext))
        {
        }

        protected SandboxGlassHtml(IGlassHtml innerGlassHtml)
        {
            SetInnerGlassHtml(innerGlassHtml);
        }

        private void SetInnerGlassHtml(IGlassHtml innerGlassHtml)
        {
            Assert.ArgumentNotNull(innerGlassHtml, "innerGlassHtml");
            InnerGlassHtml = innerGlassHtml;
        }

        public virtual RenderingResult BeginRenderLink<T>(T model, Expression<Func<T, object>> field, TextWriter writer, object attributes = null, bool isEditable = false)
        {
            object attributesModified = AttributesAdder.AddTagAttribute(model, field, attributes);
            return InnerGlassHtml.BeginRenderLink(model, field, writer, attributesModified, isEditable);
        }

        public virtual string Editable<T>(T target, Expression<Func<T, object>> field, object parameters = null)
        {
            return InnerGlassHtml.Editable(target, field, parameters);
        }

        public virtual string Editable<T>(T target, Expression<Func<T, object>> field, Expression<Func<T, string>> standardOutput, object parameters = null)
        {
            return InnerGlassHtml.Editable(target, field, standardOutput, parameters);
        }

        public virtual GlassEditFrame EditFrame(string buttons, string path = null, TextWriter output = null)
        {
            return InnerGlassHtml.EditFrame(buttons, path, output);
        }

        public virtual GlassEditFrame EditFrame<T>(T model, string title = null, TextWriter output = null, params Expression<Func<T, object>>[] fields) where T : class
        {
            return InnerGlassHtml.EditFrame(model, title, output, fields);
        }

        public virtual T GetRenderingParameters<T>(NameValueCollection parameters) where T : class
        {
            return InnerGlassHtml.GetRenderingParameters<T>(parameters);
        }

        public virtual T GetRenderingParameters<T>(string parameters) where T : class
        {
            return InnerGlassHtml.GetRenderingParameters<T>(parameters);
        }

        public virtual T GetRenderingParameters<T>(NameValueCollection parameters, ID renderParametersTemplateId) where T : class
        {
            return InnerGlassHtml.GetRenderingParameters<T>(parameters, renderParametersTemplateId);
        }

        public virtual T GetRenderingParameters<T>(string parameters, ID renderParametersTemplateId) where T : class
        {
            return InnerGlassHtml.GetRenderingParameters<T>(parameters, renderParametersTemplateId);
        }

        public virtual string RenderImage<T>(T model, Expression<Func<T, object>> field, object parameters = null, bool isEditable = false, bool outputHeightWidth = false)
        {
            return InnerGlassHtml.RenderImage(model, field, parameters, isEditable, outputHeightWidth);
        }

        public virtual string RenderLink<T>(T model, Expression<Func<T, object>> field, object attributes = null, bool isEditable = false, string contents = null)
        {
            object attributesModified = AttributesAdder.AddTagAttribute(model, field, attributes);
            return InnerGlassHtml.RenderLink(model, field, attributesModified, isEditable, contents);
        }

        public virtual string ProtectMediaUrl(string url)
        {
            return InnerGlassHtml.ProtectMediaUrl(url);
        }

        protected virtual ICustomAttributesAdder GetCustomAttributesAdder()
        {
            return CustomAttributesAdder.Current;
        }
    }
}

In the above class, I’m using the Decorator Pattern — another Glass.Mapper.Sc.IGlassHtml instance (this is set to an instance of Glass.Mapper.Sc.GlassHtml by default — have a look at the public constructor above) is passed to the class instance and stored in a private property. Every interface-defined method implemented in this class delegates to the inner-IGlassHtml instance.

Since I’m only targeting links in this solution, I utilize a CustomAttributesAdder instance — this is a Singleton which I shared in my last post which is defined in the Sitecore configuration file further down in this post — in both RenderLink methods. The CustomAttributesAdder instance adds the Tag attribute name and value to the attributes collection when applicable. The modified/unmodified attributes collection is then passed to the RenderLink method with the same signature on the inner Glass.Mapper.Sc.IGlassHtml instance.

Now, we need a way to instantiate the above class. I decided to create the following interface for classes that create instances of classes that implement the Glass.Mapper.Sc.IGlassHtml interface:

using Glass.Mapper.Sc;

namespace Sitecore.Sandbox.Glass.Mapper.Sc.Web.Mvc
{
    public interface IGlassHtmlFactory
    {
        IGlassHtml CreateGlassHtml(ISitecoreContext sitecoreContext);
    }
}

I then built the following class which creates an instance of the SandboxGlassHtml class defined above:

using Sitecore.Diagnostics;

using Glass.Mapper.Sc;

namespace Sitecore.Sandbox.Glass.Mapper.Sc.Web.Mvc
{
    public class SandboxGlassHtmlFactory : IGlassHtmlFactory
    {
        public IGlassHtml CreateGlassHtml(ISitecoreContext sitecoreContext)
        {
            Assert.ArgumentNotNull(sitecoreContext, "sitecoreContext");
            return new SandboxGlassHtml(sitecoreContext);
        }
    }
}

There isn’t much going on in the the class above exception object instantiation — the above is an example of the Factory method pattern for those who are curious.

Now, we need an extension method on the ASP.NET MVC HtmlHelper instance used in our Razor views in order to leverage the custom Glass.Mapper.Sc.IGlassHtml class defined above:

using System.Web.Mvc;

using Sitecore.Configuration;
using Sitecore.Diagnostics;

using Glass.Mapper.Sc;
using Glass.Mapper.Sc.Web.Mvc;

using Sitecore.Sandbox.Glass.Mapper.Sc.Attributes;

namespace Sitecore.Sandbox.Glass.Mapper.Sc.Web.Mvc
{
    public static class SandboxHtmlHelperExtensions
    {
        private static IGlassHtmlFactory GlassHtmlFactory { get; set; }

        static SandboxHtmlHelperExtensions()
        {
            GlassHtmlFactory = CreateGlassHtmlFactory();
        }

        public static GlassHtmlMvc<T> SandboxGlass<T>(this HtmlHelper<T> htmlHelper)
        {
            IGlassHtml glassHtml = GlassHtmlFactory.CreateGlassHtml(SitecoreContext.GetFromHttpContext(null));
            Assert.IsNotNull(glassHtml, "glassHtml cannot be null!");
            return new GlassHtmlMvc<T>(glassHtml, htmlHelper.ViewContext.Writer, htmlHelper.ViewData.Model);
        }

        private static IGlassHtmlFactory CreateGlassHtmlFactory()
        {
            IGlassHtmlFactory factory = Factory.CreateObject("sandbox.Glass.Mvc/glassHtmlFactory", true) as IGlassHtmlFactory;
            Assert.IsNotNull(factory, "Be sure the configuration is correct in utilities/customAttributesAdder of your Sitecore configuration!");
            return factory;
        }
    }
}

In the SandboxGlass method above, we instantiate an instance of the IGlassHtmlFactory which is defined in Sitecore configuration (see the patch configuration file below) and use it to create an instance of whatever Glass.Mapper.Sc.IGlassHtml it is tasked to create (in our case here it’s an instance of the SandboxGlassHtml class defined above). This is then passed to a newly created instance of Glass.Mapper.Sc.Web.Mvc.GlassHtmlMvc.

I then glued all the pieces together using the following Sitecore patch configuration file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <controlSources>
      <source mode="on" namespace="Sitecore.Sandbox.Shell.Applications.ContentEditor" assembly="Sitecore.Sandbox" prefix="sandbox-content"/>
    </controlSources>
    <fieldTypes>
      <fieldType name="General Link">
        <patch:attribute name="type">Sitecore.Sandbox.Data.Fields.TagLinkField, Sitecore.Sandbox</patch:attribute>
      </fieldType>
      <fieldType name="General Link with Search">
        <patch:attribute name="type">Sitecore.Sandbox.Data.Fields.TagLinkField, Sitecore.Sandbox</patch:attribute>
      </fieldType>
      <fieldType name="link">
        <patch:attribute name="type">Sitecore.Sandbox.Data.Fields.TagLinkField, Sitecore.Sandbox</patch:attribute>
        </fieldType>
    </fieldTypes>
    <pipelines>
      <dialogInfo>
        <processor type="Sitecore.Sandbox.Pipelines.DialogInfo.SetDialogInfo, Sitecore.Sandbox">
          <ParameterNameAttributeName>name</ParameterNameAttributeName>
          <ParameterValueAttributeName>value</ParameterValueAttributeName>
          <Message>contentlink:externallink</Message>
          <Url>/sitecore/shell/Applications/Dialogs/External link.aspx</Url>
          <parameters hint="raw:AddParameter">
            <parameter name="height" value="300" />
          </parameters>
        </processor>
      </dialogInfo>
      <renderField>
        <processor patch:after="processor[@type='Sitecore.Pipelines.RenderField.GetInternalLinkFieldValue, Sitecore.Kernel']" 
                   type="Sitecore.Sandbox.Pipelines.RenderField.SetTagAttributeOnLink, Sitecore.Sandbox">
          <TagXmlAttributeName>tag</TagXmlAttributeName>
          <TagAttributeName>tag</TagAttributeName>
          <BeginningHtml>&lt;a </BeginningHtml>
        </processor>  
      </renderField>
    </pipelines>
    <sandbox.Glass.Mvc>
      <customAttributesAdder type="Sitecore.Sandbox.Glass.Mapper.Sc.Attributes.CustomAttributesAdder, Sitecore.Sandbox" singleInstance="true" />
      <glassHtmlFactory type="Sitecore.Sandbox.Glass.Mapper.Sc.Web.Mvc.SandboxGlassHtmlFactory, Sitecore.Sandbox" singleInstance="true" />
    </sandbox.Glass.Mvc>
  </sitecore>
</configuration>

Let’s see if this works.

For testing, I created the following Razor view — notice how I’m using the Html Helper instead of using the methods on the class the Razor view inherits from:

@inherits Glass.Mapper.Sc.Web.Mvc.GlassView<Sitecore.Sandbox.Models.ViewModels.ISampleItem>
@using Glass.Mapper.Sc.Web.Mvc
@using Sitecore.Sandbox.Glass.Mapper.Sc.Web.Mvc

<div id="Content">
    <div id="LeftContent">
    </div>
    <div id="CenterColumn">
        <div id="Header">
            <img src="/~/media/Default Website/sc_logo.png" id="scLogo" />
        </div>
        <h1 class="contentTitle">
            @Html.SandboxGlass().Editable(x => x.Title)
        </h1>
        <div class="contentDescription">
            @Html.SandboxGlass().Editable(x => x.Text)
            <div>
                @Html.SandboxGlass().RenderLink(x => x.LinkOne)
            </div>
            <div>
                @Html.SandboxGlass().RenderLink(x => x.LinkTwo)
            </div>
        </div>
    </div>
</div>

After building and deploying everything above, I made sure I had some tags defined on some General Link fields on my home Item in Sitecore:

tag-attributes-raw-values

I then navigated to my homepage; looked at the rendered HTML; and saw the following:

tag-attributes-rendered-SandboxGlassHtml

As you can see it worked. 🙂

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

Until next time, be sure to:

sitecore-all-the-things

😀

One Approach to Render a Custom General Link Field Attribute in a Sitecore MVC View Rendering via Glass.Mapper

In my previous post, I shared a way to add a custom attribute to the General Link field in Sitecore — in that post I called this attribute “Tag” and will continue to do so here — and also showed how to render it on the front-end using the Sitecore Link field control.

You might have been asking yourself when reading that last post “Mike, how would I go about getting this to work in the Sitecore ORM Glass.Mapper?” (well, actually, I planted a seed in that post that I was going to write another post on how to get this to work in Glass.Mapper so you might not have been asking yourself that at all but instead were thinking “Mike, just get on with it!”).

In this post, I am going to show you one approach on how to tweak Glass to render a Tag attribute in the rendered markup of a General Link field (I’m not going reiterate the bits on how to customize the General Link field as I had done in my previous post, so you might want to have a read of that first before reading this post).

I first created a custom Sitecore.Data.Fields.LinkField class:

using Sitecore.Data.Fields;

namespace Sitecore.Sandbox.Data.Fields
{
    public class TagLinkField : LinkField
    {
        public TagLinkField(Field innerField)
            : base(innerField)
        {
        }

        public TagLinkField(Field innerField, string runtimeValue)
            : base(innerField, runtimeValue)
        {
        }

        public string Tag
        {
            get
            {
                return GetAttribute("tag");
            }
            set
            {
                this.SetAttribute("tag", value);
            }
        }   
    }
}

An instance of this class will magically create a XML representation of itself when saving to the General Link field, and will also parse the attributes that are defined in the XML.

Next, we need a Glass.Mapper field like Glass.Mapper.Sc.Fields.Link but with an additional property for the Tag value. This sound like an opportune time to subclass Glass.Mapper.Sc.Fields.Link and add a new property to hold the Tag value 😉 :

using Glass.Mapper.Sc.Fields;

namespace Sitecore.Sandbox.Glass.Mapper.Sc.Fields
{
    public class TagLink : Link
    {
        public string Tag { get; set; }
    }
}

There’s nothing much in the above class except for an additional property for the Tag attribute value.

I then built the following Glass.Mapper.Sc.DataMappers.AbstractSitecoreFieldMapper for the TagLink:

using System;

using Sitecore.Data;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Links;
using Sitecore.Resources.Media;

using Glass.Mapper;
using Glass.Mapper.Sc;
using Glass.Mapper.Sc.Configuration;
using Glass.Mapper.Sc.DataMappers;
using Glass.Mapper.Sc.Fields;
using Utilities = Glass.Mapper.Sc.Utilities;

using Sitecore.Sandbox.Data.Fields;
using Sitecore.Sandbox.Glass.Mapper.Sc.Fields;

namespace Sitecore.Sandbox.Glass.Mapper.Sc.DataMappers
{
    public class SitecoreFieldTagLinkMapper : AbstractSitecoreFieldMapper
    {
        private AbstractSitecoreFieldMapper InnerLinkMapper { get; set;}

        public SitecoreFieldTagLinkMapper()
            : this(new SitecoreFieldLinkMapper(), typeof(TagLink))
        {
        }

        public SitecoreFieldTagLinkMapper(AbstractSitecoreFieldMapper innerLinkMapper, Type linkType)
            : base(linkType)
        {
            SetInnerLinkMapper(innerLinkMapper);
        }

        private void SetInnerLinkMapper(AbstractSitecoreFieldMapper innerLinkMapper)
        {
            Assert.ArgumentNotNull(innerLinkMapper, "innerLinkMapper");
            InnerLinkMapper = innerLinkMapper;
        }
        
        public override string SetFieldValue(object value, SitecoreFieldConfiguration config, SitecoreDataMappingContext context)
        {
            return InnerLinkMapper.SetFieldValue(value, config, context);
        }
        
        public override object GetFieldValue(string fieldValue, SitecoreFieldConfiguration config, SitecoreDataMappingContext context)
        {
            return InnerLinkMapper.GetFieldValue(fieldValue, config, context);
        }

        public override object GetField(Field field, SitecoreFieldConfiguration config, SitecoreDataMappingContext context)
        {
            if (field == null || field.Value.Trim().IsNullOrEmpty())
            {
                return null;
            }

            TagLink link = new TagLink();
            TagLinkField linkField = new TagLinkField(field);
            link.Anchor = linkField.Anchor;
            link.Class = linkField.Class;
            link.Text = linkField.Text;
            link.Title = linkField.Title;
            link.Target = linkField.Target;
            link.Query = linkField.QueryString;
            link.Tag = linkField.Tag;

            switch (linkField.LinkType)
            {
                case "anchor":
                    link.Url = linkField.Anchor;
                    link.Type = LinkType.Anchor;
                    break;
                case "external":
                    link.Url = linkField.Url;
                    link.Type = LinkType.External;
                    break;
                case "mailto":
                    link.Url = linkField.Url;
                    link.Type = LinkType.MailTo;
                    break;
                case "javascript":
                    link.Url = linkField.Url;
                    link.Type = LinkType.JavaScript;
                    break;
                case "media":
                    if (linkField.TargetItem == null)
                        link.Url = string.Empty;
                    else
                    {
                        global::Sitecore.Data.Items.MediaItem media =
                            new global::Sitecore.Data.Items.MediaItem(linkField.TargetItem);
                        link.Url = global::Sitecore.Resources.Media.MediaManager.GetMediaUrl(media);
                    }
                    link.Type = LinkType.Media;
                    link.TargetId = linkField.TargetID.Guid;
                    break;
                case "internal":
                    var urlOptions = Utilities.CreateUrlOptions(config.UrlOptions);
                    link.Url = linkField.TargetItem == null ? string.Empty : LinkManager.GetItemUrl(linkField.TargetItem, urlOptions);
                    link.Type = LinkType.Internal;
                    link.TargetId = linkField.TargetID.Guid;
                    link.Text = linkField.Text.IsNullOrEmpty() ? (linkField.TargetItem == null ? string.Empty : linkField.TargetItem.DisplayName) : linkField.Text;
                    break;
                default:
                    return null;
            }

            return link;
        }

        public override void SetField(Field field, object value, SitecoreFieldConfiguration config, SitecoreDataMappingContext context)
        {
            if (field == null)
            {
                return;
            }

            TagLink link = value as TagLink;
            if(link == null)
            {
                return;
            }

            Item item = field.Item;
            TagLinkField linkField = new TagLinkField(field);
            if (link == null || link.Type == LinkType.NotSet)
            {
                linkField.Clear();
                return;
            }
            
            switch (link.Type)
            {
                case LinkType.Internal:
                    linkField.LinkType = "internal";
                    if (linkField.TargetID.Guid != link.TargetId)
                    {
                        if (link.TargetId == Guid.Empty)
                        {
                            ItemLink iLink = new ItemLink(item.Database.Name, item.ID, linkField.InnerField.ID, linkField.TargetItem.Database.Name, linkField.TargetID, linkField.TargetItem.Paths.FullPath);
                            linkField.RemoveLink(iLink);
                        }
                        else
                        {
                            ID newId = new ID(link.TargetId);
                            Item target = item.Database.GetItem(newId);
                            if (target != null)
                            {
                                linkField.TargetID = newId;
                                ItemLink nLink = new ItemLink(item.Database.Name, item.ID, linkField.InnerField.ID, target.Database.Name, target.ID, target.Paths.FullPath);
                                linkField.UpdateLink(nLink);
                                linkField.Url = LinkManager.GetItemUrl(target);
                            }
                            else throw new Exception(String.Format("No item with ID {0}. Can not update Link linkField", newId));
                        }

                    }
                    break;
                case LinkType.Media:
                    linkField.LinkType = "media";
                    if (linkField.TargetID.Guid != link.TargetId)
                    {
                        if (link.TargetId == Guid.Empty)
                        {
                            ItemLink iLink = new ItemLink(item.Database.Name, item.ID, linkField.InnerField.ID, linkField.TargetItem.Database.Name, linkField.TargetID, linkField.TargetItem.Paths.FullPath);
                            linkField.RemoveLink(iLink);
                        }
                        else
                        {
                            ID newId = new ID(link.TargetId);
                            Item target = item.Database.GetItem(newId);

                            if (target != null)
                            {
                                MediaItem media = new MediaItem(target);

                                linkField.TargetID = newId;
                                ItemLink nLink = new ItemLink(item.Database.Name, item.ID, linkField.InnerField.ID, target.Database.Name, target.ID, target.Paths.FullPath);
                                linkField.UpdateLink(nLink);
                                linkField.Url = MediaManager.GetMediaUrl(media);
                            }
                            else throw new Exception(String.Format("No item with ID {0}. Can not update Link linkField", newId));
                        }
                    }
                    break;
                case LinkType.External:
                    linkField.LinkType = "external";
                    linkField.Url = link.Url;
                    break;
                case LinkType.Anchor:
                    linkField.LinkType = "anchor";
                    linkField.Url = link.Anchor;
                    break;
                case LinkType.MailTo:
                    linkField.LinkType = "mailto";
                    linkField.Url = link.Url;
                    break;
                case LinkType.JavaScript:
                    linkField.LinkType = "javascript";
                    linkField.Url = link.Url;
                    break;
            }

            if (!link.Anchor.IsNullOrEmpty())
            {
                linkField.Anchor = link.Anchor;
            }
                
            if (!link.Class.IsNullOrEmpty())
            {
                linkField.Class = link.Class;
            }
                
            if (!link.Text.IsNullOrEmpty())
            {
                linkField.Text = link.Text;
            }
                
            if (!link.Title.IsNullOrEmpty())
            {
                linkField.Title = link.Title;
            }
                
            if (!link.Query.IsNullOrEmpty())
            {
                linkField.QueryString = link.Query;
            }
                
            if (!link.Target.IsNullOrEmpty())
            {
                linkField.Target = link.Target;
            }

            if (!link.Tag.IsNullOrEmpty())
            {
                linkField.Tag = link.Tag;
            }
        }
    }
}

Most of the code in the GetField and SetField methods above are taken from the same methods in Glass.Mapper.Sc.DataMappers.SitecoreFieldLinkMapper except for the additional lines for the TagLink.

In both methods a TagLinkField instance is created so that we can get the Tag value from the field.

The follow class is used by an <initialize> pipeline processor that configures Glass on Sitecore application start:

using Glass.Mapper.Configuration;
using Glass.Mapper.IoC;
using Glass.Mapper.Maps;
using Glass.Mapper.Sc;
using Glass.Mapper.Sc.IoC;
using Sitecore.Sandbox.DI;
using Sitecore.Sandbox.Glass.Mapper.Sc.DataMappers;
using IDependencyResolver = Glass.Mapper.Sc.IoC.IDependencyResolver;

namespace Sitecore.Sandbox.Web.Mvc.App_Start
{
    public static class GlassMapperScCustom
    {
		public static IDependencyResolver CreateResolver(){
			var config = new Config();
            DependencyResolver dependencyResolver = new DependencyResolver(config);
            AddDataMappers(dependencyResolver);
            return dependencyResolver;
		}

        private static void AddDataMappers(DependencyResolver dependencyResolver)
        {
            if(dependencyResolver == null)
            {
                return;
            }
            
            dependencyResolver.DataMapperFactory.Replace(15, () => new SitecoreFieldTagLinkMapper());
        }

		public static IConfigurationLoader[] GlassLoaders(){			
			
			/* USE THIS AREA TO ADD FLUENT CONFIGURATION LOADERS
             * 
             * If you are using Attribute Configuration or automapping/on-demand mapping you don't need to do anything!
             * 
             */

			return new IConfigurationLoader[]{};
		}
		public static void PostLoad(){
			//Remove the comments to activate CodeFist
			/* CODE FIRST START
            var dbs = Sitecore.Configuration.Factory.GetDatabases();
            foreach (var db in dbs)
            {
                var provider = db.GetDataProviders().FirstOrDefault(x => x is GlassDataProvider) as GlassDataProvider;
                if (provider != null)
                {
                    using (new SecurityDisabler())
                    {
                        provider.Initialise(db);
                    }
                }
            }
             * CODE FIRST END
             */
		}
		public static void AddMaps(IConfigFactory<IGlassMap> mapsConfigFactory)
        {
			// Add maps here
            ContainerManager containerManager = new ContainerManager();
            foreach (var map in containerManager.Container.GetAllInstances<IGlassMap>())
            {
                mapsConfigFactory.Add(() => map);
            }
        }
    }
}

I added the AddDataMappers method to it. This method replaces the “out of the box” SitecoreFieldLinkMapper with a SitecoreFieldTagLinkMapper instance — the “out of the box” SitecoreFieldLinkMapper lives in the 15th place in the index (I determined this using .NET Reflector on one of the Glass.Mapper assemblies).

Now that the above things are squared away, we need a way to add the Tag attribute with its value to the attributes collection that is passed to Glass so that it can transform this into rendered HTML. I decided to define an interface for classes that do that:

using System;
using System.Linq.Expressions;

namespace Sitecore.Sandbox.Glass.Mapper.Sc.Attributes
{
    public interface ICustomAttributesAdder
    {
        object AddTagAttribute<T>(T model, Expression<Func<T, object>> field, object attributes);
    }
}

Classes that implement the above interface will take in a Glass Model instance, the field we are rendering, and the existing collection of attributes that are to be rendered by Glass.

The following class implements the above interface:

using System;
using System.Collections.Specialized;
using System.Linq.Expressions;

using Sitecore.Configuration;
using Sitecore.Diagnostics;

using Glass.Mapper.Sc;

using Sitecore.Sandbox.Glass.Mapper.Sc.Fields;

namespace Sitecore.Sandbox.Glass.Mapper.Sc.Attributes
{
    public class CustomAttributesAdder : ICustomAttributesAdder
    {
        private static readonly Lazy<ICustomAttributesAdder> current = new Lazy<ICustomAttributesAdder>(() => { return GetCustomAttributesAdder(); });

        public static ICustomAttributesAdder Current
        {
            get
            {
                return current.Value;
            }
        }

        public CustomAttributesAdder()
        {
        }

        public virtual object AddTagAttribute<T>(T model, Expression<Func<T, object>> field, object attributes)
        {
            TagLink tagLink = field.Compile()(model) as TagLink;
            if (tagLink == null || string.IsNullOrWhiteSpace(tagLink.Tag))
            {
                return attributes;
            }

            NameValueCollection attributesCollection;
            if (attributes is NameValueCollection)
            {
                attributesCollection = attributes as NameValueCollection;
            }
            else
            {
                attributesCollection = Utilities.GetPropertiesCollection(attributes, true, true);
            }

            attributesCollection.Add("tag", tagLink.Tag);
            return attributesCollection;
        }

        private static ICustomAttributesAdder GetCustomAttributesAdder()
        {
            ICustomAttributesAdder adder = Factory.CreateObject("sandbox.Glass.Mvc/customAttributesAdder", true) as ICustomAttributesAdder;
            Assert.IsNotNull(adder, "Be sure the configuration for CustomAttributesAdder is correct in utilities/customAttributesAdder of your Sitecore configuration!");
            return adder;
        }
    }
}

The AddTagAttribute method above first determines if the field passed to it is a TagLink. If it’s not, it just returns the attribute collection unaltered.

The method also determines if there is a Tag value. If there is no Tag value, it just returns the attribute collection “as is” since we don’t want to render an attribute with an empty value.

If the field is a TagLink and there is a Tag value, the method adds the Tag attribute name and value into the attributes collection that was passed to it, and then returns the modified NameValueCollection instance.

I decided to use the Singleton Pattern for the above class — the type of the class is defined in Sitecore configuration (see the patch configuration file further down in this post — since I am going to reuse it in my next post where I’ll show another approach on rendering a Tag attribute using Glass.Mapper, and I had built both approaches simultaneously (I decided to break these into separate blog posts since this one by itself will already be quite long).

Next, I built a new subclass of Glass.Mapper.Sc.Web.Mvc.GlassView so that I can intercept attributes collection passed to the RenderLink methods on the “out of the box” Glass.Mapper.Sc.Web.Mvc.GlassView (our Razor views will have to inherit from the class below in order for everything to work):

using System;
using System.Collections.Specialized;
using System.Linq.Expressions;
using System.Web;

using Sitecore.Configuration;
using Sitecore.Diagnostics;

using Sitecore.Sandbox.Glass.Mapper.Sc.Attributes;
using Sitecore.Sandbox.Glass.Mapper.Sc.Fields;

using Glass.Mapper.Sc;
using Glass.Mapper.Sc.Fields;
using Glass.Mapper.Sc.Web.Mvc;

namespace Sitecore.Sandbox.Glass.Mapper.Sc.Web.Mvc
{
    public abstract class SandboxGlassView<TModel> : GlassView<TModel> where TModel : class
    {
        private ICustomAttributesAdder attributesAdder;
        private ICustomAttributesAdder AttributesAdder 
        { 
            get
            {
                if (attributesAdder == null)
                {
                    attributesAdder = GetCustomAttributesAdder();
                }

                return attributesAdder;
            }
        }

        public override RenderingResult BeginRenderLink<T>(T model, Expression<Func<T, object>> field, object attributes = null, bool isEditable = false)
        {
            object attributesModified = AttributesAdder.AddTagAttribute(model, field, attributes);
            return base.BeginRenderLink<T>(model, field, attributesModified, isEditable);
        }

        public override HtmlString RenderLink(Expression<Func<TModel, object>> field, object attributes = null, bool isEditable = false, string contents = null)
        {
            object attributesModified = AttributesAdder.AddTagAttribute(Model, field, attributes);
            return base.RenderLink(field, attributesModified, isEditable, contents);
        }

        public override HtmlString RenderLink<T>(T model, Expression<Func<T, object>> field, object attributes = null, bool isEditable = false, string contents = null)
        {
            object attributesModified = AttributesAdder.AddTagAttribute(model, field, attributes);
            return base.RenderLink<T>(model, field, attributesModified, isEditable, contents);
        }

        protected virtual ICustomAttributesAdder GetCustomAttributesAdder()
        {
            return CustomAttributesAdder.Current;
        }
    }
}

I used the CustomAttributesAdder Singleton instance to add the Tag attribute name and value into the passed attributes collection, and then pass it on to the base class to do its magic.

I then strung everything together using the following Sitecore patch configuration file (Note: lots of stuff in this configuration file come from my previous post so I advise having a look at it):

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <controlSources>
      <source mode="on" namespace="Sitecore.Sandbox.Shell.Applications.ContentEditor" assembly="Sitecore.Sandbox" prefix="sandbox-content"/>
    </controlSources>
    <fieldTypes>
      <fieldType name="General Link">
        <patch:attribute name="type">Sitecore.Sandbox.Data.Fields.TagLinkField, Sitecore.Sandbox</patch:attribute>
      </fieldType>
      <fieldType name="General Link with Search">
        <patch:attribute name="type">Sitecore.Sandbox.Data.Fields.TagLinkField, Sitecore.Sandbox</patch:attribute>
      </fieldType>
      <fieldType name="link">
        <patch:attribute name="type">Sitecore.Sandbox.Data.Fields.TagLinkField, Sitecore.Sandbox</patch:attribute>
        </fieldType>
    </fieldTypes>
    <pipelines>
      <dialogInfo>
        <processor type="Sitecore.Sandbox.Pipelines.DialogInfo.SetDialogInfo, Sitecore.Sandbox">
          <ParameterNameAttributeName>name</ParameterNameAttributeName>
          <ParameterValueAttributeName>value</ParameterValueAttributeName>
          <Message>contentlink:externallink</Message>
          <Url>/sitecore/shell/Applications/Dialogs/External link.aspx</Url>
          <parameters hint="raw:AddParameter">
            <parameter name="height" value="300" />
          </parameters>
        </processor>
      </dialogInfo>
      <renderField>
        <processor patch:after="processor[@type='Sitecore.Pipelines.RenderField.GetInternalLinkFieldValue, Sitecore.Kernel']" 
                   type="Sitecore.Sandbox.Pipelines.RenderField.SetTagAttributeOnLink, Sitecore.Sandbox">
          <TagXmlAttributeName>tag</TagXmlAttributeName>
          <TagAttributeName>tag</TagAttributeName>
          <BeginningHtml>&lt;a </BeginningHtml>
        </processor>  
      </renderField>
    </pipelines>
    <sandbox.Glass.Mvc>
      <customAttributesAdder type="Sitecore.Sandbox.Glass.Mapper.Sc.Attributes.CustomAttributesAdder, Sitecore.Sandbox" singleInstance="true" />
    </sandbox.Glass.Mvc>
  </sitecore>
</configuration>

Let’s see this in action!

For testing, I created the following interface for a model for my Sitecore instance’s Home Item (we are using fields defined on the Sample Item template):

using Glass.Mapper.Sc.Configuration.Attributes;

using Sitecore.Sandbox.Glass.Mapper.Sc.Fields;

namespace Sitecore.Sandbox.Models.ViewModels
{
    public interface ISampleItem
    {
        string Title { get; set; }

        string Text { get; set; }

        [SitecoreField("Link One")]
        TagLink LinkOne { get; set; }

        [SitecoreField("Link Two")]
        TagLink LinkTwo { get; set; }
    }
}

Model instances of the above interface will have two TagLink instances on them.

Next, I built the following Glass.Mapper.Sc.Maps.SitecoreGlassMap for my model interface defined above:

using Glass.Mapper.Sc.Maps;

using Sitecore.Sandbox.Models.ViewModels;

namespace Sitecore.Sandbox.Mappings.ViewModels.SampleItem
{
    public class SampleItemMap : SitecoreGlassMap<ISampleItem>
    {
        public override void Configure()
        {
            Map(x =>
            {
                x.AutoMap();
            });
        }
    }
}

Glass.Mapper will create an instance of the above class which will magically create a concrete instance of a class that implements the ISampleItem interface.

We need to plug the above into the front-end. I did this using the following Razor view:

@inherits Sitecore.Sandbox.Glass.Mapper.Sc.Web.Mvc.SandboxGlassView<Sitecore.Sandbox.Models.ViewModels.ISampleItem>
@using Glass.Mapper.Sc.Web.Mvc
@using Sitecore.Sandbox.Glass.Mapper.Sc.Web.Mvc

<div id="Content">
    <div id="LeftContent">
    </div>
    <div id="CenterColumn">
        <div id="Header">
            <img src="/~/media/Default Website/sc_logo.png" id="scLogo" />
        </div>
        <h1 class="contentTitle">
            @Editable(x => x.Title)
        </h1>
        <div class="contentDescription">
            @Editable(x => x.Text)
            <div>
                @RenderLink(x => x.LinkOne)
            </div>
            <div>
                @RenderLink(x => x.LinkTwo)
            </div>
        </div>
    </div>
</div>

The above Razor file inherits from SandboxGlassView so that it can access the RenderLink methods that were defined in the SandboxGlassView class.

I then ensured I had some tag attributes set on some General Link fields on my home Item (I kept these the same as my last blog post):

tag-attributes-raw-values

After doing a build and navigating to my homepage Item, I saw the following in the rendered HTML:

tag-attributes-rendered

As you can see, it worked magically. 🙂

magic

If you have any questions/comments/thoughts on the above, please share in a comment.

Also, I would like to thank Sitecore MVP Nat Mann for helping me on some of the bits above. Without your help Nat, there would be no solution and no blog post.

Until next time, keep on Sitecore-ing. 😀

Bundle CSS and JavaScript Files in Sitecore MVC

The other day I was poking around Sitecore.Forms.Mvc.dll — this assembly ships with Web Forms for Marketers (WFFM), and is used when WFFM is running on Sitecore MVC — and noticed WFFM does some bundling of JavaScript and CSS files:

wffm-bundling

WFFM uses the above class as an <initialize> pipeline processor. You can see this defined in Sitecore.Forms.Mvc.config:

Sitecore-Forms-Mvc-config

This got me thinking: why not build my own class to serve as an <initialize> pipeline processor to bundle my CSS and JavaScript files?

As an experiment I whipped up the following class to do just that:

using System.Collections.Generic;
using System.Linq;
using System.Web.Optimization;

using Sitecore.Pipelines;

namespace Sitecore.Sandbox.Forms.Mvc.Pipelines
{         
    public class RegisterAdditionalFormBundles
    {
        public RegisterAdditionalFormBundles()
        {
            CssFiles = new List<string>();
            JavaScriptFiles = new List<string>();
        }

        public void Process(PipelineArgs args)
        {
            BundleCollection bundles = GetBundleCollection();
            if (bundles == null)
            {
                return;
            }

            AddBundle(bundles, CreateCssBundle());
            AddBundle(bundles, CreateJavaScriptBundle());
        }

        protected virtual BundleCollection GetBundleCollection()
        {
            return BundleTable.Bundles;
        }

        protected virtual Bundle CreateCssBundle()
        {
            if (!CanBundleAssets(CssVirtualPath, CssFiles))
            {
                return null;
            }

            return new StyleBundle(CssVirtualPath).Include(CssFiles.ToArray());
        }

        protected virtual Bundle CreateJavaScriptBundle()
        {
            if (!CanBundleAssets(JavaScriptVirtualPath, JavaScriptFiles))
            {
                return null;
            }

            return new ScriptBundle(JavaScriptVirtualPath).Include(JavaScriptFiles.ToArray());
        }

        protected virtual bool CanBundleAssets(string virtualPath, IEnumerable<string> filePaths)
        {
            return !string.IsNullOrWhiteSpace(virtualPath)
                    && filePaths != null
                    && filePaths.Any();
        }

        private static void AddBundle(BundleCollection bundles, Bundle bundle)
        {
            if(bundle == null)
            {
                return;
            }

            bundles.Add(bundle);
        }

        private string CssVirtualPath { get; set; }

        private List<string> CssFiles { get; set; }

        private string JavaScriptVirtualPath { get; set; }

        private List<string> JavaScriptFiles { get; set; }
    }
}

The class above basically takes in a collection of CSS and JavaScript file paths as well as their virtual bundled paths — these are magically populated by Sitecore’s Configuration Factory using values provided by the configuration file shown below — iterates over both collections, and adds them to the BundleTable — the BundleTable is defined in System.Web.Optimization.dll.

I then glued everything together using a patch configuration file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <initialize>
        <processor patch:after="processor[@type='Sitecore.Forms.Mvc.Pipelines.RegisterFormBundles, Sitecore.Forms.Mvc']"
          type="Sitecore.Sandbox.Forms.Mvc.Pipelines.RegisterAdditionalFormBundles, Sitecore.Sandbox">
          <CssVirtualPath>~/wffm-bundles/styles.css</CssVirtualPath>
          <CssFiles hint="list">
            <CssFile>~/css/uniform.aristo.css</CssFile>
          </CssFiles>
          <JavaScriptVirtualPath>~/wffm-bundles/scripts.js</JavaScriptVirtualPath>
          <JavaScriptFiles hint="list">
            <JavaScriptFile>~/js/jquery.min.js</JavaScriptFile>
            <JavaScriptFile>~/js/jquery.uniform.min.js</JavaScriptFile>
            <JavaScriptFile>~/js/bind.uniform.js</JavaScriptFile>
          </JavaScriptFiles>
        </processor>
      </initialize>
    </pipelines>
  </sitecore>
</configuration>

I’m adding the <initialize> pipeline processor shown above after WFFM’s though theoretically you could add it anywhere within the <initialize> pipeline.

The CSS and JavaScript files defined in the configuration file above are from the Uniform project — this project includes CSS, JavaScript and images to make forms look nice, though I am in no way endorsing this project. I only needed some CSS and JavaScript files to spin up something quickly for testing.

For testing, I built the following View — it uses some helpers to render the <link> and <script> tags for the bundles — and tied it to my Layout in Sitecore:

@using System.Web.Optimization
@using Sitecore.Mvc
@using Sitecore.Mvc.Presentation

<!DOCTYPE html>
<html>

<head>
    <title></title>

    @Styles.Render("~/wffm-bundles/styles.css")
    @Scripts.Render("~/wffm-bundles/scripts.js")
    
</head>
<body>
    

    @Html.Sitecore().Placeholder("page content")

</body>
</html>

I then built a “Feedback” form in WFFM; mapped it to the “page content” placeholder defined in the View above; published it; and pulled it up in my browser. As you can see the code from the Uniform project styled the form:

styled-form

For comparison, this is what the form looks like without the <initialize> pipeline processor above:

2014-10-30_2316

If you have any thoughts on this, or have alternative ways of bundling CSS and JavaScript files in your Sitecore MVC solutions, please share in a comment.

A Fix for the Hard-coded Submit Button Text in Web Forms for Marketers on Sitecore MVC

No doubt you have heard the latest version of Web Forms for Marketers — better known as WFFM — now works on Sitecore MVC — don’t know about you but I am quite excited over this!

Plus, as an added bonus — and this made my day when I installed it 😀 — you can change most of the rendered markup for forms in Views that ship with the module (these are installed into ~\Views\Form\EditorTemplates\ of your Sitecore instance).

However, the other day, I discovered the submit button text is hard-coded in one of the module’s Views:

submit-button-text-hardcoded

As you may know, the module does provide a field on Form Items to change the submit button text:

submit-button-text-form-item

Unfortunately, the value of this field is not being put into the value attribute of the submit button in the View above.

I have alerted Sitecore support of this issue via a ticket though do recommend modifying ~\Views\Form\EditorTemplates\FormModel.cshtml to use the submit button text set on the Form Item if you are using WFFM on Sitecore MVC:

submit-button-text-not-hardcoded

As you can see, the value of the submit button text field is set on a property of the model, and can be easily be put into the value attribute of the submit button.

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