Thursday, July 28, 2016

Solr Cloud Patch for Sitecore 8.1

Sitecore has some issues working with Solr Cloud out of the box. There is an unofficial patch for it here. I was able to get it to work with my setup (8.1 rev 160302, Solr Cloud 5.3.1, Castle.Windsor 3.3), with the following modifications.


  1. No changes to the Global.asax file. Use the default one.
  2. Disable the file Sitecore.ContentSearch.SolrProvider.CastleWindsorIntegration.dll in the \bin folder.
  3. Add the file Sitecore.Support.405677.Windsor.dll to the \bin folder.
  4. Add the file Sitecore.Support.ContentSearch.Solr.WindsorInitializer.405677.config to the \Include\Sitecore.Support.405677 folder.
  5. Add the file Sitecore.Support.449298.dll to the \bin folder.
  6. Add the file Sitecore.Support.449298.config to the \Include\Sitecore.Support.449298 folder.
  7. Add the file Sitecore.Support.449298.SwitchOnRebuild.IndexConfig.example to the \Include\Sitecore.Support.449298 folder.
  8. Enable and configure the file Sitecore.Support.449298.SwitchOnRebuild.IndexConfig.example. This includes setting the collection names for the master, web and core indexes, as well as the ServiceBaseAddress of your Solr instance.
  9. Add the file SolrCloud.config to your \zzzMustBeLast folder (or whatever your equivalent "run these configs last" folder is).


The SolrCloud.config file switches ALL the Sitecore indexes over to using the patched Solr Cloud indexing. Feel free to edit this as needed, or to include your own custom indexes in here.

Note: Make sure that your Solr instance has all the cores that you've declared in these indexes. IE: sitecore_master_index, sitecore_master_index_secondary, etc.

WARNING: When using this patch, you will notice some ERRORS appearing in your log that coincide with whenever you open the Index Manager window. I have confirmed with Sitecore Support that this is just noise and can be ignored, it doesn't affect the functionality of the patch.

"You can safely ignore these messages. They indeed happen to Indexing manager and bound to the fact that we can't get index summary in the current realization of this patch. But this shouldn't affect indexing and everything should work fine."

Here are copies of all the files I used.
Sitecore.Support.405677.zip
Sitecore.Support.449298-8.1.zip
SolrCloud.config

Friday, May 27, 2016

Error switching to SOLR, with Exerpience Analytics/Reduce/Agent (IFieldMapEx error)

I recently encountered an error when trying to switch my Sitecore instance over to using SOLR. The error is pasted below, which turned up ZERO results googling. Always fun when that happens.

Turned out to be just a mismatch in versions. I'm running Sitecore 8.1 rev160302, which at the time I installed it was the most recent version. I wasn't paying attention when I grabbed the SOLR Support Package, and I just went to the most recent version of Sitecore... which is now Sitecore 8.1 rev 160519. Turns out versions really do matter!

Dumb error on my part, but I'd figure I'd make a post about it just in case anyone else makes the same mistake, so they aren't treated to an empty google search.

3760 13:10:59 ERROR Exception when executing agent experienceAnalytics/reduce/agent
Exception: System.Reflection.TargetInvocationException
Message: Exception has been thrown by the target of an invocation.
Source: mscorlib
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Sitecore.Reflection.ReflectionUtil.CreateObject(Type type, Object[] parameters)
at Sitecore.Configuration.Factory.CreateFromTypeName(XmlNode configNode, String[] parameters, Boolean assert)
at Sitecore.Configuration.Factory.CreateObject(XmlNode configNode, String[] parameters, Boolean assert, IFactoryHelper helper)
at Sitecore.Configuration.Factory.CreateObject(String configPath, String[] parameters, Boolean assert)
at Sitecore.Configuration.Factory.CreateFromReference(XmlNode configNode, String[] parameters, Boolean assert)
at Sitecore.Configuration.Factory.CreateObject(XmlNode configNode, String[] parameters, Boolean assert, IFactoryHelper helper)
at Sitecore.Configuration.Factory.GetInnerObject(XmlNode paramNode, String[] parameters, Boolean assert)
at Sitecore.Configuration.Factory.GetConstructorParameters(XmlNode configNode, String[] parameters, Boolean assert)
at Sitecore.Configuration.Factory.CreateFromTypeName(XmlNode configNode, String[] parameters, Boolean assert)
at Sitecore.Configuration.Factory.CreateObject(XmlNode configNode, String[] parameters, Boolean assert, IFactoryHelper helper)
at Sitecore.Configuration.Factory.CreateObject(String configPath, String[] parameters, Boolean assert)
at Sitecore.Analytics.Core.BackgroundService.CreateAgent()
at Sitecore.Analytics.Core.BackgroundService.ExecuteAgent()
at Sitecore.Analytics.Core.BackgroundService.Run()

