EPiServer – Be Careful using Ignore Properties in Page and Block Models
While developing a project we decided to use properties with the ignore attribute to set some information programatically as well as hide these properties to the editor in some page and block models. A better explanation of this can be found here. Unfortunately, we realized that these properties were causing some issues we did not anticipate. This post, will explain what was the error, how we solved it and some recommendations about when to use ignore properties.
So, lets begin !!!!
First, we will recreate the code that caused the issue, so we will add a new article detail page model that we are going to use as example.
namespace Data.Models.Page { using EPiServer.Core; using EPiServer.DataAbstraction; using EPiServer.DataAnnotations; using System.ComponentModel.DataAnnotations; [ContentType(GUID = "5c2a6ae1-6cc0-408a-a72f-effe7e8ba65a", DisplayName = "ArticleDetailPage", GroupName = "Other")] public class ArticleDetailPage : PageData { [Display(GroupName = SystemTabNames.Content, Order = 100)] public virtual string ArticleTitle { get; set; } [Display(GroupName = SystemTabNames.Content, Order = 200)] public virtual string ArticleSubTitle { get; set; } [Display(GroupName = SystemTabNames.Content, Order = 300)] public virtual XhtmlString ArticleContent { get; set; } [Display(GroupName = SystemTabNames.Content, Order = 400)] public virtual string ArticleType { get; set; } [Ignore] public bool HasNewVersion { get; set; } } }
Then, we will add the controller for this page model, the controller will only set the ignore property HasNewVersion to the value of its own variable negated.
namespace Web.Controllers.Page { using System.Web.Mvc; using Data.Models.Page; using EPiServer.Web.Mvc; public class ArticleDetailPageController : PageController<ArticleDetailPage> { public ActionResult Index(ArticleDetailPage currentPage) { currentPage.HasNewVersion = !currentPage.HasNewVersion; return this.View(currentPage); } } }
Finally, we will add a view to display the results in the browser
@model ArticleDetailPage @{ Layout = null; } Article Title @Html.PropertyFor(x = > x.ArticleTitle) Article SubTitle @Html.PropertyFor(x = > x.ArticleSubTitle) Article Content @Html.PropertyFor(x = > x.ArticleContent) Article Type @Html.PropertyFor(x = > x.ArticleType) Article Has New @Model.HasNewVersion Article Object Hash Code @Model.GetHashCode()
Now, if we load the page that uses this model, it should have the variable in true (default boolean value is false) and it should keep that value every single time we reload the page because we are not saving the data in the CMS. Unfortunately if you load the page again you will realize that the variable HasNewVersion does not always have the value we expected. In fact, if we load the page in separate tabs, several times at once it can get messy
The reason for this is quite easy to understand, but hard to pick up as an error. The controller receive a parameter that is the current page in the CMS. EPiServer under the hood tries to get the page and fill the parameter of the controller with it. This parameter is filled always with the same object to increase performance (you can verify this with the GetHasMethod, which always return the same hash) , so when you modify the ignore property HasNewVersion, you are not modifying a unique object, you are modifying directly the content of the page in the CMS and because it has the ignore attribute, it will keep the value even without explicitly stating to be saved using the publish process.
This will generate a race condition when you open the page several times in several tabs at the same time. The variable displayed in the view will have the value that at that moment was set by the controller, but because several threads are modifying the same variable in the same object some, it will display random values (This behavior also occurs in blocks with controllers).
The easy solution for this is do not use ignore properties inside a page or block model, if you are going to modify it in the controller. Instead, add a view model which will have these extra properties and set them as before. Because the view model is created as a new object every time we go inside the controller it will be unique and will not raise the race condition error explained above.
And that is all. The lesson here is be careful when you use properties with ignore attributes, the page or block model is not a view model and it should not be treated like that. I hope it will help someone and as always keep learning !!!
Leave a Reply