Integrating EPiServer 7 MVC and Commerce 1 R3 - Part 1: Developing the Basics

March 2022 Update: This post was originally written for EPiServer version 7. In 2021, Episerver (now known as Optimizely) released version 12, and Commerce was updated to version 14. This post is very outdated now, and I suggest looking into using the new versions of the platform. No content in this post has been changed for the current version.

Over the last couple of months, we've been working on integrating EPiServer Commerce 1 R3 with an EPiServer 7 MVC site. Unfortunately, this is not the most straightforward process, as Commerce 1 R3 is not fully supported with EPiServer 7 sites using MVC. After a lot of trial and error, we've developed a workable solution to getting both to play nicely with each other.

This is part of a multi-post series. In this post, I'll briefly discuss setting up the solution, then cover the basic components needed for our EPiServer 7 MVC and Commerce 1 R3 site.

Before I begin, I must say that this is solely based off my implementation of EPiServer 7 MVC and Commerce 1 R3. It is possible that you could encounter different issues in different environments. While I generally believe that this should work for a wide range of environments, please note that your results may vary.

While developing out our project, we kept one common idea in mind: do the least amount of work to make Commerce happy, then let the Framework (or pure MVC) control the communication with Commerce using the Commerce API. This may cause some Commerce "features" to not correctly function in the CMS, but we'll cross that bridge when we get there, if it's required for the project.

Setting Up the Solution

How you set up the solution is completely dependent on your situation. I'm not going to fully explain how I set up my solution, because I could write a short novel just on that topic alone. The general goal at this stage is to make the solution fairly clean for development purposes, then we can add files and fix deployment issues when that time comes. When setting up your solution, you should focus on getting a working EPiServer 7 CMS site running. Test often that the site functions properly.

There are a couple things to keep in mind:

  • You'll need to use Deployment Center to create the EPiServer 7 site and install the Commerce 1 R3 module. Unfortunately, EPiServer 7 MVC sites created using the the EPiServer Visual Studio Extension do not currently work with Deployment Center.
  • After you create your site and add it to a project, you should install the NuGet packages for EPiServer (specifically CMS, Framework, and Common). This will make many of the .DLLs in the original site's bin folder obsolete. In fact, I try to add the corresponding NuGet package for many of the libraries in the bin folder.
  • I highly recommend upgrading to EPiServer 7.1, but if you do, you'll need a Commerce UI hotfix. You can contact EPiServer Support for the hotfix.

Developing the Basics

At this point, we have a working CMS site, minus any actual page types, but none of the integration with Commerce is complete. For example, if you try to use a CatalogNodeBrowser property on a page, you won't be able to select any catalog nodes, nor will the catalog nodes even show in the window. For our solution (and I'd guess for many solutions), this is one of the most important properties. Therefore, we will focus on getting this working.

There are some basic components required for this to work, and the end goal of developing this is to be able to set the 'Product Listing Page Type' and the 'Product Detail Page Type' values in the Commerce Settings screen, which makes the CatalogNodeBrowser property function as expected.

Note: If you try to set the values in the Commerce Settings screen before all steps are complete, the site will throw an internal server error, and the only way to get the error to go away is to remove the Commerce Settings values directly from the database (they live in the DDS/BigTable).

Creating our pages for the CatalogPageProvider

We first start by creating two models that are specific to the CatalogPageProvider: the ProductListProviderType and the ProductDetailProviderType. These will work with two WebForms templates (named ProductListProvider.aspx and ProductDetailProvider.aspx) that will integrate with Commerce. We won't be doing anything with these templates, so they'll be blank.

Models/Providers/ProductListProviderType.cs

The ProductListProviderType model contains two properties required by Commerce: CatalogNode and NodeName. Aside from that, we inherit from ISearchable so we can make sure this page type is not indexed. ISearchable is part of EPiServer.Search, so if you are using another search solution, you may be able to exclude this.

[ContentType(DisplayName = "[Commerce] Product List Provider", GUID = "00000000-0000-0000-0000-000000000000")]
public class ProductListProviderType : PageData, ISearchable
{
    public virtual string CatalogNode { get; set; }
    public virtual string NodeName { get; set; }

    public bool AllowReIndexChildren
    {
        get { return false; }
    }

    public bool IsSearchable
    {
        get { return false; }
    }
}

Models/Providers/ProductDetailProviderType.cs

The ProductDetailProviderType model contains just one property required by Commerce: ec. As before, we inherit from ISearchable so we can make sure this page type is not indexed.

[ContentType(DisplayName = "[Commerce] Product Detail Provider", GUID = "00000000-0000-0000-0000-000000000000")]
public class ProductDetailProviderType : PageData, ISearchable
{
    public virtual string ec { get; set; }

    public bool AllowReIndexChildren
    {
        get { return false; }
    }

    public bool IsSearchable
    {
        get { return false; }
    }
}

Views/Shared/Provider/ProductListProvider.aspx & Views/Shared/Provider/ProductDetailProvider.aspx

These are simple .ASPX pages that inherit from System.Web.UI.Page. They are both blank pages with no methods in the code-behind. We will be directly pointing to these files from our two Start Page properties.

Integrating with our MVC page type models

