Sitecore Contacts – Create and save contacts to and from xDB (MongoDB)

The Sitecore Contact is the cornerstone of the Sitecore Experience Platform and is the place where you store every data you know about any contact, named and anonymous.

UPDATE: 09-09-2016: CreateContact Updated: Thanks to moginheden for the update.

UPDATE: 27-09-2016: Repository Updated. In certain situations it would be possible for the class to go into an infinite loop. Read more here: Sitecore General error when submitting contact – Another contact with the same identifier already exists. Thanks to Dan and others for the update. 

This library was made by my colleague Peter Wind for a project we are both working on. In some cases we need to manipulate a contact that is not currently identified, for example when updating contact facets from imported data.
To do so you need to find the contact in xDB. If it does not exist, you need to create the contact. And when you are done updating the contact, you must save the data back to xDB and release the lock on the contact.

Any Contact in Sitecore is identified by a string. There is no connection between the user database (the .NET Security Provider) and a contact other than the one you make yourself. The username IS your key, and the key should be unique. You must be careful when identifying a Sitecore User, and never identify extranet\anonymous.

A WORD OF CAUTION:

The code uses some direct calls to the Sitecore Analytics and thus explores some undocumented features that was not meant to be called directly. The code is therefore a result of trial-and-error plus help from Sitecore Support. In other words: Just because it works on my 500.000+ contacts, it might fail on yours.

ENOUGH TALK LETS CODE:

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using Sitecore.Analytics;
using Sitecore.Analytics.Data;
using Sitecore.Analytics.DataAccess;
using Sitecore.Analytics.Model;
using Sitecore.Analytics.Tracking;
using Sitecore.Analytics.Tracking.SharedSessionState;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Diagnostics;

namespace MyNamespace
{
  public class ExtendedContactRepository
  {
    private const int _MAX_RETRIES = 10;
    private int _retries;

    public Contact GetOrCreateContact(string userName)
    {
      if (IsContactInSession(userName))
        return Tracker.Current.Session.Contact;

      ContactRepository contactRepository = Factory.CreateObject("tracking/contactRepository", true) as ContactRepository;
      ContactManager contactManager = Factory.CreateObject("tracking/contactManager", true) as ContactManager;

      Assert.IsNotNull(contactRepository, "contactRepository");
      Assert.IsNotNull(contactManager, "contactManager");

      try
      {
        Contact contact = contactRepository.LoadContactReadOnly(userName);
        LockAttemptResult<Contact> lockAttempt;

        if (contact == null)
          lockAttempt = new LockAttemptResult<Contact>(LockAttemptStatus.NotFound, null, null);
        else
          lockAttempt = contactManager.TryLoadContact(contact.ContactId);

        return GetOrCreateContact(userName, lockAttempt, contactRepository, contactManager);
      }
      catch (Exception ex)
      {
        throw new Exception(this.GetType() + " Contact could not be loaded/created - " + userName, ex);
      }
    }

    public void ReleaseAndSaveContact(Contact contact)
    {
      ContactManager manager = Factory.CreateObject("tracking/contactManager", true) as ContactManager;
      if (manager == null)
        throw new Exception(this.GetType() +  " Could not instantiate " + typeof(ContactManager));
      manager.SaveAndReleaseContact(contact);
      ClearSharedSessionLocks(manager, contact);
    }
    
    private Contact GetOrCreateContact(string userName, LockAttemptResult<Contact> lockAttempt, ContactRepository contactRepository, ContactManager contactManager)
    {
      switch (lockAttempt.Status)
      {
        case LockAttemptStatus.Success:
          Contact lockedContact = lockAttempt.Object;
          lockedContact.ContactSaveMode = ContactSaveMode.AlwaysSave;
          return lockedContact;

        case LockAttemptStatus.NotFound:
          Contact createdContact = CreateContact(userName, contactRepository);
          contactManager.FlushContactToXdb(createdContact);
          // Avoid going into an infinite loop.
          _retries++;
          if (_retries >= _MAX_RETRIES)
            throw new Exception(string.Format("ExtendedContactRepository: Contact {0} could not be created. ", username));
          return GetOrCreateContact(userName);

        default:
          throw new Exception(this.GetType() + " Contact could not be locked - " + userName);
      }
    }

    private Contact CreateContact(string userName, ContactRepository contactRepository)
    {
      Contact contact = contactRepository.CreateContact(ID.NewID);
      contact.Identifiers.Identifier = userName;
      contact.Identifiers.IdentificationLevel = Sitecore.Analytics.Model.ContactIdentificationLevel.Known;
      contact.System.Value = 0;
      contact.System.VisitCount = 0;
      contact.ContactSaveMode = ContactSaveMode.AlwaysSave;
      return contact;
    }

    private bool IsContactInSession(string userName)
    {
      var tracker = Tracker.Current;

      if (tracker != null && 
	      tracker.IsActive && 
		  tracker.Session != null && 
		  tracker.Session.Contact != null && 
		  tracker.Session.Contact.Identifiers != null && 
		  tracker.Session.Contact.Identifiers.Identifier != null && 
		  tracker.Session.Contact.Identifiers.Identifier.Equals(userName, StringComparison.InvariantCultureIgnoreCase))
        return true;

      return false;
    }

