Creating an async block in EPiServer

Blocks are reusable components within a webpage that were introduced with the release of EPiServer 7. It was a major step forward for EPiServer as it shifted from page based content model to actual usage of content based model. The purpose of this blog is to demonstrate the implementation of an asynchronous block in an EPiServer webpage. 

The block will get the data from a controller using the $http service of AngularJS and the controller itself will do async calls to an external web api.

Let’s assume that you have installed already Alloy Mvc Templates. First go to Global.asax.cs and add the following code to handle the routing for blocks.

RouteTable.Routes.MapRoute("DefaultControllerAction", "{controller}/{action}");
            RouteTable.Routes.MapRoute("Car", "car/{action}/{id}",
                new { controller = "Car", action = "GetCars", id = "" });

Then create a new block that inherits SiteBlockData. No properties are required. Create also the model (which will be empty) and the view for the block. The view should be something like the folllowing.

The “app” tag is missing so I have to add that too. On the _Root view on the html tag add ng-app=”AsyncPoc”. We are missing the angular script and the controller. On the controller we first need to inherit from AsynController. For the method to be async both the “async” word in the beginning and the “await” are needed for it to work asynchronously.

A requirement for our example is the connection is to be achieved through https. To achieve this I have setup a simple web api using the example project in visual studio. I have added the project in my local IIS and added the https binding using the IIS Express Development Certificate. Make sure that you have the certificate added in the root of the trusted certificates in your machine. Notice the comment in the code that is used to bypass security issues with SSL. It is only used for demonstrations purposes. Do not use it when you have your own certificate for your site but instead make sure that there are no errors in the connection. Also in the client initialization I have added a key, value pair in the web.config from which I get the path i.e. https://asynpocapi.myshosting.com

public class CarController : AsyncController
{
	[HttpGet]
	public async Task<ActionResult> GetCars(string id)
	{
		// Initialize Client
		var client = new HttpClient {BaseAddress = new Uri(ConfigurationManager.AppSettings["AsyncPocApi"])};
		client.DefaultRequestHeaders.Accept.Clear();
		client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

		var cars = new List<Car>();

		// Bypass certificate issues with iis express dev certificate
		System.Net.ServicePointManager.ServerCertificateValidationCallback =
			((sender, certificate, chain, sslPolicyErrors) => true);

		try
		{
			var response = await client.GetAsync("api/cars");
			if (response.IsSuccessStatusCode)
			{
				var carsString = await response.Content.ReadAsStringAsync();
				//Covert string obj to list of cars and return as succesfull
				cars = JsonConvert.DeserializeObject<List<Car>>(carsString);
				return Json(new {Success = true, Cars = cars}, JsonRequestBehavior.AllowGet);
			}
		}
		catch (Exception ex)
		{
		}

		return Json(new {Success = false, Cars = cars}, JsonRequestBehavior.AllowGet);
	}
}

Let’s also add a car class.

public class Car
{
	public string Brand { get; set; }
	public string Model { get; set; }
	public string Price { get; set; }
}

Finally let’s create a new javascript that will handle the angular code. The implementation of service is needed to retrieve the data from the controller (in similar way as ajax in jquery). Keep in mind that the way the scope works in this script there is no problem to have more than one controllers in the same page.

(function() {
    var app = angular.module("AsyncPoc", []);
    app.controller("AsyncPocController", function($scope, asyncPocService) {
        $scope.cars = [];
        
        asyncPocService.getCars().then(function(newCars) {
            $scope.cars = newCars;
        });
    });

    app.service("asyncPocService", function($http, $q) {
        return ({
            getCars: getCars
        });

        function getCars() {
            var request = $http({
                method: "get",
                url: "car/getcars",
                params: {
                    action: "get"
                }
            });
            

            return (request.then(handleSuccess, handleError));

            function handleError(response) {
                if (!angular.isObject(response.data) || !response.data.message) {
                    return ($q.reject("An unknown error occurred."));
                }

                return ($q.reject(response.data.message));
            }

            function handleSuccess(response) {
                return (response.data.Cars);
            }
        }
    });
}())

