Thursday, March 29, 2018

Sitecore Experience Commerce - Storefront configuration missing.

When loading up the frontend site of a Sitecore Experience Commerce storefront, the following error may appear:
Storefront configuration missing.
Sitecore Experience Commerce - Storefront configuration missing
The logs were showing:
ERROR Application error.Exception: System.InvalidOperationExceptionMessage: Storefront configuration missing.Source: Sitecore.Commerce.XA.Foundation.CommonSitecore.Commerce.XA.Foundation.Common.Models.CommerceStorefront.get_StorefrontConfiguration()Sitecore.Commerce.XA.Foundation.Common.Pipelines.SecuredPageProcessor.IsSslEnforcedForStorefront()Sitecore.Commerce.XA.Foundation.Common.Pipelines.SecuredPageProcessor.Process(PipelineArgs args)
In particular the IsSslEnforced part of the error stood out and looking at my Storefront configuration, this was somehow missing. Comparing with the branch which should be inserted (/sitecore/templates/Branches/Foundation/Commerce Experience Accelerator/Commerce Foundation/Storefront Control Panel) several other configuration items were missing as well.

Inserting a new storefront configuration of this branch time and setting the relevant settings then allowed the frontend web site to load as expected.

Sitecore Experience Commerce - Error on Frontend - Control Panel Storefront

When attempting to load the front end of an Experience Commerce Storefront the following error was appearing:
Server Error in '/' Application.
Control Panel Storefront
Sitecore Experience Commerce - Control Panel Storefront error
It can be fixed by simply going to the Home node of your storefront site (/sitecore/content/Sitecore/MySite/Home) and ensuring the Control Panel Configuration setting is correctly pointing to a commerce control panel storefront settings.

Sitecore Experience Commerce - Update data templates fails

After upgrading from version 9.0 to 9.1 of Sitecore Experience Commerce, the update data templates command was failing.

Sitecore Experience Commerce - Update data templates error
The Sitecore logs were showing:
ERROR GetList(id='Catalogs',type='Sitecore.Commerce.Plugin.Catalog.Catalog, Sitecore.Commerce.Plugin.Catalog',skip=0,take=1)?$expand=Items($select=Id)
System.Exception: An error occured while trying to contact the Commerce Service. Error code BadRequest at Sitecore.Commerce.Engine.Connect.DataProvider.CatalogRepository.LogResponseError(HttpResponseMessage response, Boolean raiseError)
   at Sitecore.Commerce.Engine.Connect.DataProvider.CatalogRepository.<InvokeHttpClientGetAsync>d__24.MoveNext()
Looking into the logs for the Authoring role of commerce engine, there were more detailed logs:
ERROR Management.block.getitembypath: Sitecore Item Service Get item failed, Item /sitecore/Commerce/Commerce Control Panel/Storefront Settings/Storefronts/MyShop not found.54 13:23:40 ERROR CtxMsg.Error.InvalidShop: Text=Shop 'MyShop' does not exist.54 13:23:40 ERROR PipelineAbort:Shop 'MyShop' does not exist.54 13:23:40 ERROR CtxMsg.Error.InvalidShop: Text=Shop 'MyShop' does not exist.49 13:23:40 ERROR CommerceController.OnActionExecuting.BadRequest: Code=Error|TermKey=InvalidShop|Text=Shop 'MyShop' does not exist.
Two changes was able to get this resolved for me:
  • If you have renamed the default instance of the Storefront settings (/sitecore/Commerce/Commerce Control Panel/Storefront Settings/Storefronts\Storefront) this change may have been overwritten during the upgrade.
  • Ensure that the PlugIn.Content.PolicySet-1.0.0.json file inside each commerce engine site has the correct Host URL that points to your Sitecore instance.
These changes should get the data templates updating as expected. Don't forget to bootstrap if you change the PolicySet inside the commerce engine sites.

Sitecore Experience Commerce - One or more local models conflict with the xDB service layer

During an upgrade from the initial release for Sitecore Experience Commerce 9.0 to update 1, the following errors were appearing in the logs:
ERROR Exception when executing agent aggregation/pathAnalyzerLiveAgent
Exception: Sitecore.XConnect.Client.XdbModelConflictException
Message: One or more local models conflict with the xDB service layer.
 'RegisterConnectEventModel, 0.1' does not match the remote version