Nested Exception

Exception: System.Reflection.TargetInvocationException
Message: Exception has been thrown by the target of an invocation.
Source: mscorlib
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Sitecore.Reflection.ReflectionUtil.CreateObject(Type type, Object[] parameters)
at Sitecore.Configuration.Factory.CreateFromTypeName(XmlNode configNode, String[] parameters, Boolean assert)
at Sitecore.Configuration.Factory.CreateObject(XmlNode configNode, String[] parameters, Boolean assert, IFactoryHelper helper)
at Sitecore.Configuration.Factory.CreateObject[T](XmlNode configNode)
at Sitecore.ExperienceAnalytics.Reduce.ReduceContainer.Repositories.GetDimensionDefinitionService()
at Sitecore.ExperienceAnalytics.Reduce.ReduceWorker..ctor(String connectionString)
at Sitecore.ExperienceAnalytics.Reduce.ReduceManager..ctor(String connectionStringName, String retentionDays, ILogger logger)

Nested Exception

Exception: System.Reflection.TargetInvocationException
Message: Exception has been thrown by the target of an invocation.
Source: mscorlib
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Sitecore.Reflection.ReflectionUtil.CreateObject(Type type, Object[] parameters)
at Sitecore.ExperienceAnalytics.Core.Repositories.DimensionDefinitionService.CreateDimensionFromConfig(XmlElement childNode)
at Sitecore.ExperienceAnalytics.Core.Repositories.DimensionDefinitionService.LoadDimensionsFromConfig(String pathToConfigNode)

Nested Exception

Exception: System.Reflection.TargetInvocationException
Message: Exception has been thrown by the target of an invocation.
Source: mscorlib
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Sitecore.Reflection.ReflectionUtil.CreateObject(Type type, Object[] parameters)
at Sitecore.Configuration.Factory.CreateFromTypeName(XmlNode configNode, String[] parameters, Boolean assert)
at Sitecore.Configuration.Factory.CreateObject(XmlNode configNode, String[] parameters, Boolean assert, IFactoryHelper helper)
at Sitecore.Configuration.Factory.CreateObject(String configPath, String[] parameters, Boolean assert)
at Sitecore.Configuration.Factory.CreateFromReference(XmlNode configNode, String[] parameters, Boolean assert)
at Sitecore.Configuration.Factory.CreateObject(XmlNode configNode, String[] parameters, Boolean assert, IFactoryHelper helper)
at Sitecore.Configuration.Factory.GetInnerObject(XmlNode paramNode, String[] parameters, Boolean assert)
at Sitecore.Configuration.Factory.GetConstructorParameters(XmlNode configNode, String[] parameters, Boolean assert)
at Sitecore.Configuration.Factory.CreateFromTypeName(XmlNode configNode, String[] parameters, Boolean assert)
at Sitecore.Configuration.Factory.CreateObject(XmlNode configNode, String[] parameters, Boolean assert, IFactoryHelper helper)
at Sitecore.Configuration.Factory.CreateObject(String configPath, String[] parameters, Boolean assert)
at Sitecore.Marketing.Definitions.DefinitionManagerFactory.GetDefinitionManager[TDefinition](String targetRepository)
at Sitecore.ExperienceAnalytics.Aggregation.Dimensions.ByCampaignGroup..ctor(Guid dimensionId)