It is outside the scope of this blog to show the code for the Web Api but it is pretty simple to create one based on the example of the Visual Studio and with the purposes of this post. 

When all is done you can be able to add one or more blocks of this type in the same page to perform async calls to an external web api.

Extending FriendlyUrlRewrite provider for EPiServer 6 R2

It is a common practice among CMS to convert their internal links to external which are user friendly. EPiServer achieves that by using the FriendlyUrlRewrite provider. In a recent case one of our customers wanted to remove a specific query string from their site urls and replace it with an actual name that this string represented. Since the original case is complicated a simpler example will be presented. Suppose that there is an enumerable with 3 pairs, Stockholm = 1, Lund = 2 and Uppsala = 3. If there is a link of type www.softresource.se/Kontakt/?region=2  that should be rewritten into www.softresource.se/Kontakt/Lund/

While in EPiServer 7 (or latest) this can be easily handled with routing, in EPiServer 6 R2 the only option is to extend the FriendlyUrlRewrite provider. This can turn into a very complex task depending on the demands of rewriting. In the following example aside from EPi 6 R2 the site is also using PageTypeBuilder.

Firstly, one needs to create a class that extends EPiServer FriendlyUrlRewrite and override the following methods

public class SrUrlRewriteProvider : EPiServer.Web.FriendlyUrlRewriteProvider {

   public override bool TryConvertToInternal(UrlBuilder url, out CultureInfo preferredCulture, out object internalObject) {}

   public override bool ConvertToInternal(UrlBuilder url, out object internalObject){}

   protected override bool ConvertToInternalInternal(UrlBuilder url, ref object internalObject) {}

   protected override bool ConvertToExternalInternal(UrlBuilder url, object internalObject, Encoding convertToEncoding){}
}

Then EPiserver.config urlRewrite providers must be updated. Use the name of the class as name and in the type field include the full path of the class and then the namespace.     

<add name="SrUrlRewriteProvider"
          enableSimpleAddress="true"
          friendlyUrlCacheAbsoluteExpiration="0:0:10"
          type="SrExternwebben.UrlRewriteProviders.SrUrlRewriteProvider, SrExternwebben"
          description="Rewrites regional url" />

Then set this one as the default provider <urlRewrite defaultProvider="SrUrlRewriteProvider">

Now that UrlRewriteProvider is set up we can start working with the functions. But let’s explain first what they do. When an EPiServer page loads, the first function to fire is the ConvertToInternalInternal. This functions gets the friendly url (the actual link on the browser) and converts it to the internal EPiServer url (Something like the following /Common/PageTemplates/ContactPageTemplate.aspx?id=45&epslanguange=sv ). If EPiServer finds the correct page then it starts to render it and with that all the urls included in that page. At that step the function TryConvertToInternal fires. This function should check if a url is qualified for conversion from our provider or not. If not it should be passed to the default provider of EPiServer. If it is qualified it should be rewritten using the method ConvertToInternalExternal() which will do the final rewriting.

While a few people use only the ConvertToInternal method in this case only the TryConvertToInternal fired on debug. Just to be safe both are included in the class and since they do the same thing their contents should be the same.

  public override bool TryConvertToInternal(UrlBuilder url, out CultureInfo preferredCulture, out object internalObject)
       {
           internalObject = null;
           preferredCulture = ContentLanguage.PreferredCulture;
           if (this.IsUrlEscaped(url.ToString()))
           {
               return base.ConvertToInternalInternal(url, ref internalObject);
           }

var referrer = url.ToString().Replace(HttpContext.Current.Request.Url.Scheme + "://" + HttpContext.Current.Request.Url.Host, string.Empty);

           if (this.IsReferrerEscaped(referrer))
           {
               return base.ConvertToInternalInternal(url, ref internalObject);
           }

           return this.ConvertToInternalInternal(url, ref internalObject);
       }

Below actual code related to this example is avoided in favor of describing the obstacles that one might encounter and their solution. Since it will be referred quite a few times below to use the function from the default UrlRewriteProvider use base or else this (as shown in the example above).