Source: Sitecore.Xdb.Common.Web
This was appearing when attempting to update the commerce data templates. It was not actually an error related to this action and is resolved when following these steps in the upgrade guide:

  1. In File Explorer, go to: \inetpub\wwwroot\<siteName>\App_Config\Include\Y.Commerce.Engine and Rename/enable:
    1. Sitecore.Commerce.Engine.DataProvider.config
    2. Sitecore.Commerce.Engine.Connectors.Index.Common.config
    3. All Sitecore.Commerce.Engine.Connectors.Index.Solr.*.config
  2. Copy the file: \inetpub\wwwroot\<siteName>\XConnectModels\Sitecore.Commerce.Connect.XConnect.Models.json and Paste it to two locations to overwrite:
    1. C:\inetpub\wwwroot\<siteName>.xconnect\App_data\jobs\continuous\IndexWorker\App_data\Models
    2. C:\inetpub\wwwroot\<siteName>.xconnect\App_data\Models
  3. Perform an IISreset

Tuesday, March 13, 2018

Sitecore Experience Commerce - Error: catalog name X is already in use

During testing of a Sitecore Experience Commerce minion which manipulated catalog and sellable item entities, the following error occurred when attempting to run the ICreateCatalogPipeline pipeline.
ERROR PipelineAbort:Catalog name MyCatalog is already in use.
In between minion runs I had been running the following DevOps calls in postman to ensure a fresh start:

  1.  {{OpsApiHost}}/{{OpsApi}}/CleanEnvironment() - Cleans the environment of all entities
  2. {{OpsApiHost}}/{{OpsApi}}/Bootstrap() - Bootstraps commerce, if this is not run you may not be able to add categories
  3. {{OpsApiHost}}/{{OpsApi}}/InitializeEnvironment(environment='MyEnvironment') - as above
However the error was still appearing, even though the catalog no longer existed. To solve this error one of the following will usually work:
  1. Restart IIS to clear any caches
  2. Run the cache clear using the devops postman call {{ServiceHost}}/{{OpsApi}}/ClearCacheStore()
Doing these, stopped the error and my catalog would successfully be created again.

Sitecore Experience Commerce - Error: PopulateItemAvailabilityBlock.AllocationNull

When creating a minion that handled data sync into Sitecore Experience Commerce, the following error occurred when using the IAssociateSellableItemToCatalog pipeline to associate a sellable item with a given category (inside a catalog).
ERROR PopulateItemAvailabilityBlock.AllocationNull.CatalogName|ProductId|
From what I could see, the cause of this error was attempting to add a sellable item to a catalog when no pricing/inventory information is available for it. Doing one of the following to/for the product before attempting to associate with a catalog should fix the error:

  1. Tag the sellable item and a price card with the same tag - worked for me
  2. Add a list price to the sellable item
Let me know in the comments down below, if either of these don't solve your issue. Also ensure that the entity has been updated correctly after making changes (IPersistEntityPipeline pipeline can help with this).

Sitecore Experience Commerce - Error: PipelineAbort:Invalid currency

During minion development in Sitecore Experience Commerce, the following error occurred when running the IAddPriceCardPipeline pipeline.
2 00:10:13 ERROR Pipeline completed with error
System.Exception: Error processing block: Management.block.GetCurrencySetById ---> System.ArgumentNullException: Management.block.GetCurrencySetById: The currency set Id cannot be null or empty.
42 00:10:13 WARN CtxMsg.ValidationError.InvalidCurrency: Text=Invalid currency 'NZD'. Valid currencies are ''.|Shopper=[None]|Shop=|Correlation=d68174cf-9976-410d-b15b-0ee1de8ba39c
42 00:10:13 ERROR PipelineAbort:Invalid currency 'NZD'. Valid currencies are ''.
This error traces back to the fact that I was incorrectly passing in the PriceBook parameter. There is no pipeline to get you a price book by name, however the following example will get you a PriceBook entity by name (if it exists). This makes use of the IFindEntitiesInListPipeline pipeline.
var books = (await _findEntitiesPipeline.Run(new FindEntitiesInListArgument(typeof(PriceBook), string.Format(context.GetPolicy<KnownPricingViewsPolicy>().PriceBooks, "BookName"), 0, int.MaxValue), context)).List;
var book = (PriceBook)books.Items.FirstOrDefault();
Hopefully this snippet is able to save someone some time!

Friday, March 9, 2018

Sitecore Experience Commerce - Adding a new empty environment

