So we are doing Sitecore MVP announcements now, are we?

Sitecore MVP 2018

Sitecore MVP 2018

Yes, again Sitecore thought that my rants about contacts, experience editor, SOLR, and other Sitecore related topics, are good enough to be awarded with the Sitecore MVP title.

My first award was given back in 2010. Back then, the Sitecore MVP title was a 2 year nomination, which only gave you a badge for your website. Today, the title bring more than just the glory. Access to early product releases, MVP forums and the legendary MVP Summit adds value to the award.

This year, 5 of my colleagues in Pentia have also been awarded, making Pentia the only danish company with 6 MVP awards:

  • Alan Coates, Technology MVP, for most of you known as one of the organizers behind SUGCON. Oh, and he also knows everything.
  • Christina Hauge Engel, the only Danish Digital Strategist MVP, and the go-to-girl when the personalization and analytics powers of Sitecore needs to be unleashed.
  • Jens Gustafsson, Technology MVP, my Swedish colleague, with the excellent blog on Sitecore issues.
  • Mads-Peter Jakobsen, former digital strategist, now holds the Ambassador MVP title. Where Christina solves the how’s behind analytics, Mads-Peter finds the why’s.
  • Thomas Stern, Technology MVP, is also organizing SUGCON’s, and like Alan Coates, knows everything.

Congratulations to all of the MVP’s around the world. I hope to see many of you at the upcoming SUGCON’s and the MVP Summit in Orlando.

Advertisements
Posted in Sitecore | Leave a comment

In Sitecore 9, the ProxyDisabler have been retired completely

Sitecore have finally retired the ProxyDisabler in Sitecore 9. Proxy items were the early version of item cloning and were deprecated in Sitecore 6. And now the ProxyDisabler have been removed.

There are no replacement. All you need to do is to remove the line from your code.

// Old Sitecore 5,6,7,8 code:
public void Put(Item source)
{
  using (new ProxyDisabler())
  {
    // Do stuff with your item
  }
}

// New Sitecore 9 code:
public void Put(Item source)
{
  // Do stuff with your item
}

MORE TO READ:

Posted in c#, General .NET, Sitecore 5, Sitecore 6, Sitecore 7, Sitecore 8, Sitecore 9 | Tagged , | Leave a comment

Sitecore 9 Tracker.Current.Session.Identify is replaced with Tracker.Current.Session.IdentifyAs

In Sitecore 9, Sitecore have decided to change how you identify named users, i.e. how you match a Contact with a user that is logged into your website. The  Tracker.Current.Session.Identify method is obsolete. It has been replaced with Tracker.Current.Session.IdentifyAs:

// Sitecore 8 identification method:
public static void IdentifyUser(string username)
{
  // Never identify an anonymous user
  if (username.ToLower() == "extranet\\anonymous")
	return;

  if (Tracker.Current != null && Tracker.Current.IsActive && Tracker.Current.Session != null)
  {
	Tracker.Current.Session.Identify(username);
  }
}

// Sitecore 9 identification method:
public static void IdentifyUser(string username)
{
  // Never identify an anonymous user
  if (username.ToLower() == "extranet\\anonymous")
	return;

  string identificationSource = "website";
  if (Tracker.Current != null && Tracker.Current.IsActive && Tracker.Current.Session != null)
  {
	Tracker.Current.Session.IdentifyAs(identificationSource, username);
  }
}

The IdentifyAs() takes 2 parameters:

  • Source: A string that identifies where this contact comes from (for instance, “twitter” or “website”)
  • Identifier: The identifier itself (username, email, customerID or any other string based on your implementation)

Thanks to Sitecore support for the claification.

MORE TO READ:

 

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

Sitecore Paging SOLR Responses

When querying Sitecore items from the SOLR index, you do not need to get all items in one go. In fact, the default SOLR implementation will stop at 5000 returned items, so for large queries, you need to use the .Page() method:

private IEnumerable<MyItem> GetPage(int page, int pageSize)
{
  using (IProviderSearchContext searchContext = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext())
  {
    return
      searchContext.GetQueryable<MyItem>()
        .Page(page, pageSize)
        .ToArray();
  }
}

If you are using filters or predicates, remember that the .Page() method is the last to call. If you filter your query after the .Page() is called, you would at best have a filtered result based on the paged result, at worst have an error at execution time:

// This is wrong. The predicate filters are applied after the
// paging have occurred. At best, you will first retrieve some MyItem's
// (lets's say 10), then filter on those. The result will be less than 
// the expected 10 results.
searchContext.GetQueryable<MyItem>()
	.Page(page, pageSize)
	.Where(Predicates.IsDerived<MyItem>(Constants.Templates.Advert))
	.OrderBy(myItem => myItem.SortOrder)
	.ToArray();

// This is correct. First the filtered list of MyItems are retrieved,
// then we grab a portion of those
searchContext.GetQueryable<MyItem>()
	.Where(Predicates.IsDerived<MyItem>(Constants.Templates.Advert))
	.OrderBy(myItem => myItem.SortOrder)
	.Page(page, pageSize)
	.ToArray();

If you are new into Sitecore and SOLR, I would recommend that you read the article Sitecore ContentSearch – Get items from SOLR or Lucene – A base class implementation. Here you will get the basics on how to:

  • Map a C# class to a Sitecore Template using the IndexFieldAttribute’s
    In the example above, the <MyItem> is a C# model class where properties have been mapped to Sitecore Template Field Names
  • How to create a nifty base class for all of your search model classes.
  • How to use Predicates to do filtered search queries.

MORE TO READ:

 

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

Sitecore publish:end and publish:end:remote

In Sitecore, you have several ways of executing code after a publish. The publish:end and publish:end:remote events are the 2 most obvious choices.

There is a little confusion as to when in the publish pipeline these events are fired. In previous versions (prior to Sitecore 7.2), publish:end and publish:end:remote was fired for each language and each target, and publish:complete and publish:complete:remote was fired when the publish job was done. But in later versions, publish:end and publish:end:remote is also only fired once, when the current publish operation is completed.

The :remote events (publish:end:remote and publish:complete:remote) is fired on your remote (content delivery, or CD) servers.

Please note that the EventArgs are not the same for publish:end and publish:end:remote. So to properly handle publish events you need 2 different functions in your code.

To handle the publish:end event, you will need the following function:

public void PublishEnd(object sender, EventArgs args)
{
  var sitecoreArgs = args as Sitecore.Events.SitecoreEventArgs;
  if (sitecoreArgs == null)
    return;

  var publisher = sitecoreArgs.Parameters[0] as Publisher;
    return;

  var rootItem = publisher.Options.RootItem;
  
  // Do code
}

To handle the publish:end:remote event, you need the following function:

public void PublishEndRemote(object sender, EventArgs args)
{
  var sitecoreArgs = args as Sitecore.Data.Events.PublishEndRemoteEventArgs;
  if (sitecoreArgs == null)
    return;

  Item rootItem = Factory.GetDatabase("web").GetItem(new ID(sitecoreArgs.RootItemId));
  
  // Do code
}

And in the configuration you need to point to the proper method:

<sitecore>
  <events>
    <event name="publish:end">
      <handler type="MyNamespace, MyDll" method="PublishEnd"/>
    </event>
    <event name="publish:end:remote">
      <handler type="MyNamespace, MyDll" method="PublishEndRemote"/>
    </event>
  </events>
</sitecore>

MORE TO READ:

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

Sitecore replace spaces with dashes in URL’s

In Sitecore, there is an easy of avoiding the %20 signs in your URL’s. To clarify: If you have an item with a space in the name, the space will appear in the URL as a %20 sign. For example, the item with name “read this” will appear as:

https://yourwebsite.com/read%20this

To avoid this, Sitecore have implemented a simple encodeNameReplacements function. In the sitecore.config, Sitecore have defined a list of character replacements:

<encodeNameReplacements>
  <replace mode="on" find="&amp;" replaceWith=",-a-," />
  <replace mode="on" find="?" replaceWith=",-q-," />
  <replace mode="on" find="/" replaceWith=",-s-," />
  <replace mode="on" find="*" replaceWith=",-w-," />
  <replace mode="on" find="." replaceWith=",-d-," />
  <replace mode="on" find=":" replaceWith=",-c-," />
</encodeNameReplacements>

To make a new replacement, simply add it to the list. In my example, I would like to replace space (” “) with a dash (“-“):

<replace mode="on" find=" " replaceWith="-" />

From now on, all my spaces will be replaced with a dash, and my “read this” page will now have this URL:

https://yourwebsite.com/read-this

BEWARE OF THE INFAMOUS MEDIA LIBRARY REPLACEMENT BUG