    private void ClearSharedSessionLocks(ContactManager manager, Contact contact)
    {
      if (HttpContext.Current != null && HttpContext.Current.Session != null)
        return;

      var sharedSessionStateManagerField = manager.GetType().GetField("sharedSessionStateManager", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
      Assert.IsNotNull(sharedSessionStateManagerField, "Didn't find field 'sharedSessionStateManager' in type '{0}'.", typeof(ContactManager));
      var sssm = (SharedSessionStateManager)sharedSessionStateManagerField.GetValue(manager);
      Assert.IsNotNull(sssm, "Shared session state manager field value is null.");

      var contactLockIdsProperty = sssm.GetType().GetProperty("ContactLockIds", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
      Assert.IsNotNull(contactLockIdsProperty, "Didn't find property 'ContactLockIds' in type '{0}'.", sssm.GetType());
      var contactLockIds = (Dictionary<Guid, object>)contactLockIdsProperty.GetValue(sssm);
      Assert.IsNotNull(contactLockIds, "Contact lock IDs property value is null.");
      contactLockIds.Remove(contact.ContactId);
    }
  }
}

HOW TO USE THE CODE:

// Create an instance of the repository
ExtendedContactRepository extendedContactRepository = new ExtendedContactRepository();
// Get a contact by a username
Contact contact = extendedContactRepository.GetOrCreateContact(userName);

// Do some code that updates the contact
// For example update a facet:
// https://briancaos.wordpress.com/2015/07/16/sitecore-contact-facets-create-your-own-facet/

// Save the contact
extendedContactRepository.ReleaseAndSaveContact(contact);

SOME EXPLANATION:

The 2 public methods, GetOrCreateContact() and ReleaseAndSaveContact(), are the getter and setter methods.

GetOrCreateContact() tries to get a lock on a Contact. If the lock is successful, a Contact is found and the Contact can be returned.  If not, no Contact is found and we create one.

ReleaseAndSaveContact() saves and releases the contact which means that the data is stored in the Shared Session, and the contact is released; the ClearSharedSessionLocks() attempts to release the locks from the Sitecore Shared Session  State Database. Please note that the data is still not stored directly in xDB but in the Shared Session, and data is flushed when the session expires. The trick is that we open the contact in write-mode, and release the Contact after the update, making it available immediately after by other threads.

Generally, when using the Sitecore ContactManager(), data is not manipulated directly. Only when using the Sitecore ContactRepository() you update xDB directly, but Sitecore does not recommend this, as it may have unwanted side effects.

MORE TO READ:

 

Advertisements
Posted in .net, c#, General .NET, Sitecore 8 | Tagged , , , , , , , , | 20 Comments

Sitecore register page events

A Page Event in Sitecore is a way of tracking when a user have reached a goal by executing a call to action on a specific page, for example:

  • Downloaded a brochure
  • Clicked your banner
  • Done a search

A page event can be any text you like, and you can attach any data you wish.

You use the Sitecore.Analytics.Tracker to register the page event. Here is a small example on how to do it:

using System;
using Sitecore.Analytics;
using Sitecore.Analytics.Data;
using Sitecore.Analytics.Tracking;
using Sitecore.Diagnostics;

namespace MyNamespace
{
  public class RegisterPageDataService
  {
    public void RegisterPageEvent(string eventName, string text, string data, string dataKey)
    {
      if(!Tracker.Enabled)
        return;

      if (!Tracker.IsActive)
        Tracker.StartTracking();

      ICurrentPageContext currentPage = Tracker.Current.CurrentPage;
      if (currentPage == null)
        return;

      RegisterEventOnCurrentPage(eventName, text, data, dataKey, currentPage);
    }