Now that we have the WebForms provider pages created, we can focus on the MVC portion of our integration. In our Start Page, we need to create a couple properties that are used by the various Commerce providers and handlers, and set some values to point to our provider pages that we just created.

Models/Pages/StartPageType.cs

Our Start Page has two properties required by Commerce: DefaultEntryViewPage and DefaultNodeViewPage. We use the ScaffoldColumn attribute to hide the property from being edited in Edit Mode. We also override the SetDefaultValues method to point our two property values to the WebForms provider pages we created earlier. This will ensure that the correct values are in the properties when the instance of the Start Page is created.

The names of these properties are not something we made up; we had to reflect the code in order to see which values the CatalogPageProvider and the related handlers were looking for in the Start Page. There are other properties that you may need for your implementation. You can see all of these values by reflecting and looking through the EPiServer.Business.Commerce.Providers.StaticUrlProvider class.

[ContentType(DisplayName = "Start Page", GUID = "00000000-0000-0000-0000-000000000000")]
public class StartPage : PageData
{
    #region CMS/Commerce Integration

    [ScaffoldColumn(false)]
    public virtual string DefaultEntryViewPage { get; set; }

    [ScaffoldColumn(false)]
    public virtual string DefaultNodeViewPage { get; set; }

    public override void SetDefaultValues(ContentType contentType)
    {
        base.SetDefaultValues(contentType);

        DefaultEntryViewPage = "/Views/Shared/Provider/ProductDetailProvider.aspx";
        DefaultNodeViewPage = "/Views/Shared/Provider/ProductListProvider.aspx";
    }

    #endregion
}

Models/Pages/ProductListPage.cs

Our Product List Page is fairly simple. The only property we have created is for the editor to specify a catalog node using the CatalogNodeBrowser.

[ContentType(DisplayName = "Product List Page", GUID = "00000000-0000-0000-0000-000000000000")]
public class ProductListPage : PageData
{
    [UIHint("CatalogNode")]
    [BackingType(typeof(CatalogNodeBrowserProperty))]
    public virtual string CatalogNode { get; set; }
}

Models/Pages/ProductDetailPage.cs

Our Product Detail Page is even more simple, since we just need it to create the page type.

[ContentType(DisplayName = "Product Detail Page", GUID = "00000000-0000-0000-0000-000000000000")]
public class ProductDetailPage : PageData
{

}

Controllers/ProductListPageController.cs

Since our Product List Page will have multiple page instances, we don't need to do anything special, other than using our favorite Commerce API method to pull in Entries from the catalog node.

public class ProductListPageController : PageController<ProductListPage>
{
    public ActionResult Index(ProductListPage currentPage)
    {
        // Get the Entries from the CatalogNode property and pass it to the view model
        return View();
    }
}

Controllers/ProductDetailPageController.cs

Our Product Detail Page is a little more involved. The intention is to have only one instance of the page, then let the routing control which Entry we should be viewing. In order to accomplish this, we will need to create a special route to this page (which is covered in Part 2). In our particular solution, we are also using an action filter to ensure that we always have a legitimate Entry when the controller is hit (which is covered in Part 3).

For now, let's just create the controller.

// We will be updating this controller in future posts
public class ProductDetailPageController : PageController<ProductDetailPage>
{
    public ActionResult Index(ProductDetailPage currentPage)
    {
        return View();
    }
}

Views/ProductListPage/Index.cshtml & Views/ProductDetailPage/Index.cshtml

For now, we don't need anything special in these files. Just fill the files with some test data, so we can see that we actually get to the views.

Putting it all together

Now that we have all the necessary models, page types, controllers, and views created, we can start building things out in Edit Mode.

  1. Ensure that the master language branch for the Start Page will be 'en-US'. EPiServer Commerce 1 R3 requires a four-letter language code, so the EPiServer CMS default 'en' language branch that will not work for us. Getting this set up may involve enabling the 'en-US' language branch, making the language available, and switching to that language branch in edit mode. You can refer to my localization and language branch post on for reference on how to get this set up.
  2. Create an instance of the Start Page in the 'en-US' branch. Creating the Start Page will set those hidden properties with the correct values. You can ensure the values are set by going to the Start Page type in Admin Mode, checking the 'Display in Edit Mode' value on the specific property, and seeing the property has the correct value.
  3. Create an instance of the provider pages. It doesn't really matter where we create these pages, nor which language branch we create these on. For our solution, we created them under the Root Page and in the 'en' branch, so the editor does not see that these pages exist. Afterwards, you can go into Admin Mode and make it so these page types cannot be created again by un-checking the 'Available in Edit mode' value on the page type's settings.
  4. Set the Commerce Settings values. Once all is done, we can go into Commerce Settings and set the 'Product List Page' to be '[Commerce] Product List Provider', and set 'Product Detail Page' to be '[Commerce] Product Detail Provider'.

Next Steps

If all was set up correctly, we should have a working CMS site with integration into the Commerce system. We can test this by creating a Product List Page and seeing if the window is populated with the nodes in our catalog (assuming you have catalog nodes created).

Our next steps are to create the routing to the Product Detail page, and to make an action filter to validate that we have a legitimate entry. These will be covered in Part 2 and Part 3 of this series.