Sitecore and WebApi

So you have some legacy WebApi code that needs to run in your Sitecore solution? Or are just just a WebApi expert and need to use your favorite tool in the toolbox? Fear not, WebApi will run fine in your Sitecore solution.

You don’t need to use the native Sitecore 8.2 support for WebApi, you can use your own routes as well, and implement your nasty controller selectors, formatters and message handlers.

The API routes can be registered as a processor in the /sitecore/pipelines/initialize pipeline:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <sitecore>
    <pipelines>
      <initialize>
        <processor type="MyProject.RegisterApiRoutes, MyDll" />
      </initialize>
    </pipelines>
  </sitecore>
</configuration>

Please be aware, that Sitecore have taken the /api/sitecore and the /api/rest/ routes for it’s own code already, so use another route and you will avoid clashes with the Sitecore API.

This is my sample route registering, using the /myapi/ route instead of /api/:

using System.Web.Http;
using Newtonsoft.Json;
using Sitecore.Pipelines;

namespace MyProject
{
  public class RegisterApiRoutes
  {
    public void Process(PipelineArgs args)
    {
      HttpConfiguration config = GlobalConfiguration.Configuration;

      SetRoutes(config);
      SetSerializerSettings(config);
    }

    private void SetRoutes(HttpConfiguration config)
    {
      config.routes.MapHttpRoute("Features", "myapi/features", new { action = "Get", controller = "Feature" });
      config.routes.MapHttpRoute("Default route", "myapi/{controller}", new { action = "Get" });
    }

    private void SetSerializerSettings(HttpConfiguration config)
    {
      JsonSerializerSettings settings = new JsonSerializerSettings { ContractResolver = new DefaultContractResolver() };
      config.Formatters.JsonFormatter.SerializerSettings = settings;
      config.Formatters.Remove(config.Formatters.XmlFormatter);
      config.EnsureInitialized();
    }
  }
}

And I can implement my “Features” controller:

using System.Collections.Generic;
using System.Web.Http;

namespace Myproject
{
  public class FeatureController : ApiController
  {
    public dynamic Get()
    {
      return "hello world";
    }
  }
}

MORE TO READ:

 

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

Creating dynamic arrays and lists using Dynamic and ExpandoObject in C#

In this previous post, C# Using Newtonsoft and dynamic ExpandoObject to convert one Json to another, I described how you can use the dynamic keyword and the ExpandoObject class to quickly transform JSON without the need for any concrete implementations of either the source or destination JSON.

This is an example of a dynamic list where you do not know the number of objects in the output array:

dynamic output = new List<dynamic>();

dynamic row = new ExpandoObject();
row.NAME = "My name";
row.Age = "42";
output.Add(row);

USAGE IN REAL LIFE:

Imagine you need to convert the following JSON by taking only those rows where the age is above 18:

{
	"attributes": [{
			"name": "Arthur Dent",
			"age": 42,
		},
		{	"name": "Ford Prefect",
			"age": 1088,
		},
		{	"name": "Zaphod Beeblebrox",
			"age": 17,
		}]
}

The code to transform the JSON would look something like this:

// Convert input JSON to a dynamic object
dynamic input = JsonConvert.DeserializeObject(myQueueItem);

// Create a list of dynamic object as output
dynamic output = new List<dynamic>();

foreach (var inputAttribute in input.attributes)
{
  if (inputAttribute.Age >= 18)
  {
    // Create a new dynamic ExpandoObject
    dynamic row = new ExpandoObject();
	row.name = inputAttribute.name;
	row.age = inputAttribute.age;
	// Add the object to the dynamic output list
	output.Add(row);
  }
}

// Finally serialize the output array
string outputJson = JsonConvert.SerializeObject(output);

The output is this:

[
  {  "name": "Arthur Dent",
	 "age": 42,
  }, 
  {	 "name": "Ford Prefect",
	 "age": 1088,
  }
]

MORE TO READ:

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

Sitecore open internal links in new window

Frequent users of Sitecore have already noticed that the “Insert Sitecore Link” dialog does not have a target selector:

Insert Internal Link

Insert Internal Link

Yes it’s true, if you wish to open an internal link in a new windows, it’s a 2 step process. First you add the internal link. Then you select the link and click the “Hyperlink Manager“. From here you can choose the target of the link:

Hyperlink Manager

Hyperlink Manager

MORE TO READ: 

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

Sitecore install local SSL certificate for shared xConnect SOLR server

In Sitecore 9, the SOLR connection is secure by default. The effect is that if your development environment includes a shared SOLR server, your local IIS requires a SSL certificate issued for that server, even when your local site does not run HTTPS.

This makes installing a local dev environment a little but more complicated, which is why my colleague Kristian Gansted made this guide for me:

STEP 1: GET THE SSL CERTIFICATE FOR THE SOLR SSL SERVER

DevOps should provide you with the appropriate .pfx file.
Double click the file and follow the guide:

STEP 2: USE THE CERTIFICATE IMPORT WIZARD TO INSTALL THE CERTIFICATE

Select the Local Machine and click next:

Certificate Import Wizard

Certificate Import Wizard – Select the Local Machine

On the “File to Import“, the file name is already chosen so just click next, and go the “Private key protection“. Do not select a password, just click “Next”:

No Password

Do not select a password. Just click Next

In the Certificate Store, click “Browse…” and select the “Personal” store:

Certificate Store

Select “Personal” store

Click “Next” and click “Finish”. The certificate is now installed.

The import was successful

The import was successful

STEP 3: ALLOW AppPool ACCESS TO THE CERTIFICATE

Find the “Manage computer certificates” control panel:

Manage Computer Certificates

Manage Computer Certificates

Find the certificate under the “Personal” certificates, right click and find the “Manage Private Keys…” under “All Tasks“:

Certificates

Find the certificate, right click and find Manage Private Keys under “All tasks”

Press “Add” (1) to add a new user.
Press “Locations” (2) and select the machine to search in the correct location.

Select the correct location

Select the correct location

Then type “IIS APPPOOL/[name of IIS site]”:

Select User

Select IIS APPPOOL\[name of iis site]

Give the user full control.

Your IIS site is now ready to access the SOLR server.

MORE TO READ:

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

Azure Functions – How to retry messages in the poison queue

When working with a scenario where your Microsoft Azure Function reads data from a queue, your azure functions is automatically triggered when an entry is added to the queue you wish to read from.

The SDK will call your function 5 times, and the message is only removed from the trigger queue if:

  • The function was successful
  • The function fails 5 times

On the 5th fail, the message is moved to the poison queue, a separate queue named [queuename]-poison.

Queues and poison queues

Queues and poison queues

HOW TO RETRY THE POISON QUEUE:

Microsoft have built in methods for manual and automatically poison message handling, but there is no description of how to just retry the messages. In many situations, the problem causing your issue is fixed elsewhere, and all you need to do is retry the messages.

The solution is easier that I thought: The poison queue is just another queue, so all you need to do is to point the Azure Function to the poison queue, and the messages will be executed:

Read from Poison Queue

Read from Poison Queue

MORE TO READ:

 

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

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:

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