    private static void RegisterEventOnCurrentPage(string eventName, string text, string data, string dataKey, IPageContext currentPage)
    {
      PageEventData pageEvent = new PageEventData(eventName) 
        {
            Text = text, 
	    Data = data ?? string.Empty, 
            DataKey = string.IsNullOrEmpty(dataKey) ? string.Empty : dataKey
        };
      try
      {
        currentPage.Register(pageEvent);
      }
      catch (Exception exception)
      {
        Log.Error(string.Format("{0} pageevent not created in current Sitecore Instance: {1}", eventName, exception), typeof(RegisterPageDataService));
      }
    }
  }
}

Using the code is pretty straight forward:

// Registering a click goal
RegisterPageDataService service = new RegisterPageDataService();
service.RegisterPageEvent("Goal", "Clicked", string.Empty, string.Empty);

// Registering a search
RegisterPageDataService service = new RegisterPageDataService();
service.RegisterPageEvent("Search", "Freetext", "Pentia A/S", "Search");

The register page event ends up in the xDB (MongoDB) in the”Interactions” collection, look for “Pages” (this example is one of Sitecore’s events, not my event):

Page Event

Page Event

MORE TO READ:

 

 

Posted in c#, General .NET, Sitecore 8 | Tagged , , , , , | Leave a comment

Sitecore locating sublayouts on your webpage

This is a followup from the post, Get Sitecore placeholders and rendering hierarchy from a Sitecore item, where I adress one of the difficulties when working with pages in Sitecore, that they tend to get very complex as one page may contain 10-30 sublayouts and renderings.
Is is very easy to loose track of which sublayout is placed where, especially if you use nested or dynamic placeholders on your pages.

My colleagues Laust Skat Nielsen and Jannik Nilsson came up with this solution to write out the name of the sublayout in the rendered HTML when in debug mode.

Their method adds a comment to the HTML when a control is rendered:

Showing the path to the control

Showing the path to the control

It is very simple to do. On PreRender, simply go through the control collection and inject the path before the usercontrol is rendered. Place the following code in the codebehind of your Layout (.aspx) file.

protected void Page_PreRender(object sender, EventArgs e)
{
  if (HttpContext.Current.IsDebuggingEnabled)
  {
    InjectControlPaths(Controls);
  }
}

protected void InjectControlPaths(ControlCollection controlCollection)
{
  foreach (Control control in controlCollection)
  {
    if (control is UserControl)
    {
      try
      {
        control.Controls.AddAt(0, new LiteralControl("<!-- Control path: " + (control as UserControl).AppRelativeVirtualPath + " -->"));
      }
      catch (Exception exception)
      {
        Sitecore.Diagnostics.Log.Debug("Failed to inject comment into " + (control as UserControl).AppRelativeVirtualPath);
      }
    }
    InjectControlPaths(control.Controls);
  }
}

Notice how the control tree is only rendered when in debugging mode. A very simple and clever solution to a common nuisance. Thanks guys.

 

Posted in Sitecore 6, .net, Sitecore 7, Sitecore 8 | Tagged , , , , , | 5 Comments

Sitecore contact facets – Create your own facet

This article describes how to create a simple Sitecore facet consisting of a DateTime and a list of strings.

A contact is made up of facets. Here are all the facets Sitecore uses (you will find the facets in \App_Config\Include\Sitecore.Analytics.Model.Config):

<facets>
  <facet name="Personal" contract="Sitecore.Analytics.Model.Entities.IContactPersonalInfo, Sitecore.Analytics.Model" />
  <facet name="Addresses" contract="Sitecore.Analytics.Model.Entities.IContactAddresses, Sitecore.Analytics.Model" />
  <facet name="Emails" contract="Sitecore.Analytics.Model.Entities.IContactEmailAddresses, Sitecore.Analytics.Model" />
  <facet name="Phone Numbers" contract="Sitecore.Analytics.Model.Entities.IContactPhoneNumbers, Sitecore.Analytics.Model" />
  <facet name="Picture" contract="Sitecore.Analytics.Model.Entities.IContactPicture, Sitecore.Analytics.Model" />
  <facet name="Communication Profile" contract="Sitecore.Analytics.Model.Entities.IContactCommunicationProfile, Sitecore.Analytics.Model" />
  <facet name="Preferences" contract="Sitecore.Analytics.Model.Entities.IContactPreferences, Sitecore.Analytics.Model" />
</facets>

In this example I will add a facet that consists of a date and a list of strings. I will call it “AvailablePublishers“.

This is a real-life example where I needed to store a list of publishers that were available the last time the user was online. Each publisher is just an ID (a string) and I store these as a list on the Contact:

Available Publishers Facet

Available Publishers Facet

It sounds simple, and it is – but there is a lot of code involved. So hang on, lets code.

STEP 1: THE BASIC INTERFACES

The “AvailablePublishers” is a Facet, the list below consists of Elements. So I need to create a IFacet interface and a IElement interface.

Here is the IFacet:

using System;
using Sitecore.Analytics.Model.Framework;

namespace PT.AvailablePublishers
{
  public interface IAvailablePublishersFacet : IFacet
  {
    IElementCollection<IAvailablePublishersElement> AvailablePublishers { get; }
    DateTime Updated { get; set; }
  }
}

The IFacet contains a list (IElementCollection) of my IElement. Here is the IElement:

using Sitecore.Analytics.Model.Framework;

namespace PT.AvailablePublishers
{
  public interface IAvailablePublishersElement : IElement, IValidatable
  {
    string PublisherID { get; set; }
  }
}

STEP 2: THE IMPLEMENTATION

Now we need concrete classes implementing IAvailablePublishersFacet and IAvailablePublishersElement:

Here is the AvailablePublishersFacet class:

using System;
using Sitecore.Analytics.Model.Framework;

namespace PT.AvailablePublishers
{
  [Serializable]
  public class AvailablePublishersFacet : Facet, IAvailablePublishersFacet
  {
    public static readonly string _FACET_NAME = "AvailablePublishers";
    private const string _UPDATED_NAME = "LastUpdated";