For a detailed explanation of Commerce roles and how they tie in with environments, please see my dedicated post. This gives an overview of environments and I recommend reading before continuing with this post.

This example shows how to add a new environement with empty data, if you want to initialize it with data (like the Habitat example) then see my post: Adding a new environment initialized with data.

Define the environment

The first piece of the puzzle is a JSON configuration file where the environment/plugin is defined. These files are placed it src\Sitecore.Commerce.Engine\wwwroot\data\Environments, and in my case I duplicated the PlugIn.Habitat.CommerceShops-1.0.0.json example. It's too big to post here, but the areas I changed were:
  • ArtifactStoreId - to a new GUID - this would be the same for a given set of environments (authoring, shops and minions)
  • Id - to a new ID (the efe49ff743d34b649fffcof293615cd9one)
  • Id - to a new name (the Entity-CommerceEnvironment-HabitatShops one)
  • Name - a new environment name
  • InitialArtifactSets - as follows
InitialArtifactSets for environment

Add the environment to global

To have the environment appear in the business tools, open up the Global.json file that comes inside the bootstrap folder of the Sitecore.Commerce.Engine project. There will be a GlobalEnvironment section which contains the values of the environments to appear in business tools. Here you can add the new environment name. 

Sitecore Experience Commerce - Environment for business tools

Initialize the environment

Now I just publish the Sitecore.Commerce.Engine project to the four commerce sites (ensuring thumbprints for certificates are setup correctly). Of course don't forget that each of these needs their environment set in the config file.

Inside postman, create a new environment to match the one you just created:

New postman environment
The run the following service calls from Postman:
  1. GetToken - under authentication
  2. Bootstrap Sitecore Commerce - under SitecoreCommerce_DevOps
  3. Initialize Environment - under SitecoreCommerce_DevOps
If any of these calls failed (due to 500 error), you will need to take a look in the Commerce Ops log files. If all went smoothly then your new environment is available in the Commerce Business Tools interface!

new environment in Experience Commerce
For me, the only error I got was around some dodgy JSON in the environment/plugin configuration. I had changed the currency elements to NZD and had missed a single USD instance. 

Sitecore Experience Commerce - Commerce roles and how they tie in with environments

As part of the installation of Sitecore Experience Commerce, 4 commerce websites are added to the IIS manager, each of which performs a unique engine role. Note that in some cases users will scale this down to a single instance locally for development and the full 4 in production. These 4 roles are:
  1. Authoring - handles traffic from the commerce business tools.
  2. Shops - serves traffic from a storefront.
  3. Minions - runs independently and runs your minion(s).
  4. DevOps - internal and will have higher permissions to handle environment bootstrapping etc.
Sitecore Experience Commerce roles
These 4 engine roles actually have the same code deployed - via the Sitecore.Commerce.Engine project which contains references to any plugins you create as well as defined environments. 

Where they differ is in which commerce environment is defined against a given role. To quote the Sitecore documentation:
Each deployed role can have different policies and behaviors, which can be specified using a specific Commerce Environment for that role. When a call is made to the Commerce Engine, the call’s header specifies an environment. This environment is used by the engine to control policies and behavior for that call. Specifying a particular environment allows explicit independent control (per role) of functions, such as caching, data storage, integration connections, and so on. 
Using environment policies, engine roles can either share a common persistent store, or use separate dedicated storage. All roles share the same storage in the out-of-box implementation. This allows artifacts to be visible to each role without a publishing process. For example, this makes orders immediately available to the Authoring role, and makes approved promotions and pricing changes immediately available to the Shops role. 
Each role can have independent caching policies. For example, reduced caching can be configured on the Authoring role so that changes are seen right away, and heavier caching can be configured on the Shops role to increase performance.
So what exactly is an environment, again lets take a look at the documentation:
Commerce environments provide the ability to have separately configurable pools of data and service functionality that run together in a single-service instance. Environments can share the same persistent store as other environments or be separated into their own exclusive persistent store.
Basically an environment can be used to group configurations for a given function and can share as much as needed with other related/unrelated environments.

To give a better illustration, lets take a look at what comes out of the box with Experience Commerce and Habitat. Inside the default Sitecore.Commerce.Engine project (comes as part of the SDK and is what is deployed to these roles) is an Environments folder which contains a number of JSON configuration files. The files that are relevant for this discussions are:
  1. PlugIn.Habitat.CommerceAuthoring-1.0.0.json - Name: HabitatAuthoring
  2. PlugIn.Habitat.CommerceMinions-1.0.0.json - Name: HabitatMinions
  3. PlugIn.Habitat.CommerceShops-1.0.0.json  - Name: HabitatShops
