Integrating EPiServer 7 MVC and Commerce 1 R3 - Part 3: Creating an Entry Action Filter

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.

When developing out our EPiServer 7 MVC and Commerce site, we needed to have some way to ensure that the requested Entry is valid and exists in our catalog. In our solution, since we have multiple actions in our ProductDetailPageController, we created an action filter to validate {entry} parameter in our route, instead of repeating the code for every action.

This is part of a multi-post series regarding integrating EPiServer Commerce 1 R3 with an EPiServer 7 MVC site. In this post, I'll discuss the action filter we created to validate the requested Entry.

Our [ValidateEntry] action filter

Since we use this action filter to ensure the requested Entry is valid and present for our controller, we do a lot of checks in the OnActionExecuting() method.

First, we ensure we have a currentPage. In most cases we will, but if we don't we just fallback to our pre-defined PageReference to the Product Details Page.

Next, if we are in Edit Mode and no Entry is found, we don't want to throw a 404. Instead, we'll just display a "No entry set" placeholder page. You'll typically only see this page if you directly view the instance of the Product Detail Page.

Lastly, we validate and create our Entry object, and put the Entry object into our action parameter, so we don't need to re-fetch the Entry. As you can see, if at anytime something is missing or invalid, we throw a 404.

Business/Attributes/ValidateEntryAttribute.cs

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ValidateEntryAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var currentPage = filterContext.ActionParameters["currentPage"] as ProductDetailPage;

        if (currentPage == null)
        {
            // Update this line so it points to your Product Detail PageReference
            currentPage = (new ContentReference(123)).GetPage<ProductDetailPage>();
            filterContext.ActionParameters["currentPage"] = currentPage;
        }

        if (filterContext.ActionParameters["entryCode"] == null && filterContext.HttpContext.GetContextMode(filterContext.RouteData) == ContextMode.Edit)
        {
            filterContext.Controller.ViewData.Model = PageViewModel.Create(currentPage);

            filterContext.Result = new ViewResult
            {
                ViewName = string.Format("~/Views/Pages/{0}/NoEntry.cshtml", currentPage.GetOriginalType().Name),
                ViewData = filterContext.Controller.ViewData,
                TempData = filterContext.Controller.TempData
            };

            return;
        }

        if (filterContext.ActionParameters["entryCode"] == null)
        {
            throw new HttpException(404, "No entry found.");
        }

        var entryCode = filterContext.ActionParameters["entryCode"].ToString();

        if (string.IsNullOrEmpty(entryCode))
        {
            throw new HttpException(404, "No entry found.");
        }

        // Update this line with your favorite method to retrieve the Entry
        Entry entry = null;

        if (entry == null)
        {
            throw new HttpException(404, "No entry found.");
        }

        filterContext.ActionParameters["entry"] = entry;
    }
}

Using the attribute

It's actually pretty simple to use this attribute. Since we are allowing the attribute to target classes or methods, we can just decorate our ProductDetailPageController class. Also, since we are adding the actual Entry value to our action parameters, we can add the Entry as a parameter in our actions. This is also why we bind the {entry} route data to the entryCode parameter.

[ValidateEntry]
public class ProductDetailPageController : PageController<ProductDetailPage>
{
    public ActionResult Index(ProductDetailPage currentPage, [Bind(Prefix = "entry")] string entryCode, Entry entry)
    {
        return View();
    }
}