    public AvailablePublishersFacet()
    {
      EnsureCollection<IAvailablePublishersElement>(_FACET_NAME);
    }

    public IElementCollection<IAvailablePublishersElement> AvailablePublishers
    {
      get
      {
        return GetCollection<IAvailablePublishersElement>(_FACET_NAME);
      }
    }


    public DateTime Updated
    {
      get
      {
        return GetAttribute<DateTime>(_UPDATED_NAME);
      }
      set
      {
        SetAttribute(_UPDATED_NAME, value);
      }
    }
  }
}

and the AvailablePublishersElement class:

using System;
using Sitecore.Analytics.Model.Framework;

namespace PT.AvailablePublishers
{
  [Serializable]
  public class AvailablePublishersElement : Element, IAvailablePublishersElement
  {
    private const string _PUBLISHERID = "PublisherID";

    public AvailablePublishersElement()
    {
      EnsureAttribute<string>(_PUBLISHERID);
    }
    
    public string PublisherID
    {
      get
      {
        return GetAttribute<string>(_PUBLISHERID);
      }
      set
      {
        SetAttribute(_PUBLISHERID, value);
      }
    }
  }
}

Both classes are serializable.

Getting and setting properties are done using GetAttribute and SetAttribute methods retrieved from Sitecore.Analytics.Model.Framework.Element and Sitecore.Analytics.Model.Framework.Facet.

Lists are done using IElementCollection or IElementDictionary, provided by Sitecore.

STEP 3: REGISTER FACET IN SITECORE CONFIGURATION

The facets and elements are registered in Sitecore. In the configuration you also register the  IAvailablePublishersFacet as an element, even when it inherits from IFacet.

This is a simplified version of the \App_Config\Include\Sitecore.Analytics.Model.config where I have removed all other elements but my own:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <model>
      <elements>
        <element interface="PT.AvailablePublishers.IAvailablePublishersElement, MyDLL" implementation="PT.AvailablePublishers.AvailablePublishersElement, MyDLL" />
        <element interface="PT.AvailablePublishers.IAvailablePublishersFacet, MyDLL" implementation="PT.AvailablePublishers.AvailablePublishersFacet, MyDLL" />
      </elements>
      <entities>
        <contact>
          <facets>
            <facet name="AvailablePublishers" contract="PT.AvailablePublishers.IAvailablePublishersFacet, MyDLL" />
          </facets>
        </contact>
      </entities>
    </model>
  </sitecore>
</configuration>

So as you can see, both my interfaces are defined in <elements/>. The <elements/> describes the implementation of the interface, just like IOC would do it.

The facet is defined in <facets/> with a unique name. This unique name is the name you use to find the facet when you need to access it.

STEP 4: CALL THE FACET

The facet is now defined. Next step is to use the facet. To retrieve a facet you need a Contact. The contact is usually retrieved from the Sitecore Tracker:

Contact contact = Tracker.Current.Session.Contact;

This is a repository than can get the list of available publishers from a contact, and update the list of available publishers:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using PT.AvailablePublishers;
using Sitecore.Analytics.Tracking;

namespace PT.Forum.Domain.NejTakPlus.Model.Repositories
{
  public class AvailablePublishersRepository
  {
    public IEnumerable<string> Get(Contact contact)
    {
      IAvailablePublishersFacet facet = contact.GetFacet<IAvailablePublishersFacet>(AvailablePublishersFacet._FACET_NAME);
      return facet.AvailablePublishers.Select(element => element.PublisherID);
    }

