CSLA and ASP.NET Core

Published on 24 January 2017

I am a fan of CSLA and I recently came accross a need to make a shiny CSLA business layer work nicely within the context of an ASP.NET Core application. This post is aimed at CSLA developers with a similar need. As of the current release, there are a few things that you will need to take care of in order to get CSLA working smoothly, and I will cover those in this post.

Csla.ApplicationContext

CSLA developers should be familiar with Csla.ApplicationContext which provides the means to access useful context, as well as the current User / IPrinciple. However when running under ASP.NET Core, Csla.ApplicationContext doesn't work correctly - as CSLA decides that your application is not a web application (as HttpContext.Current is null under ASP.NET Core) and so it ends up storing things that should be stored in HttpContext on the current Thread instead.

To overcome this, however, is pretty straight forward. You just need to implement an extensibility point that Rocky has provided, called an IContextManager.

IContextManager for ASP.NET Core.

Here is an implementation of an IContextManager that resolves the HttpContext using the IHttpContextAccessor instead:



    /// 
    /// Application context manager that uses HttpContextAccessor whenr esolving HttpContext
    /// to store context values.
    /// 
    public class HttpContextAccessorContextMananger : IContextManager
    {

        private const string _localContextName = "Csla.LocalContext";
        private const string _clientContextName = "Csla.ClientContext";
        private const string _globalContextName = "Csla.GlobalContext";

        private readonly IServiceProvider _serviceProvider;

        public HttpContextAccessorContextMananger(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }
      

        protected virtual HttpContext GetHttpContext()
        {
            var httpContextAccessor = (IHttpContextAccessor)_serviceProvider.GetService(typeof(IHttpContextAccessor));
            if (httpContextAccessor != null)
            {
                if (httpContextAccessor.HttpContext == null)
                {
                    //   Debugger.Break();
                }
                return httpContextAccessor.HttpContext;
            }

            // Debugger.Break();
            return null;
        }


        /// 
        /// Gets a value indicating whether this
        /// context manager is valid for use in
        /// the current environment.
        /// 
        public bool IsValid
        {
            get
            {
                return GetHttpContext() != null;
            }
        }

        /// 
        /// Gets the current principal.
        /// 
        public System.Security.Principal.IPrincipal GetUser()
        {
            return GetHttpContext()?.User;
        }

        /// 
        /// Sets the current principal.
        /// 
        /// Principal object.
        public void SetUser(System.Security.Principal.IPrincipal principal)
        {
            var context = GetHttpContext();
            if (context != null)
            {
                context.User = (ClaimsPrincipal)principal;
            }
            else
            {
                //  Debugger.Break();
            }
           
        }

        /// 
        /// Gets the local context.
        /// 
        public ContextDictionary GetLocalContext()
        {
            return (ContextDictionary)GetHttpContext()?.Items[_localContextName];
        }

        /// 
        /// Sets the local context.
        /// 
        /// Local context.
        public void SetLocalContext(ContextDictionary localContext)
        {
            var context = GetHttpContext();
            if (context != null)
            {
                context.Items[_localContextName] = localContext;
            }
            else
            {
              //  Debugger.Break();
            }
        }

        /// 
        /// Gets the client context.
        /// 
        public ContextDictionary GetClientContext()
        {
            return (ContextDictionary)GetHttpContext()?.Items[_clientContextName];
        }

        /// 
        /// Sets the client context.
        /// 
        /// Client context.
        public void SetClientContext(ContextDictionary clientContext)
        {
            var context = GetHttpContext();
            if (context != null)
            {
                context.Items[_clientContextName] = clientContext;
            }
            else
            {
              //  Debugger.Break();
            }
        }

        /// 
        /// Gets the global context.
        /// 
        public ContextDictionary GetGlobalContext()
        {
            return (ContextDictionary)GetHttpContext()?.Items[_globalContextName];
        }

        /// 
        /// Sets the global context.
        /// 
        /// Global context.
        public void SetGlobalContext(ContextDictionary globalContext)
        {
            var context = GetHttpContext();
            if (context != null)
            {
                context.Items[_globalContextName] = globalContext;
            }
            else
            {
              //  Debugger.Break();
            }
        }
    }

Configuring CSLA

Now that you have this implementation, you need to tell CSLA to use it. In the ASP.NET Core world, we are used to nice extension methods that we can use in our startup.cs classes, in order to configure things fluently.

CSLA doesn't provide one of these just yet, but we can create one fairly easily:


    public static class CslaConfiguration
    {
        public static IServiceCollection ConfigureCsla(this IServiceCollection services, Action<CslaOptions, IServiceProvider> setupAction = null)
        {
            services.AddSingleton<CslaOptions>((sp) =>
            {
                var options = new CslaOptions();
                setupAction?.Invoke(options, sp);
                return options;
            });

            return services;
        }

        public static IApplicationBuilder UseCsla(this IApplicationBuilder appBuilder)
        {
            // grab the options
            var options = appBuilder.ApplicationServices.GetRequiredService<CslaOptions>();

            // configure csla according to options.
            Csla.Server.FactoryDataPortal.FactoryLoader = options.ObjectFactoryLoader;
            Csla.ApplicationContext.WebContextManager = options.WebContextManager ?? new HttpContextAccessorContextMananger(appBuilder.ApplicationServices);
            
            return appBuilder;
        }

        public class CslaOptions
        {
            public IObjectFactoryLoader ObjectFactoryLoader { get; set; }

            public IContextManager WebContextManager { get; set; }           

        }
    }

So we can now configure CSLA in our startup.cs like this:


    public class Startup
    {              
       
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Configure Csla
           services.ConfigureCsla((options, sp) => {               
                // could configure other CSLA hooks / options here in future.
            });

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login")
            });
           app.UseCsla();      
        }        
    }

CSLA ApplicationContext should now behave correctly thanks to the custom IContextManager for ASP.NET Core.

Conclusion

I also went a few steps further in my particular application, to enable DI to flow from my ASP.NET Core IServiceProvider into my CSLA business tier, but that's a topic for another article.

I suspect that Rocky will release improved support for ASP.NET Core in the future, so that this post becomes obsolete. Until then, Bon Appétit!

comments powered by Disqus