Nested Exception

Exception: System.Reflection.TargetInvocationException
Message: Exception has been thrown by the target of an invocation.
Source: mscorlib
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Sitecore.Reflection.ReflectionUtil.CreateObject(Type type, Object[] parameters)
at Sitecore.Configuration.Factory.CreateFromTypeName(XmlNode configNode, String[] parameters, Boolean assert)
at Sitecore.Configuration.Factory.CreateObject(XmlNode configNode, String[] parameters, Boolean assert, IFactoryHelper helper)
at Sitecore.Configuration.Factory.CreateObject(String configPath, String[] parameters, Boolean assert)
at Sitecore.Configuration.Factory.CreateFromReference(XmlNode configNode, String[] parameters, Boolean assert)
at Sitecore.Configuration.Factory.CreateObject(XmlNode configNode, String[] parameters, Boolean assert, IFactoryHelper helper)
at Sitecore.Configuration.Factory.GetInnerObject(XmlNode paramNode, String[] parameters, Boolean assert)
at Sitecore.Configuration.Factory.AssignProperties(XmlNode configNode, String[] parameters, Object obj, Boolean assert, Boolean deferred, IFactoryHelper helper)
at Sitecore.Configuration.Factory.CreateObject(XmlNode configNode, String[] parameters, Boolean assert, IFactoryHelper helper)
at Sitecore.Configuration.Factory.GetInnerObject(XmlNode paramNode, String[] parameters, Boolean assert)
at Sitecore.Configuration.Factory.AssignProperties(XmlNode configNode, String[] parameters, Object obj, Boolean assert, Boolean deferred, IFactoryHelper helper)
at Sitecore.Configuration.Factory.CreateObject(XmlNode configNode, String[] parameters, Boolean assert, IFactoryHelper helper)
at Sitecore.Configuration.Factory.CreateObject(String configPath, String[] parameters, Boolean assert)
at Sitecore.ContentSearch.ContentSearchManager.get_SearchConfiguration()
at Sitecore.ContentSearch.ContentSearchManager.GetIndex(String name)
at Sitecore.Marketing.Search.CampaignDefinitionSearchProvider..ctor(String indexName)

Nested Exception

Exception: System.TypeLoadException
Message: Could not load type 'Sitecore.ContentSearch.IFieldMapEx' from assembly 'Sitecore.ContentSearch, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Source: Sitecore.ContentSearch.SolrProvider
at Sitecore.ContentSearch.SolrProvider.SolrIndexConfiguration.set_DocumentOptions(IDocumentBuilderOptions value)

Tuesday, January 26, 2016

Sitecore 8 Experience Editor: An example MVC rendering

Working With The Experience Editor

The following is a deep-dive into a rendering I recently had to develop. The goal was to allow the Content Editor to be able to use the Experience Editor 100%, and to have a very WYSIWYG feel to their content creation.

This resulted in the addition of some extra code that I normally would not have thought about. It also slightly changed how I designed the IA of the Sitecore item. Things that could have been made more extensible and reusable ended up being more flattened. While this might make future upkeep of the code slightly more time intensive, it allowed the rendering to be much more intuitive for the Content Editor.

The rendering itself is a fairly simple "Call to Action" overlay. It consists of a Title, Body, either 1 or 2 Buttons with Text and an Icon, and an Image that may or may not be used. The design stated that there was only plans for 2 formats to be used, and the markup supporting each was handled through a css class in the div wrapper.

Here are examples of the 2 possible formats:




Coming Up With The Design

The first thing to setup is the template for the CtoA. Normally I would have made the Buttons their own template, and then linked to them from the CtoA template using a Multilist. This would allow for the reuse of buttons, as well as making it easier to allow for the option of increased numbers of buttons in the future (ie: they want to use 3 or 4 buttons on a CtoA).