    public void Set(Contact contact, IEnumerable<string> availablePublisherKeys)
    {
      IAvailablePublishersFacet facet = contact.GetFacet<IAvailablePublishersFacet>(AvailablePublishersFacet._FACET_NAME);
      facet.Updated = DateTime.Now;
      while (facet.AvailablePublishers.Count > 0)
        facet.AvailablePublishers.Remove(0);
      foreach (string availablePublisherKey in availablePublisherKeys)
      {
        facet.AvailablePublishers.Create().PublisherID = availablePublisherKey;
      }
    }
  }
}

Notice the contact.GetFacet<>() method. Here you specify the name of the facet you defined in the <facets/> section of the config file. Luckily you also define the same name in the code, so I can get the facet name from my AvailablePublishersFacet class.

Also notice the Set() method. If you wish to clear the list before inserting, you need to iterate through the list and remove them one by one. There is no Clear() method.

Remember that facets are not written to MongoDB before your session ends. So you cannot see your fact in Mongo before your session is abandoned. You can try calling:

Session.Abandon();

To force Sitecore to write the data to MongoDB.

That’s it. Happy coding.

MORE TO READ:

 

Posted in .net, c#, General .NET, Sitecore 8 | Tagged , , , , , , , , | 14 Comments

Sitecore 8 and Tracker.Current.Session.Identify – Overriding expired contact session lock for contact id

The new Sitecore 8 Experience Profile is a vital part, yes almost a cornerstone of the new xDB concept.

UPDATE 2018-01-19: Please note that in Sitecore 9, Tracker.Current.Session.Identify have been replaced with Tracker.Current.Session.IdentifyAs. For more information, see Sitecore 9 Tracker.Current.Session.Identify is replaced with Tracker.Current.Session.IdentifyAs.

In xDB, you store information about the current user, anonymous or named, as a Contact in the Experience Profile (stored in the MongoDB).
Any user begins his life as an anonymous user, and the associated Contact object has no identifier, only a key matched in the cookie of the current user.

In order to connect the Contact with a named user, you use the Tracker.Current.Session.Identify(userName) method:

if (!Tracker.IsActive)
  return;
Tracker.Current.Session.Identify("extranet\\user@domain.com");

The method identifies the user@domain.com as a Contact, creates a named Contact if the contact does not exist, or merges the current contact data into one named Contact. Also, the Contact will be locked for this user.

Please note that Tracker.Current.Session.Identify(userName) accepts a string. You can create named contacts with any key. This is great if your named users does not come from Sitecore Users.

But here lies the danger. If you by accident Identifies extranet\anonymous, every anonymous visitor will be merged and locked to the same Contact. And since a contact can only be locked to one session at a time, any subsequent calls to Tracker.Current.Session.Identify will wait, and wait, and wait, … until the previous contact unlocks the Contact.

And the log will be filled with:

Message: Failed to extend contact lease for contact 43188c31-cbe9-4386-8739-c12c3dc049c2

Or even:

2844 18:01:11 ERROR Cannot finish Analytics page tracking
Exception: Sitecore.Analytics.Exceptions.ContactLockException
Message: Failed to extend contact lease for contact 43188c31-cbe9-4386-8739-c12c3dc049c2
Source: Sitecore.Analytics
at Sitecore.Analytics.Tracking.ContactManager.SaveAndReleaseContact(Contact contact)
at Sitecore.Analytics.Pipelines.EndAnalytics.ReleaseContact.Process(PipelineArgs args)
at (Object , Object[] )
at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
at Sitecore.Analytics.Pipelines.HttpRequest.EndAnalytics.Process(HttpRequestArgs args)

So it is VERY important that you check that the user identified is not your anonymous user:

// THIS IS BAD!!!
// The user could be extranet\anonymous
if (!Tracker.IsActive)
  return;
Tracker.Current.Session.Identify(Sitecore.Context.User.Name);

// THIS COULD BE A SOLUTION:
if (!Tracker.IsActive)
  return;
if (Sitecore.Current.User.Name.ToLower() == "extranet\\anonymous")
  return;
Tracker.Current.Session.Identify(Sitecore.Context.User.Name);

// OR MAYBE THIS?
if (!Tracker.IsActive)
  return;
if (!Sitecore.Context.User.IsAuthenticated)
  return;
Tracker.Current.Session.Identify(Sitecore.Context.User.Name);

MORE TO READ:

Posted in c#, Sitecore, Sitecore 8 | Tagged , , , , | 8 Comments

Extend the Sitecore FieldRenderer

The Sitecore FieldRenderer is the render controls used to render fields from Sitecore in a way that makes them page editable from the Page Editor (or Experience Editor as it is called in Sitecore 8).

The FieldRenderer has been known since Sitecore 5, and is still the preferred method of rendering fields when using User Controls (Sublayouts):

<%@ Register TagPrefix="sc" Namespace="Sitecore.Web.UI.WebControls" Assembly="Sitecore.Kernel" %>

<sc:FieldRenderer runat="server" FieldName="MyField" />
<sc:Text ID="txtText" runat="server" Field="FlowProfileEulaAndTermsSubTitle" Item="<%# this.DataSource %>"/>

The examples above shows how to use the FieldRenderer from User Controls. The sc:FieldRenderer is the base class, and the sc:Text is a text specific renderer inheriting from the FieldRenderer.

This is an example of an extended FieldRenderer I have been using for the last couple of years. The extension is used to render simple text fields such as headers, text etc. To make this easier we have added a few properties to our FieldRenderer such as:

To create a new FieldRenderer you need to inherit from Sitecore.Web.UI.WebControls.FieldControl.
The important method is protected override void DoRender(HtmlTextWriter output). Inside this method you instantiate your own FieldRenderer control, and voila, your control is Page Editable.

Here is the structure in pseudocode:

using System;
using System.ComponentModel;
using System.Web.UI;
using Sitecore.Collections;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Web;
using Sitecore.Web.UI.WebControls;
using Sitecore.Xml.Xsl;

namespace MyFieldRendererControls
{
  [ParseChildren(false)]
  [PersistChildren(true)]
  public class FieldRendererExt : FieldControl
  {
 