Take note of the name of each of these environment (as defined in the JSON itself). Looking into the contents of these files you may notice that shops and authoring are quite similar and the main differences are a couple of extra policies and plugins on the authoring environment. They actually share the same store/context so this makes sense as we don't want customer migration processing to happen on the instance accessed by the storefront. The minions environment definition is quite different and of course has a heap of minions defined. 

Now if we go into each of the commerce role websites (mentioned at the start of this post) they each will contain a config.json file in the wwwroot folder. The EnvironmentName setting in these configuration files is the only difference at deployment time (remember the same project gets deployed to all 4). 
  1. Authoring - HabitatAuthoring
  2. Shops - HabitatShops
  3. Minions - HabitatMinions
  4. DevOps - AdventureWorksOpsApi
As you may have noticed, these 4 roles each refer back to one of the habitat environments defined above. This is how each role knows what it's purpose is and it ties back to the environment. In this out of the box implemtation they all work together: All roles share the same storage in the out-of-box implementation.

Note: the AdventureWorksOpsApi environment does not appear to have been defined or shipped with the version of the SDK that I used. In this case it's fine to also set the role of this to the HabitatAuthoring or to remove this and point to the Authoring role.

There are two final pieces to this puzzle:

How does an environment appear in Experience Commerce Business Toosl?
Inside the Sitecore.Commerce.Engine project is a Global.Json file, this contains a GlobalEnvironment section which is where the values of environments you wish to appear in the business tools will be listed. Generally this would just be the authoring and shops environments (as is out of the box).

How do we define which environment is used inside Sitecore itself?
The full answer is available in my post: Sitecore Experience Commerce - Catalogs in the Sitecore Content Editor. The short answer is that the Sitecore.Commerce.Engine.Connect.config configuration file has a defaultEnvironment setting where we name the environment.

Hopefully this blog post has provided some clarity about what the commerce roles are in Experience Commerce and how they relate back to environments. 

Sitecore Experience Commerce - Creating a minion

Minions in Sitecore Experience Commerce can be thought of as a scheduled task. Many of the example minions that come with the Habitat commerce solution will access and watch a list of data from commerce. For example the ReleasedOrdersMinion watches the ReleasedOrders list. Other more general minions may not watch a list at all, such as the NodeHeartBeatMinion for example.

In my implementation I needed a process which would run in the background to watch an external system for product changes and then import them into Sitecore Experience Commerce. I decided to create a minion for this task (as it needed to run on a schedule and I wanted to have commerce context). This minion would not be watching a list, as one was not relevant to this case.

The full source code for the Sitecore Experience Commerce minion is available on GitHub. Also included in this example is a block and pipeline example.
The key part of this project is the minion defintion itself, this is what is configured in the minions environment and what is run on the schedule:
namespace Plugin.MyProject.Import
    public class ImportMinion : Minion
        protected IImportMinionMinionPipeline MinionPipeline { get; set; }

        public override void Initialize(IServiceProvider serviceProvider, ILogger logger, MinionPolicy policy, CommerceEnvironment environment, CommerceContext globalContext)
            base.Initialize(serviceProvider, logger, policy, environment, globalContext);
            MinionPipeline = serviceProvider.GetService<IImportMinionMinionPipeline>();

        public override async Task<MinionRunResultsModel> Run()
            this.Logger.LogInformation("ImportMinion running");

            var commerceContext = new CommerceContext(this.Logger, this.MinionContext.TelemetryClient, null);
            commerceContext.Environment = this.Environment;

            CommercePipelineExecutionContextOptions executionContextOptions = new CommercePipelineExecutionContextOptions(commerceContext, null, null, null, null, null);

            MinionRunResultsModel res = await this.MinionPipeline.Run(new MinionRunResultsModel(), executionContextOptions);

            return new MinionRunResultsModel();
To actually get this minion to run it needs to be added to the environment definition for your minions environment. In the Sitecore.Commerce.Engine project is the JSON file PlugIn.Habitat.CommerceMinions-1.0.0.json which is the definition for the habitat minions environment. This is the active environment on the commerce minions web site (one of 4 in IIS installed as part of commerce).

Inside this file, you can see some example minions have been added:

Sitecore Experience Commerce Minions environment definition