However, when I started to try and work with it in the Experience Editor, I found that this made things much more complex. I would have to mess around with adding placeholders to the rendering. The Content Editor would have extra steps to create the buttons, and select which ones to add to the rendering. The couldn't just create the buttons directly onto the page right away.

While it definitely would have been possible, it felt unnecessary for this particular rendering. The buttons themselves don't take much effort to make, so there wouldn't be a ton of effort being saved with their reuse. This was also a pretty stable design. While it would be nice for it to handle 3 or 4 buttons at some point, there was currently no plans for this to be needed in the near future.

What I ended up with was a pretty flat CtoA template that had the fields needed for the 2 buttons directly on the template. Any future changes to the design would take more effort (low risk), and the Content Editor would have an easier time in the meanwhile (guaranteed gain).

This might seem nitpicky for such a simple rendering, but these thoughts are definitely something to keep in mind when working on larger more involved renderings. Some parts of the design might be The Right Way™ to do it, but if it's making life difficult for the people who are going to be using it everyday then you may want to rethink things. The technical level of your Content Editors is another thing to keep in mind. Some might be ok with lots of popups and windows and complexity, others might require something more straightforward.

The template for the CtoA:






Creating The Model

The model was pretty straight forward. I know there are a few different ways of handling this, but this is the way I did it for this rendering. The Audience field is a link to another Sitecore item that contains the CSS class we are going to need in our div. I do the logic to grab that text using the Audience ID inside of the model.

The CallToActionModel.cs:

public class CallToActionModel : ModelBase<Call_To_Action>
{
public Audience Audience { get; set; }
public Icon Button1Icon { get; set; }
public Icon Button2Icon { get; set; }
public bool Button1IsVisible { get; set; }
public bool Button2IsVisible { get; set; }
public bool LogoIsVisible { get; set; }

public override void Initialize(Rendering rendering)
{
base.Initialize(rendering);

this.Audience = (this.DataSourceItem.Audience != Guid.Empty ? Sitecore.Context.Database.GetItem(new ID(this.DataSourceItem.Audience)).GlassCast<Audience>() : null);
this.Button1Icon = (this.DataSourceItem.Button_1_Icon != Guid.Empty ? Sitecore.Context.Database.GetItem(new ID(this.DataSourceItem.Button_1_Icon)).GlassCast<Icon>() : null);
this.Button2Icon = (this.DataSourceItem.Button_2_Icon != Guid.Empty ? Sitecore.Context.Database.GetItem(new ID(this.DataSourceItem.Button_2_Icon)).GlassCast<Icon>() : null);
this.Button1IsVisible = this.DataSourceItem.Button_1_IsVisible;
this.Button2IsVisible = this.DataSourceItem.Button_2_IsVisible;
this.LogoIsVisible = this.DataSourceItem.Logo_IsVisible;
}
}

After that you create the Sitecore Model item in Sitecore -> Layouts -> Models, and point it to your model CS file.


Setting Up The Rendering

Create the Sitecore View Rendering item in Sitecore -> Layouts -> Renderings. Fill in the following fields; Path, Model, DataSource Location, DataSource Template.

     Path: Simply the path to your cshtml file.   
     Model: Link to your Sitecore Model item.
     DataSource Location: The location of the content items this rendering will use.
     DataSource Template: The template allowed for this rendering.

Setting these last two will ensure that after the Content Editor adds the rendering to the page, they will get a nice clean popup that limits what and where they can save their items to.



Rendering Markup

There are 3 different types of editable fields that I deal with in this rendering; Text, Image, Link.

Initial setup:
First we have some code at the top of the cshtml file that determines what Model we will be using. We also grab the Audience CSS value that will be wrapping our entire control.

    @inherits GlassView<CallToActionModel>


    string audience = (Model.Audience != null ? Model.Audience.Style : string.Empty);


   <div class="box hero-bar @audience">
       ......
   </div>

Making text editable:
This is the most straightforward of the 3. Simply use the @Editable() command, and the text field that you feed it will become editable through the Experience Editor.
    
    <div class="hidden-xs">
      <div class="bx-title">
        @Editable(x => x.DataSourceItem.Title)
      </div>
      <div>
        @Editable(x => x.DataSourceItem.Body)
      </div>
    </div>