In certain versions of Sitecore 7.x and Sitecore 8.1, Sitecore have made a mistake where any item in the media library with a – in the item name will be reversely replaced internally to a space, thus making the URL invalid.

In other words: If your space/dash replacement suddenly breaks media library URL’s, try removing any dashes from the media library item name and see if the problem goes away.

Sitecore support have several bug fixes for this, depending of the Sitecore version.

MORE TO READ:

 

Posted in Sitecore 7, Sitecore 8, Sitecore 9 | Tagged , | 1 Comment

Using C# HttpClient from Sync and Async code

The .NEt 4.5 C# System.Net.Http.HttpClient() is a very nice http client implementation, but can be tricky to use if you (like me) are not a trained asynchronous programming coder. So here is a quick cheat sheet on how to work around the Task<>, async and await methods when using the HttpClient().

UPDATE 2018-01-19: Removed the HttpClient from the Using clause to avoid having to instantiate a new HttpClient for every request, as this can lead to socket exhaustion. For more information, read this Improper Instantiation antipattern article from Sitecore.

EXAMPLE 1: HTTPCLIENT GET WITH RETURN VALUE:

THE GET METHOD:

private static readonly HttpClient _httpClient= new HttpClient();

public static async Task<string> Get(string queryString)
{
  string authUserName = "user"
  string authPassword = "password"
  string url = "https://someurl.com";

  // If you do not have basic authentication, you may skip these lines
  var authToken = Encoding.ASCII.GetBytes($"{authUserName}:{authPassword}");
  httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(authToken));

  // The actual Get method
  using (var result = await _httpClient.GetAsync($"{url}{queryString}"))
  {
    string content = await result.Content.ReadAsStringAsync();
    return content;
  }
}

THE USAGE:

// From synchronous code
string queryString = "?hello=world";
string result = Get(queryString).Result;

// From asynchronous code
string queryString = "?hello=world";
string result = await Get(queryString);

 

EXAMPLE 2: HTTPCLIENT PUT “FIRE-AND-FORGET” WITHOUT RETURN VALUE:

THE PUT METHOD:

private static readonly HttpClient _httpClient = new HttpClient();

public static async Task Put(string postData)
{
  string authUserName = "user";
  string authPassword = "password"
  string url = "https://someurl.com";
        
  // If you have no basic authentication, you can skip thses lines
  var authToken = Encoding.ASCII.GetBytes($"{authUserName}:{authPassword}");
  httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(authToken));
    
  // The actual put method including error handling
  using(var content = new StringContent(postData))
  {
    var result = await _httpClient.PutAsync($"{url}", content);
    if (result.StatusCode == HttpStatusCode.OK)
    {
      return;
    }
    else
    {
      // Do something with the contents, like write the statuscode and
      // contents to a log file
      string resultContent = await result.Content.ReadAsStringAsync();
      // ... write to log
    }
  }
}

THE USAGE:

// You can call the method from asynchronous
// and it will actually run asynchronous. In this fire-and-forget 
// pattern, there is no need to wait for the answer
Put("data");

// ... but if you will wait, simply call ".Wait()":
Put("data").Wait();

MORE TO READ:

 

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

Sitecore Postbacks not working? Contents not updated on button click? Try removing caching from your rendering

This is a classic Sitecore blunder – suddenly postbacks or button clicks have no effect, as if the web site no longer are connected to your webforms codebehind or the MVC renderings. And you have not changed any code.

But have you enabled caching of your rendering?

Sitecore Caching Settings

Sitecore Caching Settings

The Sitecore rendering cache is a true HTML cache. The output markup of the rendering is cached, and the contents of the cache is stored in one or more versions, depending of your choices:

  • Clear on Index Update: Cache cleared when the item is updated in the index
  • Vary By Data: One cache per URL
  • Vary By Device: One cache per device
  • Vary By Login: One cache for anonymous users, another for authenticated users
  • Vary By Parm: One cache per rendering parameter
  • Vary By Query String: One cache per query string parameter
  • Vary by User: On version per user

So a true HTML cache means that your code is called once, and the next time the rendering is rendered, the contents is a string-output operation of the markup stored in the cache. So there is no postbacks from Webforms. Or if you expect the MVC rendering to update the markup on a button click – that’s not going to happen.

Caching have 2 further properties:

  • Your caching settings can be overridden on the presentation details of an item.
  • If a cached rendering have a placeholder containing other renderings, the renderings in the placeholder is also cached.

