Sitecore – what is the hash property in the image query string?

Have you also wondered why Sitecore adds a “hash=” property to the image query string?

https://yourwebsite.com/-/media/image.jpg?w=200&hash=A1FFA19B634EDF53A3AB3B757887E671F1C452A0

The hash key will protect your images from being scaled by others than your own server. The image above will only render if the hash key matches the width parameter:

The media request protection feature restricts media URLs that contain dynamic image-scaling parameters, so that only server-generated requests are processed. This ensures that the server only spends resources and disk space on valid image-scaling requests.

Sitecore, Protect media requests

This protects your server from using resources scaling images, if anyone tries to get an image from your server in another size. If the hash doesn’t match, the image is not scaled.

The feature can be disabled. In App_config/Include/Sitecore.Media.RequestProtection.config, Set Media.RequestProtection.Enabled to false:

<setting name="Media.RequestProtection.Enabled" value="false" />

MORE TO READ:

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

Azure API Management configure CORS in the policy

Cross-Origin Resource Sharing (CORS) allows a user agent gain permission to access a web- or REST service from a server on a different domain than the site currently in use.

Modern browsers does not allow websites with Javascript that calls external URL’s. Unless you have CORS configured, you will experiences a Cross  Site Scripting error.

Microsoft Azure API Management also supports CORS. When setting up CORS you set up the following:

So in the Azure API Management publisher portal, go to Policies, select the Product and API to configure and select “Configure Policy”:

API Management Policies

API Management Policies

Add CORS to the inbound rules and set the headers in the outbound rules:

<policies>
  <inbound>
    <base />
    <cors allow-credentials="true">
      <allowed-origins>
        <origin>http://website1.com</origin>
        <origin>http://website2.com</origin>
      </allowed-origins>
      <allowed-methods>
        <method>GET</method>
      </allowed-methods>
      <allowed-headers>
        <header>content-type</header>
        <header>accept</header>
      </allowed-headers>
    </cors>
  </inbound>
  <backend>
    <base />
  </backend>
  <outbound>
    <base />
    <set-header name="Access-Control-Allow-Origin" exists-action="override">
      <value>@(context.Request.Headers.GetValueOrDefault("Origin",""))</value>
    </set-header>
    <set-header name="Access-Control-Allow-Credentials" exists-action="override">
      <value>true</value>
    </set-header>
  </outbound>
  <on-error>
    <base />
  </on-error>
</policies>

Configuration breakdown:

  • Inbound:
    • CORS allow-credentials=true allows API Management to accept credentials
    • The allowed-origins is a list of origins that have access to your service. You can add as many domains as you like.
    • Allowed-methods lists the methods you allow
    • Allowed-headers lists the headers you allow
  • Outbound:
    • The 2 headers Access-Control-Allow-Origin and Access-Control-Allow-Credentials are set in the header. This code automatically adds the calling domain to the Access-Control-Allow-Origin header.

The cool part of this configuration is that you not only allow cross scripting, but you also control which domains have access to your service. If you call the API Management endpoint from a Restlet or POSTMAN Client you get the following error:

{
"statusCode": 401,
"message": "Access denied due to invalid subscription key. Make sure to provide a valid key for an active subscription."
}

MORE TO READ:

Posted in Microsoft Azure | Tagged , , , , , , , | Leave a comment

Sitecore 9 Configuration not available on Dependency Injection – LockRecursionException: Recursive upgradeable lock acquisitions not allowed in this mode

Form Sitecore 8.2, Sitecore have implemented Dependency Injection for their own classes. Sitecore uses Microsoft’s Dependency Injection library.

Sitecore uses dependency injection to inject many things, including configurations. Therefore, you cannot access configuration before after your code have been injected.

Take the following example:

using Sitecore.Configuration;

namespace MyCode
{
  public class ServicesConfigurator() : IServicesConfigurator
  {
    public void Configure(IServiceCollection serviceCollection)
    {
      // This line will fail:
      var configuration = Factory.GetConfiguration();
      serviceCollection.AddTransient<MyClass>();
    }
  }
}

This code will thrown an error:

[LockRecursionException: Recursive upgradeable lock acquisitions not allowed in this mode.]
System.Threading.ReaderWriterLockSlim.TryEnterUpgradeableReadLockCore(TimeoutTracker timeout) +3839391
System.Threading.ReaderWriterLockSlim.TryEnterUpgradeableReadLock(TimeoutTracker timeout) +45
Sitecore.Threading.Locks.UpgradeableReadScope..ctor(ReaderWriterLockSlim mutex) +107
Sitecore.DependencyInjection.ServiceLocator.get_ServiceProvider() +85 Sitecore.Configuration.Factory.<.cctor>b__0() +9
System.Lazy1.CreateValue() +709 System.Lazy1.LazyInitValue() +191 Sitecore.Configuration.Factory.GetConfiguration() +44

The implications is that none of your injected constructors can contain references to:

  • Databases
  • Site information
  • Settings

HOW TO WORK AROUND INJECTED CODE WITH CONSTRUCTORS:

Imaging you like to inject a repository with the Sitecore Database as a constructor. You also would like to inject the database of the current context.

First you create an interface:

using Sitecore.Data;

public interface IDatabaseFactory
{
  Database Get();
}

Then you create a concrete implementation of the interface:

public class ContextDatabaseFactory :IDatabaseFactory
{
  public Database Get()
  {
    return Sitecore.Context.Database;
  }
}

In the ServicesConfigurator you can now register the abstract implementation:

public class ServicesConfigurator : IServicesConfigurator
{
  public void Configure(IServiceCollection serviceCollection)
  {
    // The database factory to inject:
    serviceCollection.AddTransient<IDatabaseFactory, ContextDatabaseFactory>();
    // The class that needs the database in the constructor:
    serviceCollection.AddTransient<MyRepository>();
  }
}

And in the MyRepository you reference the IDatabaseRepository in the constructor instead of the concrete Sitecore Database implementation:

public class MyRepository
{
  private readonly IDatabaseFactory _database;
  
  public MyRepository(IDatabaseFactory database)
  {
    _database = database;
  }
  
  public void DoTheActualCode()
  {
    _database.Get().GetItem("/sitecore/content/...");
  }
}

Many thanks to my cool colleagues who helped forge this solution:

MORE TO READ:

 

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

Sitecore Object of type ‘System.Runtime.Serialization.TypeLoadExceptionHolder’ cannot be converted to type ‘Sitecore.Analytics.Model.Framework.IFacet’.

When deleting or refactoring Sitecore Contact Facets, and when using a Shared Session Manager, this error can exhaust your solution to the point where the IIS recycles:

ERROR Error executing the session end callback. Id: dd7b0466-af93-4130-a388-9e9eca9c0839 Exception: System.ArgumentException Message: Object of type ‘System.Runtime.Serialization.TypeLoadExceptionHolder’ cannot be converted to type ‘Sitecore.Analytics.Model.Framework.IFacet’. Source: mscorlib

The problem arises, because the existing sessions Shared Session Manager contains the old facets, and when Sitecore tries to deserialize them, the objects have nowhere to go.

THE SOLUTION:

After you have deployed your solution, you need to delete all existing sessions in the shared session manager.

Please note that Sitecore stores the sessions in the TEMPDB database of your SQL Server. The table is called “dbo.SessionState“. Deleting is easy:

delete from SessionState

If you deploy to several front end servers in sequence, you will need to delete the SessionState table several times, as the non-changed server will continue to create new sessions containing Facets unknown to the updated servers.

MORE TO READ:

COMPLETE ERROR MESSAGE:

ERROR Error executing the session end callback. Id: dd7b0466-af93-4130-a388-9e9eca9c0839 Exception: System.ArgumentException Message: Object of type ‘System.Runtime.Serialization.TypeLoadExceptionHolder’ cannot be converted to type ‘Sitecore.Analytics.Model.Framework.IFacet’. Source: mscorlib at System.RuntimeType.TryChangeType(Object value, Binder binder, CultureInfo culture, Boolean needsSpecialCast) at System.Reflection.RtFieldInfo.UnsafeSetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, CultureInfo culture) at System.Runtime.Serialization.ObjectManager.DoValueTypeFixup(FieldInfo memberToFix, ObjectHolder holder, Object value) at System.Runtime.Serialization.ObjectManager.CompleteObject(ObjectHolder holder, Boolean bObjectFullyComplete) at System.Runtime.Serialization.ObjectManager.DoNewlyRegisteredObjectFixups(ObjectHolder holder) at System.Runtime.Serialization.ObjectManager.RegisterObject(Object obj, Int64 objectID, SerializationInfo info, Int64 idOfContainingObj, MemberInfo member, Int32[] arrayIndex) at System.Runtime.Serialization.Formatters.Binary.ObjectReader.RegisterObject(Object obj, ParseRecord pr, ParseRecord objectPr, Boolean bIsString) at System.Runtime.Serialization.Formatters.Binary.ObjectReader.ParseObjectEnd(ParseRecord pr) at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run() at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage) at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage) at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, IMethodCallMessage methodCallMessage) at System.Web.Util.AltSerialization.ReadValueFromStream(BinaryReader reader) at System.Web.SessionState.SessionStateItemCollection.ReadValueFromStreamWithAssert() at System.Web.SessionState.SessionStateItemCollection.DeserializeItem(String name, Boolean check) at System.Web.SessionState.SessionStateItemCollection.get_Item(String name) at Sitecore.Analytics.Tracking.SharedSessionState.SharedSessionStateManager.OnItemExpired(String id, SessionStateStoreData item) at Sitecore.SessionProvider.SessionStateStoreProvider.ExecuteSessionEnd(String id, SessionStateStoreData item)

Posted in Sitecore 8 | Tagged , , , | Leave a comment

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.

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 Sitecore 5, General .NET, Sitecore 6, c#, Sitecore 7, Sitecore 8, Sitecore 9 | Tagged , | 1 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 , | 2 Comments