Making the image editable:
The code syntax for this one is also pretty easy, but there are a few "gotchas" you have to keep in mind from the Content Editor's point of view.

    @if (Model.LogoIsVisible && !String.IsNullOrEmpty(Model.DataSourceItem.Logo.Src))
    {
      <div class="hidden-xs">
        @RenderImage(x => x.DataSourceItem.Logo, isEditable: true)
      </div>
    }
    else if (Html.IsPageEditor() && Model.LogoIsVisible)
    {
      <div class="hidden-xs">
        @RenderImage(x => x.DataSourceItem.Logo, isEditable: true)
      </div>
    }



There are 2 things of interest to note here. The first is the use of Model.LogoIsVisible, and the second is the use of Html.IsPageEditor().

LogoIsVisible is a boolean field on the Sitecore item. We'll get into how to edit that through the Experience Editor later. The reason we are using it is because the Experience Editor gives you a stubbed image, to let you know there is an editable field there. The problem is, the Editor doesn't know when the image is purposefully being left empty. This gets in the way of the WYSIWYG experience.



By having the LogoIsVisible checkbox, the Content Editor can tell the Experience Editor that they aren't using the image, and to remove the stub.



The use of Html.IsPageEditor() combined with LogoIsVisible ensures that the code for the image only gets shown a) when we are in experience editor and the content editor *wants* to see an image, b) when we are viewing the page normally, and an image exists.

Making the buttons editable:
This last one is a little more tricky. We want both the text of the button, as well as the URL to be editable, and we want to use the Icon that is set on the Sitecore item.

  string button1_Class = (Model.Button1Icon != null ? Model.Button1Icon.Css_Class : string.Empty);
  string button2_Class = (Model.Button2Icon != null ? Model.Button2Icon.Css_Class : string.Empty);

    @if (Model.Button1IsVisible)
    {
      <div>
        @using (BeginRenderLink(x => x.DataSourceItem.Button_1_Url, new System.Collections.Specialized.NameValueCollection { { "class", "button hb-button" } }, isEditable: true))
        {
          <span class="@(button1_Class)"></span>@Editable(x => x.DataSourceItem.Button_1_Text)
        }
      </div>
    }

With the @using (BeginRenderLink()...) syntax, we make sure that the button url is editable, and we can build whatever editable markup we want within the button's code. The CSS text for the Icon is also pulled from the Model, and sets the CSS class of the <span>.







Editing Other Sitecore Item Fields

We've seen how to edit fields such as Text, Image and Link. But I've yet to find a way to edit any other field types directly on the rendering. Thankfully, it's pretty easy to expose for editing any other field we want the Content Editor to have access to.

You can create a custom Experience Editor Button, that shows up on the Experience Editor Ribbon for the rendering.



When the Content Editor clicks that button, they are presented with a popup where they can easily edit the items you have exposed for them. The only slight drawback to this is that after they have made their changes, they need to save the page they are working on for the changes to be picked up by the rendering.




Creating A Custom Experience Editor Button

To create your custom button, first switch over to the Core DB. Then go to the location /sitecore/content/Applications/WebEdit/Custom Experience Buttons. Here you will create a new item of template type /sitecore/templates/System/WebEdit/Field Editor Button.

Header - Something descriptive about your button.
Icon - I use the same icon I chose for the Rendering.
Fields - A pipe delimited list of the fields you are exposing from your Sitecore Item.
Tooltip - Something description for your button.

An example custom button:



Next you want to head over to the Sitecore Rendering you created in the Layouts folder. There is a field called Experience Editor Buttons. Find the new button you just created and add it to this field.

When you are done, you should have a new button on the ribbon bar of your rendering.




Conclusion

Creating a rendering design to be used solely by the Experience Editor requires a little more effort than normal, but can lead to some pretty easy content generation.

Here's what my final product looks like in action.