I have worked a lot with Episerver through the years. Like most Episerver developers, most of my work has been developing websites. But the world is also demanding mobile apps.
I have experienced that many clients want native apps, as a supplement to a responsive website. But supporting both a website and one or more native apps require more implementation effort.
Here are five of the most important learnings I have done through recent years.
1. Native apps do not support cookies
A good app developer colleague once told me that native apps do not support persistent or session cookies. This is very unfortunate, because both Episerver CMS and Episerver Commerce depend heavily on such cookies.
These cookies are used to store (among many things):
- Anonymous cart ID
- Current market
- Selected language
- Selected currency
- Visitor group state
- A/B testing state (KPI)
The first four are quite important. The last two are more nice-to-have, although editors would expect them to work, as they do for the website.
But why not send these values in custom HTTP headers from native apps? Or, to change the currency or market of a cart, why not call an API to change it?
This works great for the regular cookie values, except for personalization and A/B testing state values.
Calling a cart endpoint could look something like this, where lines 3-5 are the added custom headers.
GET /api/cart
Accept-Language: en-US
X-AnonymousId: A35D2A81-F9DB-4D11-9CFB-AB42F29C0CE2
X-MarketId: UAE
X-Currency: AED
<< Other standard headers go here >>
2. Use Bearer tokens instead of authentication cookies
Websites use authentication cookies for keeping users logged in. But native apps generally use Bearer tokens for API authentication. In ASP.Net this typically means using, or implementing, an OAuth service for generating and validating such tokens.
A Bearer token gets sent to the endpoint in a standard Authorization header, not in a cookie. So now this cookie is removed, too.
But if WebAPIs have to support requests from both website and native apps, they need to support both cookie authentication and token authentication.
The cookie authentication middleware normally works in active mode (meaning it will handle all requests), while the default OAuth authentication middleware is working in passive mode.
This means that if an API controller requires authentication and a request is not authenticated, OWIN would actually redirect the client to a login page. This does not make sense for an app.
To make WebAPIs always respond with a 401 Unauthorized status, instead of responding with a redirect, we need to make two changes:
- Reverse the order of authentication filters when initializing the WebAPI configuration.
- Keep the cookie middleware from redirecting unauthenticated requests to the login page.
These fixes also works for Episerver Content Delivery API and Episerver Service API.
3. Apps have long deployment cycles
One thing that is very different and challenging, coming from website-only projects, is long deployment cycles.
When we release a new version of a website, all client-side resources will be used by browsers immediately (unless cache busting is missing).
But when releasing a new build of a native app, it typically takes days, weeks or months for all users to upgrade to it. In the meantime, our WebAPIs need to be backwards compatible for at least a few past app versions. This can be very challenging, and it will keep old code in the solution for a long time, until analytics suggest that it’s safe to remove.
I can tell at least four ways to work around the deployment cycle time.
1. Force upgrade the app
The simplest solution is to block old builds from running, and simply force the users to upgrade. We can show a modal box on startup that simply tells the user to upgrade the app.
This is technically a very clean approach. But it is a very bad user experience. No business in their right mind would approve this, unless it is utmost critical to do so.
2. Feature switching
We cannot just cache-bust us out of this challenge, like we can with frontend bundles. Instead we could implement feature switching, based on a custom header containing the app platform and version (for instance "iOS-v1.1").
This way the API methods can enable a feature if the app build is known to support it, and disable it if the app build is too old.
3. API versioning
For major API changes that breaks the backwards compatibility, we can also add WebAPI versioning.
This approach lets us add new API controllers with the same general naming, but with different logic or different request/response models.
4. Extend response model
For minor changes, we can just add new fields or new field values to the response objects.
This works out if the apps will ignore response data that it does not know about. But do this sparingly, because over time this approach can quickly clutter the code base.
4. Apps need to keep local cache
Most native apps are made as standalone and offline clients to some kind of online platform.
For an app that connects to an Episerver Commerce site, it would make sense to locally cache the shopping cart, customer profile and some catalog content items.
If such things are not cached, the app would have to make lots of requests to the APIs when suspending and resuming the app, or simply when navigating around the app. This could really deteriorate the user experience and site performance.
This, however, raises many challenges in keeping the local and the online shopping carts synchronized. One thing is that Episerver requires a session cookie in order to store an anonymous shopping cart.
So, if the user is not mandated to log in to the app, before placing items in the shopping cart, it cannot be stored in Episerver until the user logs in. The app can either keep the cart items in its local cache, and then place them in a cart in Episerver when the user logs in.
Another solution is to simply not use a local cart cache at all, and only use the online cart. But again, the missing cookie support has an impact here.
To support anonymous carts with native apps, you can implement a cookie-less Anonymous ID middleware to make this work.
5. Not all Episerver products are readily supported
Episerver DXP offers some really strong features for content editors. The bad news is that many of them do not really work great on native apps.
Episerver Forms is not supported without a lot of custom work. And think about it: how should we render content blocks and Form elements in an app?
A native app expects to receive the raw data about a block to render in a native block template. Of course, this can be replicated in a native app solution. But Episerver Forms is a complicated product with flexible and extensible logic, which does not easily replicate in a native app solution.
Visitor groups work fine. In a custom API method, we can simply call the ContentArea.FilteredItems method, instead of ContentArea.Items, when returning content area items in a JSON response. This way, Episerver will honor visitor group criteria and use the right version of the content area items.
Episerver Content Delivery API works fine. If bearer token authentication is used instead of cookie authentication, then visitor group personalization will work fine, too.
Episerver Personalization, as in Content Recommendations and Product Recommendations, depends on a cookie. So, in order to use these features, we would need to replace some built-in functionality to support both cookie and header sessions.
Episerver Personalized Search & Navigation can be made to work with some tweaking. Like so many other things it depends on cookies. So, in order to use Personalized Find through a WebAPI, we need to make it support both cookies and headers. This is not out-of-the-box, but can be easily implemented.