BTW, you can always clear the cache: Check out the /sitecore/admin/cache.aspx page, or read about how to clear cache individually from code.

MORE TO READ:

 

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

Sitecore SOLR error: Invalid Date in Date Math String

There seems to be an issue with certain combinations of Sitecore, SOLR and the local machine datetime settings. This is the error:

ManagedPoolThread #11 12:20:50 INFO Job started: Index_Update_IndexName=sitecore_master_index
ManagedPoolThread #11 12:20:50 ERROR 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.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Sitecore.Reflection.ReflectionUtil.InvokeMethod(MethodInfo method, Object[] parameters, Object obj)
at Sitecore.Jobs.JobRunner.RunMethod(JobArgs args)
at (Object , Object[] )
at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
at Sitecore.Jobs.Job.ThreadEntry(Object state)

Nested Exception

Exception: SolrNet.Exceptions.SolrConnectionException
Message: <?xml version=”1.0″ encoding=”UTF-8″?>
<response>
<lst name=”responseHeader”><int name=”status”>400</int><int name=”QTime”>46</int></lst><lst name=”error”><str name=”msg”>Invalid Date in Date Math String:’2017-03-20T13.56.31Z'</str><int name=”code”>400</int></lst>
</response>

Source: SolrNet
at SolrNet.Impl.SolrConnection.PostStream(String relativeUrl, String contentType, Stream content, IEnumerable`1 parameters)
at SolrNet.Impl.SolrConnection.Post(String relativeUrl, String s)
at SolrNet.Impl.SolrBasicServer`1.SendAndParseHeader(ISolrCommand cmd)
at Sitecore.ContentSearch.SolrProvider.SolrBatchUpdateContext.AddRange(IEnumerable`1 group, Int32 groupSize)
at Sitecore.ContentSearch.SolrProvider.SolrBatchUpdateContext.Commit()
at Sitecore.ContentSearch.AbstractSearchIndex.PerformUpdate(IEnumerable`1 indexableInfo, IndexingOptions indexingOptions)

Nested Exception

Exception: System.Net.WebException
Message: The remote server returned an error: (400) Bad Request.
Source: System
at System.Net.HttpWebRequest.GetResponse()
at HttpWebAdapters.Adapters.HttpWebRequestAdapter.GetResponse()
at SolrNet.Impl.SolrConnection.GetResponse(IHttpWebRequest request)
at SolrNet.Impl.SolrConnection.PostStream(String relativeUrl, String contentType, Stream content, IEnumerable`1 parameters)

The error is thrown by SOLR when a datetime string have an invalid format. SOLR only allows datetime strings in the format of YYYY-MM-DDThh:mm:ssZ.

Please note that in the error message above, the format is in fact wrong, as the time contains . instead of :

  • Wrong: 2017-03-20T13.56.31Z
  • Correct: 2017-03-20T13:56:31Z

My machine is configured so the time format contains . not :, and that is why SOLR gets the wrong format.

2017-10-25_13-10-27-3Sitecore have addressed this in patch 178247, where they modify the default System.DateTime converter to ignore any local culture info. The configuration change is simple:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
  <sitecore>
    <contentSearch>
      <indexConfigurations>
        <defaultSolrIndexConfiguration>
          <indexFieldStorageValueFormatter>
            <converters>
              <converter handlesType="System.DateTime" typeConverter="Sitecore.ContentSearch.Converters.IndexFieldUTCDateTimeValueConverter, Sitecore.ContentSearch" set:typeConverter="Sitecore.Support.ContentSearch.Converters.IndexFieldUTCDateTimeValueConverter, Sitecore.Support.178247"/>
            </converters>
          </indexFieldStorageValueFormatter>
        </defaultSolrIndexConfiguration>
      </indexConfigurations>
    </contentSearch>
  </sitecore>
</configuration>

And the code change very small:

namespace Sitecore.Support.ContentSearch.Converters
{
  public class IndexFieldUTCDateTimeValueConverter : IndexFieldUtcDateTimeValueConverter
  {
    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
      culture = CultureInfo.InvariantCulture;
      return base.ConvertTo(context, culture, value, destinationType);
    }
  }
}

Thanks to Sitecore support for the patch.

MORE TO READ:

Posted in Sitecore 6, Sitecore 7, Sitecore 8, Sitecore 9 | Tagged , , | Leave a comment