    protected override void DoRender(HtmlTextWriter output)
    {
      string renderValue = "";
      string renderFirstPart = "";
      string renderLastPart = "";
      var fr = new FieldRenderer
        {
		  ...
		  ...
        };

      RenderFieldResult rendered = fr.RenderField();
      renderFirstPart = rendered.FirstPart;
      renderLastPart = rendered.LastPart;
      renderValue = rendered.ToString();


      output.Write(renderFirstPart);
      RenderChildren(output);
      output.Write(renderLastPart);
    }
  }
}

And here is the complete code for the enhanced FieldRenderer:

using System;
using System.ComponentModel;
using System.Web.UI;
using Sitecore.Collections;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Web;
using Sitecore.Web.UI.WebControls;
using Sitecore.Xml.Xsl;

namespace MyFieldRendererControls
{
  [ParseChildren(false)]
  [PersistChildren(true)]
  public class TextExt : FieldControl
  {
    private string after = string.Empty;
    private string before = string.Empty;
    private string cssClass = string.Empty;
    private string cssId = string.Empty;
    private string cssStyle = string.Empty;
    private string enclosingTag = string.Empty;
    private string fieldName = string.Empty;

    [Category("Method"), Description("Always output enclosing tag regardless of it being empty or not")]
    public bool AlwaysEnclosingTag
    { get; set; }

    [Category("Method"), Description("HTML tag to wrap the field value with")]
    public string EnclosingTag
    {
      get { return enclosingTag; }
      set { enclosingTag = value; }
    }

    [Category("Method"), Description("CSS class-attribute on enclosing tag")]
    public new string CssClass
    {
      get { return cssClass; }
      set { cssClass = value; }
    }

    [Category("Method"), Description("CSS style-attribute on enclosing tag")]
    public new string CssStyle
    {
      get { return cssStyle; }
      set { cssStyle = value; }
    }

    [Category("Method"), Description("CSS id-attribute on enclosing tag - will be overridden if control ClientIDMode == Static")]
    public string CssId
    {
      get { return cssId; }
      set { cssId = value; }
    }

    [Category("Method"), Description("FieldName to be rendered from datasource")]
    public string FieldName
    {
      get { return fieldName; }
      set { fieldName = value; }
    }

    [Category("Method"), Description("Disables the page editor for the control")]
    public new bool DisableWebEditing
    { get; set; }

    [Category("Method"), Description("Put some text before")]
    public string Before
    {
      get { return before; }
      set { before = value; }
    }

    [Category("Method"), Description("Put some text after")]
    public string After
    {
      get { return after; }
      set { after = value; }
    }

    [Category("Method"), Description("Set explicit what item to process")]
    public new Item Item
    { get; set; }

    protected override void DoRender(HtmlTextWriter output)
    {
      if (string.IsNullOrEmpty(Field))
      {
        Field = FieldName;
      }

      // GET ITEM
      Item currentContextItem = null;
      try
      {
        currentContextItem = GetItem();
      }
      catch (Exception ex)
      {
        Log.Error("currentContextItem exception", ex, this);
      }

      // RENDER ITEM VALUE
      bool itemValid;
      try
      {
        itemValid = (currentContextItem != null && currentContextItem.Fields[Field] != null);
      }
      catch (Exception)
      {
        itemValid = false;
      }

      string renderValue = "";
      string renderFirstPart = "";
      string renderLastPart = "";
      if (itemValid)
      {
        var fr = new FieldRenderer
        {
          Before = Before,
          After = After,
          DisableWebEditing = DisableWebEditing,
          EnclosingTag = "",
          Item = currentContextItem,
          FieldName = Field,
          Parameters = WebUtil.BuildQueryString(GetParameters(), false)
        };

        RenderFieldResult rendered = fr.RenderField();
        renderFirstPart = rendered.FirstPart;
        renderLastPart = rendered.LastPart;
        renderValue = rendered.ToString();
      }


      // OUTPUT DATA
      if (string.IsNullOrEmpty(EnclosingTag) || (string.IsNullOrEmpty(renderValue) && (!AlwaysEnclosingTag)))
      {
        // Simple value...
        output.Write(renderFirstPart);
        RenderChildren(output);
        output.Write(renderLastPart);
      }
      else
      {
        // Otherwise...
        string attributes = "";

        if (ClientIDMode == ClientIDMode.Static)
        {
          attributes += " id='" + ID + "'";
        }
        else if (!string.IsNullOrEmpty(CssId))
        {
          attributes += " id='" + CssId + "'";
        }

        if (!string.IsNullOrEmpty(CssClass))
        {
          attributes += " class='" + CssClass + "'";
        }

        if (!string.IsNullOrEmpty(CssStyle))
        {
          attributes += " style='" + CssStyle + "'";
        }

        // Wrap it in enclosing tag and attributes
        output.Write("<" + EnclosingTag + attributes + ">");
        output.Write(renderFirstPart);
        RenderChildren(output);
        output.Write(renderLastPart);
        output.Write("</" + EnclosingTag + ">");
      }
    }

    protected override Item GetItem()
    {
      var datasource = GetDatasource();
      if (datasource != null)
      {
        return datasource;
      }

      return Item ?? base.GetItem();
    }

    protected SafeDictionary<string> GetParameters()
    {
      var parameters = new SafeDictionary<string>();

      if (Controls.Count > 0)
      {
        parameters.Add("haschildren", "true");
      }

      foreach (string key in Attributes.Keys)
      {
        string str = Attributes[key];
        parameters.Add(key, str);
      }

      return parameters;
    }

