How to intercept the User Provider in EPiServer 10

Let's say we have to build a user registration system, and every time user creates, updates or deletes his profile, we have to execute a series of actions like sending notification emails, updating user records in the CRM, etc.

Exposing the user management system to the public web is not a big issue. We can create a user account controller, a few services to deal with email notifications, CRM stuff, etc. and voilà.

But what happens when site administrator make changes to the user accounts from the admin interface? Is it possible to intercept that somehow in order to send notifications, update the CRM, etc.?

Managing user accounts in episerver 10

One way to solve this would be to replace the entire EditUser.aspx control with our own. You can find it under modules / _protected / CMS / CMS.zip / Admin / EditUser.aspx.

But that's a risky business and non-recommended solution. Especially when we know that WebForms stuff will be removed in Episerver Core (if not sooner).

Another solution would be to create custom MembershipProvider and implement our custom logic there. But what if we have several membership providers or decide to replace one implementation (SqlMembershipProvider) with another (Asp Identity)? Wouldn't be nice to write the code only once and use it with any implementation?

What's new in EPiServer 10?

EPiServer 10 comes with a new namespace called EPiServer.Shell.Security and brings these new fellows:

  • IUIRole
  • IUIUser
  • UIRoleProvider
  • UISignInManager
  • UIUserCreateStatus
  • UIUserManager
  • UIUserProvider

The admin interface, as well as EditUser.aspx, is updated to use these new classes.

The class we're particularly interested in is UIUserProvider. It has the following signature:

public abstract class UIUserProvider : IDisposable
{
    public abstract IUIUser CreateUser(
        string username, string password, string email,
        string passwordQuestion, string passwordAnswer,
        bool isApproved, out UIUserCreateStatus status,
        out IEnumerable<string> errors);

    public virtual bool DeleteUser(string providerName, string username, bool deleteAllRelatedData);

    public abstract void UpdateUser(IUIUser user);

    ...
}

We can use Castle DynamicProxy (which already comes with EPiServer) to create our interceptor which will be responsible for sending emails, etc. and use StructureMap to intercept all calls to UIUserProvider.

Here's the code for interceptor:

public class UserProviderInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        invocation.Proceed();

        if (invocation.Method.Name == "CreateUser")
        {
            CreateUser((string)invocation.Arguments[0],
                (string)invocation.Arguments[1],
                (string)invocation.Arguments[2],
                (string)invocation.Arguments[3],
                (string)invocation.Arguments[4],
                (bool)invocation.Arguments[5],
                (UIUserCreateStatus)invocation.Arguments[6],
                (IEnumerable<string>)invocation.Arguments[7]);
        }

        if (invocation.Method.Name == "UpdateUser")
        {
            UpdateUser((IUIUser)invocation.Arguments[0]);
        }

        if (invocation.Method.Name == "DeleteUser")
        {
            DeleteUser((string)invocation.Arguments[0],
                (string)invocation.Arguments[1],
                (bool)invocation.Arguments[2]);
        }
    }

    private void CreateUser(
        string username, string password, string email,
        string passwordQuestion, string passwordAnswer, bool isApproved,
        UIUserCreateStatus status, IEnumerable<string> errors)
    {
        // ...
    }

    private void UpdateUser(IUIUser user)
    {
        // ...
    }

    private void DeleteUser(
        string providerName, string username, bool deleteAllRelatedData)
    {
        // ... 
    }
}

Namespaces to include:

using System.Collections.Generic;
using Castle.DynamicProxy;
using EPiServer.Shell.Security;

And this is how we can tell the EPiServer to intercept UIUserProvider:

[ModuleDependency(typeof(ServiceContainerInitialization))]
public class UserProviderInterceptorModule : IConfigurableModule
{
    public void ConfigureContainer(ServiceConfigurationContext context)
    {
        var proxyGenerator = new ProxyGenerator();
        var interceptor = new UserProviderInterceptor();

        context.Container.Configure(
            x =>
            {
                x.For<UIUserProvider>()
                    .DecorateAllWith(i =>
                        proxyGenerator.CreateClassProxyWithTarget(i, interceptor));
            });
    }

    public void Initialize(InitializationEngine context)
    {
    }

    public void Uninitialize(InitializationEngine context)
    {
    }
}

Namespaces to include:

using Castle.DynamicProxy;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.ServiceLocation;
using EPiServer.Shell.Security;

Since our UserProviderInterceptor works with abstractions, it can be used with any concrete implementation.

comments powered by Disqus