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><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:
I then navigated to my homepage; looked at the rendered HTML; and saw the following:
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:
😀
[…] A 2nd Approach to Render a Custom General Link Field Attribute in a Sitecore MVC View Rendering via&… […]
[…] A 2nd Approach to Render a Custom General Link Field Attribute in a Sitecore MVC View Rendering via … […]