The most important step is to define the logic of the algorithm and how to handle the links. With regards to the example with the 3 regions the logic should be the following for ConvertToInternalInternal function.

  • Check if the url is not null.
  • Exclude all the cases that a url should not be rewritten. Ex. A handler using the region query string.
  • Handle the url properly when the site is being browsed from edit mode. When browsing the rewritten pages from edit mode the site was crashing. That happened when the code had to cast the HttpContext.Current.Handler as PageData and it couldn’t get the id of the page. One solution is to check if the page is in edit mode and if so pass it to the normal provider. The other solution is to clean the url. After examination of the error I found out that a couple of urls seemed like www.softresource.se/Kontakt/Lund/?id_45_24564?epslanguage=sv
    This means that EPiServer was adding after the friendly url the id of the page and the WorkPageId separated by a “_”. Using another method that removed the “_” and passed the WorkPageId later in the pagereference to get right version of the page the site worked correctly on edit mode.
  • As a last step a check is required to see if the url can be converted to PageData. If so then there is no need to rewrite it. Else if the last segment of the url has as name one of the three names of the Region enum remove it from the url and then convert it to PageData. If the url is correct the conversion should be successful. Finally the url in the function should be updated with the page Id, the language and the region with their value in the queryCollection and return true like below.
url.Path = Url(page.StaticLinkURL).Path;
url.QueryCollection.Add("id", page.PageLink.ID.ToString());
url.QueryCollection.Add("epslanguage", ContentLanguage.PreferredCulture.TwoLetterISOLanguageName);
url.QueryCollection.Add("region", regionId);
return true;

ConvertToInternalExternal on the other hand is responsible for rewriting internal links to external and while the aforementioned function fires only once in every page load this one fires for every link included in the page. This includes css, js references, scripts etc. Additionaly links that should be handled by that might not only be internal but also already converted to external (for instance if there is a dropdown with items each region link to ContactPage that are still not rewritten). Here is how the algorithm should handle the links.

  • Check if the url is not null.
  • Exclude all the cases that a url should not be rewritten and can be easily identified like scripts etc. For example local scripts are located in a different folder than PageTemplates that needs to be rewritten.
  • Check if the link is internal and try to get the page id. If yes try to get the region query string. If region exists then the region name can be added in the end of the pages url.
  • If there is no PageReference is found segment the url and check if the last segment has the query string of a region (This is to handle friendly urls with no rewriting). If so remove it from the queryCollection, get the region's’ name and add it as an additional segment to the url and then return true.

With regards to the last part of the algorithm one might say that it is better to rewrite those urls when they are created in code behind than rewriting them for a second time. While this solution is ideal as a concept and less performance expensive in this example two additional things were taken into account. First the option to roll back from the custom UrlRewrite provider to the default without having to worry about the functionality on the site. Also a lot of the site needed to be rewritten and tested to verify that it works correct. Secondly ConvertToExternal method should be overridden which I have already tried and noticed that all the urls in the page tree in the edit mode were rewritten also and as such I preferred to go with my initial method.

Another issue that might be happening on the rewritten pages is that ___doPostback() javascript methods are failing. This unexpected behavior might occur when the action in the main form of the page is not rewritten properly. If it is fixed all the postback events should work as expected.

If all the above are covered and there is solid logic in the conversion algorithm of the friendly urls to internal and vice versa then the site should work with no problem.


Exclude pages in pagetree

On my current project, the customer wants to hide pages in the pagetree if they aren’t in the current language that the editors is editing. You have the option in EPiServer to use "Show only pages in [language]", but this isn’t persistent. So if you refresh the page you will lose this setting and have to set it again. Talked to EPiServer regarding if this was a bug or not, but got the answer that they had discussed this and it was by design. Therefore the customer came up with the requirement that this should not be necessary and wanted this by default since they aren’t going to translate the pages but instead have duplicate pages of English and Swedish pages.

This was quite an easy fix though. Since EPiServer uses REST queries to get children, you can create your own query when fetching children. Got the idea from Linus Ekström old blogpost

All you have to do is create a class, decorate it with [ServiceConfiguration(typeof(IContentQuery))] and make the class inherit from ContentQueryBase e.g. But there is an even easier way. EPiServer has a couple of classes that inherits ContentQueryBase, and there is one called GetChildrenQuery. So instead you can inherit from GetChildrenQuery, and therefore you will get the correct name at once and you can override the GetContent method to use default and modify it the way you want.