Here we can inject in the definition of our custom minion:
    "$type": "Sitecore.Commerce.Core.MinionPolicy, Sitecore.Commerce.Core",
    "WakeupInterval": "00:05:00",
    "ListToWatch": "",
    "FullyQualifiedName": "Plugin.MyProject.Import.ImportMinion, Plugin.MyProject.Import",
    "ItemsPerBatch": 10,
    "SleepBetweenBatches": 500
Notice that there is no ListToWatch and that the minion is set to run every 5 minutes. The type of Sitecore.Commerce.Core.MinionPolicy is important as this is what defines this as a minion definition and ensures that it will be started during environment initialization.

Now you simply deploy the Sitecore.Commerce.Engine project to your commerce minions IIS website (ensuring that the correct EnvironmentName is set the the config.json file. If you check the logs for that IIS website, it should show the minion running every 5 minutes:
7 11:33:02 INFO ImportMinion running
If all else fails, try a Bootstrap Sitecore Commerce call in Postman as this tends to fix most commerce errors.

Sitecore Experience Commerce - Plugin to extend a price card

As part of a Sitecore Experience Commerce implementation, I had requirements that involved putting some additional data (text) on price cards. The code for this plugin involves some minor changes to the SellableItem plugin example provided by Sitecore.

The full plugin code to extend a PriceCard in Experience Commerce is available on GitHub. The key file differecnes to target PriceCard instead of SellableItem are as follows:

Configure Sitecore
public void ConfigureServices(IServiceCollection services)
    var assembly = Assembly.GetExecutingAssembly();

    services.Sitecore().Pipelines(config =>
            .ConfigurePipeline<IGetEntityViewPipeline>(c =>
            .ConfigurePipeline<IPopulateEntityViewActionsPipeline>(c =>
            .ConfigurePipeline<IDoActionPipeline>(c =>
The view block is added after GetPriceCardDetailsViewBlock in this case instead of GetSellableItemDetailsViewBlock with a SellableItem.

Get View Block
public class GetPriceDetailViewBlock : PipelineBlock<EntityView, EntityView, CommercePipelineExecutionContext>
private readonly ViewCommander _viewCommander;

public GetPriceDetailViewBlock(ViewCommander viewCommander)
    this._viewCommander = viewCommander;

public override Task<EntityView> Run(EntityView arg, CommercePipelineExecutionContext context)
    Condition.Requires(arg).IsNotNull($"{Name}: The argument cannot be null.");

    var request = this._viewCommander.CurrentEntityViewArgument(context.CommerceContext);

    var pricingViewsPolicy = context.GetPolicy<KnownPricingViewsPolicy>();

    var viewsPolicy = context.GetPolicy<KnownPriceDetailViewsPolicy>();
    var actionsPolicy = context.GetPolicy<KnownPriceDetailActionsPolicy>();

    // Make sure that we target the correct views
    if (string.IsNullOrEmpty(request.ViewName) ||
        !request.ViewName.Equals(pricingViewsPolicy.Master, StringComparison.OrdinalIgnoreCase) &&
        !request.ViewName.Equals(pricingViewsPolicy.Details, StringComparison.OrdinalIgnoreCase) &&
        !request.ViewName.Equals(viewsPolicy.PriceDetail, StringComparison.OrdinalIgnoreCase))
        return Task.FromResult(arg);

    // Only proceed if the current entity is a price card
    if (!(request.Entity is Sitecore.Commerce.Plugin.Pricing.PriceCard))
        return Task.FromResult(arg);

    var priceCard = (Sitecore.Commerce.Plugin.Pricing.PriceCard) request.Entity;

    var targetView = arg;

    // Check if the edit action was requested
    var isEditView = !string.IsNullOrEmpty(arg.Action) && arg.Action.Equals(actionsPolicy.EditPriceDetail, StringComparison.OrdinalIgnoreCase);
    if (!isEditView)
        // Create a new view and add it to the current entity view.
        var view = new EntityView
            Name = context.GetPolicy<KnownPriceDetailViewsPolicy>().PriceDetail,
            DisplayName = "Pricing Details",
            EntityId = arg.EntityId


        targetView = view;

    if (priceCard != null && (priceCard.HasComponent<PriceDetailComponent>() || isEditView))
        var component = priceCard.GetComponent<PriceDetailComponent>();
        AddPropertiesToView(targetView, component, !isEditView);

    return Task.FromResult(arg);
In this case the views policy changes from KnownCatalogViewsPolicy to KnownPricingViewsPolicy and of course we target the PriceCard entity instead of SellableItem.

It's not a lot to change when trying to extend various entities in Experience Commerce 9. It's worth noting that the types of which we do extend all appear to be of type CommerceEntity in the Sitecore DLLs.

Tuesday, March 6, 2018

Sitecore Experience Commerce - Entity not found for Relationship type

In my local instance of Sitecore Experience Commerce, I had a test catalog where I was trying to add both products and categories. However both additions would fail with the following errors:
Entity not found for Source:Entity-Catalog-MyCatalog, Target:Entity-SellableItem-MyProduct, Relationship type:CatalogToSellableItem
Entity not found for Source:Entity-Catalog-MyCatalog, Target:Entity-Category-MyCatalog-MyProduct, Relationship type:CatalogToCategory
I had been developing plugins and had changed/added and removed a number of different plugins extending various commerce entities. To resolve both error above I needed to make the following service calls via Postman:

  1.  Clean Environement - {{OpsApiHost}}/{{OpsApi}}/CleanEnvironment()
  2. Bootstrap Sitecore Commerce - {{OpsApiHost}}/{{OpsApi}}/Bootstrap()
  3. Initialize Environment - {{OpsApiHost}}/{{OpsApi}}/InitializeEnvironment(environment='MyEnvironment')
These steps will often resolve most issues you encounter with Experience Commerce environments and their contained entities.

Sitecore Experience Commerce - Plugin to extend commerce entity not showing in business tools

After creating a plugin to extend the PriceCard commerce entity in Sitecore Experience Commerce, I noticed that my new section was not showing when viewing a PriceCard in commerce business tools.

The first step was to go into Postman and run the Ops Metadata service call - {{OpsApiHost}}/{{OpsApi}}/$metadata. Here I was able to compare a working plugin:
<Schema Namespace="Plugin.MyProject.SaleType" xmlns="">
 <ComplexType Name="KnownSaleTypeActionsPolicy" BaseType="Sitecore.Commerce.Core.Policy">
  <Property Name="EditSaleType" Type="Edm.String" />
 <ComplexType Name="KnownSaleTypeViewsPolicy" BaseType="Sitecore.Commerce.Core.Policy">
  <Property Name="SaleType" Type="Edm.String" />
 <EntityType Name="SaleTypeComponents" BaseType="Sitecore.Commerce.Core.Component">
  <Property Name="SaleType" Type="Edm.String" />
  <Property Name="MinOrderQuantity" Type="Edm.String" />
  <Property Name="OrderStepSize" Type="Edm.String" />
  <Property Name="OrderStepUom" Type="Edm.String" />
To one that was not working:
<Schema Namespace="Plugin.MyProject.PriceDetail.Policies" xmlns="">
 <ComplexType Name="PriceDetailActionsPolicy" BaseType="Sitecore.Commerce.Core.Policy">
  <Property Name="EditPriceDetail" Type="Edm.String" />
 <ComplexType Name="PriceDetailViewsPolicy" BaseType="Sitecore.Commerce.Core.Policy">
  <Property Name="PriceDetail" Type="Edm.String" />
Notice that the EntityType section was missing. To fix this problem, I check my Components, Pipelines and policies and noticed in the broken example, namespaces were full written out. For example I had used:
namespace Plugin.MyProject.PriceDetail.Pipelines.Blocks.DoActions
Instead of simply:
namespace Plugin.MyProject.PriceDetail
Fixing this allowed the EntityType section to appear as expected, but not the section to appear in the business tools. What was stopping this from happening was a line in the ConfigureSitecore code:
Was incorrect (notice self reference) and instead should have been:
This is a good case for always following a template that works and when it doesn't compare against one which does.

Sunday, March 4, 2018

Sitecore Error - Unknown server tag PlatformFontStylesLink

A clean instance of Sitecore 9 (update 1) was displaying the following error after a basic MVC based project was deployed to it.
Unknown server tag 'sc:PlatformFontStylesLink'.
Sitecore: Unknown server tag error
This was a nice and simple one to fix, the project which had been deployed was using newer versions of system, MVC, Newtonsoft and other DLLs that come standard with Sitecore. The easiest way to fix this is to compare your Sitecore instance's bin folder with a backup or alternatively a clean version of your Sitecore version.

Don't forget to update the Visual Studio dll references to the correct versions.