This is one of a series of posts looking at migrating Umbraco packages to V9 and .NET Core. Other posts in this series:
- Introduction
- A False Start
- A Clean Start - Controllers, Services, Configuration and Caching
- Criteria Providers - Working With HttpContext
- Leaning on Umbraco
- Migrating Tests
- Extension Methods
- Migrations
- Wiring It All Up
- Distributing and Wrapping Up
Migrating Criteria Providers
Within the package I'm looking to migrate to Umbraco V9 and .NET Core, Personalisation Groups, there are a number of Criteria classes, used as methods of identifying users for the purpose of personalising their website experience. These are things like day of week, time of day, presence of a querystring, a cookie value etc.
Most of these rely on something external to the class in order to retrieve a value from the browsing context in order to match against what's configured in the CMS against the content, in order to decide whether there's a match and the content should be shown or not. In order to follow the practice of single responsibility principle, to support alternative implementations and to make testing easier, I've abstracted these to separate provider classes and inject them into the constructor of the criteria class at rutime.
The simplest one is the "Day of Week" criteria, viewable here, which depends on an IDateTimeProvider, which is implemented by a simple wrapping class around System.DateTime. The advantage being that in a test, I can provide a mocked or stubbed implementation of IDateTimeProvider, with a known date for testing against.
In migrating these providers, there's not much that's Umbraco V9 related yet, but some changes were necessary to accomodate the new patterns and syntax of .NET Core.
HttpContext in .NET Core
Aside from the time based criteria, most of the others relied on something retrieved from the HttpContext - e.g. querystring values, headers, cookies etc.
In migrating these provider classes, whilst concepts are the same and classes similar, there was a fair bit of subtle change around how we work with HttpContext in .NET Core.
Accessing HttpContext
The first and most obvious change is that we are unable to call HttpContext.Current to gain access to the details of the current request, response, cookies etc. Instead - and again following the general pattern of coding in .NET Core of injecting what you need - we can provide an IHttpContextAccessor via the constructor of our class, which has an HttpContext property. You can see this in use here.
It's worth noting too that we only need this in our service classes. Within a controller (or other rendering items like razor pages), we have properties on the base class to use.
Cookies
When working with cookies, we no longer have access to an HttpCookie class. Instead we work with methods that take the cookie key and value directly.
For example, to add a cookie to the response, we call Response.Cookies.Append(key, value) rather than Response.Cookies.Add(cookie).
Items
I just found one subtle change in working with HttpContext.Items, in that the Contains method has been renamed to ContainsKey.
Session
In my minimal use of session variables so far, I ran into one syntax change, where retrieving values from HttpContext.Current.Session[key] has become HttpContext.Session.GetString(key).
Query String
In building web applications on .NET we don't usually have to work with querystrings directly, rather rely on model binding to map directly to method parameters on controllers. When we do though, we had access to an object on the request implemented as a NameValueCollection. In .NET Core it has a new type, that of IQueryCollection, retrieved from _httpContextAccessor.HttpContext.Request.Query.
Headers
Similarly, headers rather than being returned as NameValueCollection have a type of IHeaderDictionary.
The header values themselves - and this applies also to querystring values - are no longer simple strings, but rather instances of a new StringValues type. This is a wrapping class that handles values that may be empty, a single string or multiple strings (which are allowed). You can simply .ToString() on the value if expecting a single value or are happy to handle the multiple values as comma separated values. You can see how I'm doing both this and handling multiple values here.
Server Variables
In .NET Framework, we're able to access all the server variables associated with the request via Request.Servervariables, but this is no longer available. The reason is due to .NET Core no longer being strongly tied to a web server, i.e. IIS, rather being deployed in a reverse proxy setup. It seems they are accessible if you know you are hosting in IIS and install some extensions, but more generally, we'd expect these variables to be mapped to headers and available via other properties of the HttpContext object. So I've recoded my use of these to work with headers.
Host
The host for the current request, previously available at Requet.Url.Host is now accessed from Request.Host.Value.
Referrer
The referring URL for a request in ASP.NET Framework could be retrieved from Request.UrlReferrer.AbsoluteUrl. In .NET Core, this, and other header values, can be retrieved from Request.GetTypedHeaders().Referer?.AbsoluteUrl. The extension method GetTypedHeaders() is accessible if you add a reference to Microsoft.AspNetCore.Http.
Authentication Status
There'll be a need to dig further into membership for this package, but one quick thing I needed was to be able to tell if the current request is being made for a logged in or anonymous user. Previously this flag was available on Request.IsAuthenticated; in .NET Core we need to retrieve User.Identity.IsAuthenticated from the HttpContext object.
Other Dependencies
The only other amend I needed to make in porting the providers was in the geo-location one, where I'm working with an external dependency. Fortunately, this has been migrated to .NET Standard and hence I could simply reference it from NuGet and no other changes were necessary.
Other changes made have been discussed in the previous article - using IHostingEnvironment to get access to paths to files on disk, and Umbraco's IAppPolicyCache for runtime caching.
Following Along
The repository containing the code for the migrated package is here. At the time of writing, the state of the migrated code can be seen using this link.
Comments
Post a Comment