But since you’re registering another query with the same name, it won’t work until you give it a higher rank which is a property that will be checked, and it uses the one with highest rank. Default rank value is 0.

Below is my example where I’m only fetching pages in the language that is being edited. Note that this take place everywhere and not only in the pagetree, this also exclude blocks in other languages in the content asset panel.

 [ServiceConfiguration(typeof(IContentQuery))]
    public class ContentExcluder : GetChildrenQuery
    {
        public ContentExcluder(IContentQueryHelper queryHelper, IContentRepository contentRepository, LanguageSelectorFactory languageSelectorFactory) : base(queryHelper, contentRepository, languageSelectorFactory)
        {
        }

        protected override IEnumerable<IContent> GetContent(ContentQueryParameters parameters)
        {
            CultureInfo preferredCulture = ContentLanguage.PreferredCulture;

            List<IContent> content = base.GetContent(parameters).ToList();
          
            for (int i = 0; i < content.Count; i++)
            {
                var localizable = content[i] as ILocalizable;
                if (localizable != null && !localizable.Language.Equals(preferredCulture))
                {
                    content.RemoveAt(i);
                    i--;
                }
            }
            return content;
        }

        public override int Rank
        {
            get { return 100; }
        }
    }

Klassbibliotek från er leverantör och tredjepartsprodukter

När ni anlitar er partner för att bygga ert webbprojekt har ni säkert ställt krav på viss erfarenhet och kunskap om den plattform som ni vill att er webbplats ska ligga på. Den tekniska delen av den erfarenheten är ofta samlad i ett så kallat klassbibliotek.

Det är filer med kod som innehåller standardfunktioner och ”bra-att-ha”-grejer som partnern återanvänder i flera projekt, och det är bra för er som kund. Koden är testad tidigare och innehåller därför få buggar, dessutom så sparar man tid genom att slippa skriva kod för standardfunktioner utan kan snabbt komma till den del i projektet som har med att utveckla just er unika webbplats.

Så länge ert samarbete med partnern pågår så är detta inget problem, men vad händer om samarbetet avslutas? Då är det väldigt viktigt att ni i avtalet med partnern säkerställt att ni har oinskränkt nyttjanderätt till all kod som körs på er webbplats. Källkoden på det som utvecklas i ert webbprojekt ska naturligtvis tillfalla er som kund, men dessa klassbibliotek som er partner haft sedan tidigare, där är det inte lika självklart att källkoden följer med. Det är heller inte nödvändigt eftersom den inte innehåller några specifika funktioner för er webbplats som måste ändras om ni vill ändra något på er webbplats. Men de funktioner som finns där är nödvändiga för att er webbplats ska fungera så därför måste ni ha rätt att nyttja dessa klassbibliotek även om ert samarbete med partnern är avslutat. Då går webbplatsen att flytta till en ny leverantör och kan driftsättas och vidareutvecklas där utan att ni måste bygga om den kod som fanns i klassbiblioteken för att er förra leverantör behöll dessa vid leverantörsbytet.

Tredjeparts­produkter

Ibland så ska man bygga funktioner på sin webbplats som finns helt eller delvis färdigutvecklade och säljs som kommersiella program eller klassbibliotek där det krävs licenser. Det är ofta effektivt att använda dessa eftersom man sparar tid, kan få support och man sparar pengar eftersom licenskostnaden vanligtvis inte alls är så stor som kostnaden för att utveckla hela produkten själv.

Dessa tredjepartsprodukter kommer ofta som rekommendation från er partner som kanske är van att jobba med dessa från tidigare projekt. Då är det viktigt att ni ser till att licenserna för dessa tredjepartsprodukter tecknas i ert namn så de tillhör er vid ett eventuellt byte av leverantör.

Det kan vara bekvämt att be er partner ta hand om allt och bara leverera projektet men det är väldigt viktigt för er som kund att säkerställa nyttjanderätten till alla delar av er webbplats så ni har möjlighet att fritt byta leverantör om ni vill.