    private Item GetDatasource()
    {
      var layout = GetParent(this);

      if (layout == null)
      {
        return null;
      }

      return string.IsNullOrEmpty(layout.DataSource) ? null : Sitecore.Context.Database.GetItem(layout.DataSource);
    }

    private Sublayout GetParent(Control control)
    {
      if (control.Parent == null)
      {
        return null;
      }

      var sublayout = control.Parent as Sublayout;
      return sublayout ?? GetParent(control.Parent);
    }

  }
}

To use the new control:

<%@ Register tagPrefix="EXT" namespace="MyFieldRendererControls" assembly="MyFieldRendererControls" %>

<EXT:TextExt FieldName="NameOfField" ID="fldTitle" runat="server" EnclosingTag="h1" CssClass="class" CssStyle="color:red" />

Several of my colleagues has worked on this extension, and I would like to thank them all.

 

Posted in Sitecore 5, Sitecore 6, c#, Sitecore 7, Sitecore 8 | Tagged , , , | Leave a comment

Sitecore 8 EXM Get the email recipient from a Sublayout

The Sitecore 8 Email Experience Manager (EXM) is not only about sending bulk emails. It can be used to send one-time messages like “Thank you for signing up” or “forgot your password?” directly from code.

In the previous versions of EXM, back when it was called ECM, the sublayouts of an email could get the email address of the recipient directly from the querystring parameter “ec_recipient“. But with the introduction of the xDB and the Contacts, things have complicated.

This method will return the username (not the email address) of the recipient of the email:

using System;
using System.Text;
using System.Web;
using Sitecore;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Modules.EmailCampaign;
using Sitecore.Modules.EmailCampaign.Core;
using Sitecore.Modules.EmailCampaign.Factories;
using Factory = Sitecore.Configuration.Factory;

namespace MyEXMService
{
  public class RecipientService
  {
    public string GetRecipientName(HttpRequest request)
    {
      string contactIdValue = request.QueryString[GlobalSettings.AnalyticsContactIdQueryKey];
      string itemId = Context.Request.QueryString["sc_itemid"];
      Item messageItem = Factory.GetDatabase("master").GetItem(ShortID.Parse(itemId).ToID()).Parent;
      Guid messageIdGuid = messageItem.ID.Guid;
      Guid contactId;
      using (GuidCryptoServiceProvider provider = new GuidCryptoServiceProvider(Encoding.UTF8.GetBytes(GlobalSettings.PrivateKey), messageIdGuid.ToByteArray()))
      {
        contactId = provider.Decrypt(new Guid(contactIdValue));
      }
      string contactName = EcmFactory.GetDefaultFactory().Gateways.AnalyticsGateway.GetContactIdentifier(contactId);
      return contactName;
    }
  }
}

With the username in hand, you can either look up the user in the Contacts xDB database, or in the .NET membership database:

RecipientService receipientService = new RecipientService();
string userName = receipientService.GetRecipientName(Request); 
MembershipUser user = Membership.GetUser(userName);

MORE TO READ:

Posted in c#, Sitecore 8 | Tagged , , , | 2 Comments

Sitecore 8 ServerTimeZone – my dates are all wrong

This issue appeared when I upgraded from Sitecore 7.5 to Sitecore 8. Suddenly my dates on the website were 2 hours behind – on my development server it was one hour behind.

In Sitecore, my date-time was (example) 2015-04-12 00:00:00. This date was presented as 2015-04-11 22:00:00+2.

The issue arises, because Sitecore have implemented a feature that tries to handle local time zones on the server. Old Sitecore 7.5 dates are stored without any timezone information:

  • 20150412T000000

In Sitecore 8, Sitecore have changed the raw data format of the date field by taking the timezone into consideration. Which in my case means that my 2014-04-12 00:00:00 becomes 2015-04-11 22:00:00:

  • 20150411T220000Z

Notice the “Z” at the end? This implies that this is a datetime that have a timezone, and the timezone is now 2 hours behind UTC.

To fix the old datetime fields, I need to tell Sitecore which timezone non-timezone-datetime fields have. This is the setting, ServerTimeZone:

&lt;setting name=&quot;ServerTimeZone&quot; value=&quot;UTC&quot; /&gt;

I also needed to update the Lucene index, as the invalid datetime was added to the index as well.

Thanks to Peter Wind who helped find this issue.

MORE TO READ:

Posted in Sitecore 8 | Tagged , , | 8 Comments

Sitecore Transfer items from one database to another

In this article I will describe how to transfer items from one Sitecore database to another. Usually you transfer content by publishing. But in rare cases this is not an option:

  • You are not moving content from master to web
  • You are not moving content to a publishing target.
  • You are moving content to another path in another database.
  • You would like to avoid raising any events that would clear cache.

Before you start transferring content, you need to know the following:

  • Transfer does not retain the path structure, because a transfer is like a copy, just between databases. If you would like to retain the path, you must do it yourself.
  • The templates of the transferred items must exist in the target database. If not, Sitecore will raise an TemplateNotFoundException.
  • Transferring is done by copying the OuterXML of an item into a string and pasting this string to another database. This could be a very CPU and memory heavy process if you choose to copy the root of a large website.

Enough talk. Here is the code:

using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Data.Proxies;
using Sitecore.Diagnostics;
using Sitecore.Exceptions;

namespace MyNameSpace
{
  public class ArchiveRepository
  {
    public void Put(Item source, Item destination, bool deep)
    {
      using (new ProxyDisabler())
      {
        ItemSerializerOptions defaultOptions = ItemSerializerOptions.GetDefaultOptions();
        defaultOptions.AllowDefaultValues = false;
        defaultOptions.AllowStandardValues = false;
        defaultOptions.ProcessChildren = deep;
        string outerXml = source.GetOuterXml(defaultOptions);
        try
        {
          destination.Paste(outerXml, false, PasteMode.Overwrite);
          Log.Audit(this, "Transfer from {0} to {1}. Deep: {2}", new[]{ AuditFormatter.FormatItem(source), AuditFormatter.FormatItem(destination), deep.ToString() });
        }
        catch (TemplateNotFoundException)
        {
          // Handle the template not found exception
        }
      }
    }

  }
}

Parameter source is the item to copy, parameter destination is the item in the destination database to copy to. Parameter deep determines if you transfer one item of the whole tree.

I created a few more methods to allow for transferring an item from one database to another whilst retaining the path structure. By adding the destination database as private property I hard-code the repository to a specific destination database.

The method Put(item) takes a source item, creates the item path if not found, then transfer the item:

    private readonly Database _database = Factory.GetDatabase("DestinationDatabase");

    public void Put(Item item)
    {
      EnsurePath(item);
      Put(item, true);
    }

    private void Put(Item item, bool deep)
    {
      Item destination = _database.GetItem(item.Parent.ID);
      Put(item, destination, deep);
    }

    private void EnsurePath(Item item)
    {
      foreach (Item ancestor in item.Axes.GetAncestors())
      {
        if (_database.GetItem(ancestor.ID) == null)
          Put(ancestor, false);
      }
    }

MORE TO READ:

Posted in c#, General .NET, Sitecore 6, Sitecore 7, Sitecore 8 | Tagged , , , | 2 Comments

All my Sitecore items are called __Standard Values – The fix

In the previous post i described how a tiny error in Sitecore made you believe that all of the Sitecore items are called __Standard Values.

The error occurs in the Danish language because Sitecore unintentionally added the word “__Standard Values” to the Display Name of several items.

A friend of mine asked for a fix. Here it is. You should:

  • Create a new .aspx page in the /sitecore modules/shell folder
  • Ensure that the website is running in the Danish (da) language
  • Copy the following contents to the .aspx page you created.
  • Call the page.

The code iterates through all Sitecore templates, identifies the __Standard Values templates where the display name is “__Standard Values” and deletes the contents in the Display Name field.

<%@ Page language="c#" EnableEventValidation="false" AutoEventWireup="true" %>

<script runat="server">
  
  void Page_Load(object sender, System.EventArgs e) 
  {
    Response.Buffer = false;
    Response.BufferOutput = false;
    Response.Write(string.Format("{0} {1}<br/>", Sitecore.Context.ContentDatabase.Name, Sitecore.Context.Language));
    Sitecore.Data.Items.Item templateRoot = Sitecore.Context.ContentDatabase.GetItem("/sitecore/templates");
    Iterate(templateRoot);
  }

  void Iterate(Sitecore.Data.Items.Item root)
  {
    foreach (Sitecore.Data.Items.Item child in root.GetChildren())
    {
      if (child.Name == "__Standard Values" && child["__display name"] == "__Standard Values")
      {
        Response.Write(string.Format("path: {0}. Name: {1}. Display Name: {2}<br/>", child.Paths.FullPath, child.Name, child["__display name"]));
        FixDisplayName(child);
      }
      if (child.HasChildren)
        Iterate(child);
    }
  }

  void FixDisplayName(Sitecore.Data.Items.Item item)
  {
    item.Editing.BeginEdit();
    item.Fields["__display name"].Value = string.Empty;
    item.Editing.EndEdit();
  }

</script>  
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
  <head>
    <title>www.sitecore.net</title>
    <meta content="Microsoft Visual Studio 7.0" name="GENERATOR">
    <meta content="C#" name="CODE_LANGUAGE">
    <meta content="JavaScript" name="vs_defaultClientScript">
    <meta content="http://schemas.microsoft.com/intellisense/ie5" name="vs_targetSchema">
    <link href="/default.css" rel="stylesheet">
  </head>
  <body>
    <form runat="server">
    </form>
  </body>
</html>

As always, use at own risk, take a backup before running etc.

Posted in c#, General .NET, Sitecore 7, Sitecore 8 | Tagged , | 1 Comment