Up till now in my SharePoint 2010 projects, I’ve been using the very nice SharePoint Service Locator implementation, from the patterns & practices group. This has been really useful, and works great. If you’re not familiar with the service locator pattern, you can read up on it here. Using this pattern, it’s easy to build a lightweight common library that you pass out to your team (or as is often the case “teams”) of developers without them having to mess around with the implementation of every interface. After all, the implementation of the interfaces are registered and deployed on the SharePoint server using a feature, and that’s all developers need to know. In many instances, developers might even develop for SharePoint without even having SharePoint installed. The service locator allows you to build an API that often looks like this in my projects:
- AppContext.Repositories.ContactsRepository.GetContact( … ), UpdateContact( … )
- AppContext.Services.RegisterUser( … ), PurchaseProduct( … ) –> complex step that might work with multiple repositories and talk to AD, and the user profile manager
- AppContext.Data.Call<storedprocedure>( … ) –> allow a direct call to a database, which happens sometimes in a custom service application
Why Autofac?
While Autofac provides service locator functionality with it’s ILifetimeScope and IContainer implementations, it also provides dependency injection of constructor parameters, and properties, and a fluent interface that allows late-bound resolution of injected parameters, and much more. Other containers you could look at are: StructureMap (another favorite of mine), Unity, Ninject, Spring.NET, Castle Windsor. Based on benchmarks though – see here and here – it seems that Autofac is one of the better performing containers. Another thing that is useful about using Autofac is the “InstancePerLifetimeScope()” registration extension that will automatically instantiate the registered type for every ASP.NET request, and properly dispose of the object at the end of the ASP.NET request, which makes it an ideal candidate to minimize round-trip connections to a database for example, or other persistent storage.
Autofac vs. SharePoint Service Locator
Whereas the SharePoint Service Locator stores it’s mapping of interfaces to their implementations as persisted objects in the database, Autofac wires things up in ASP.NET during the Application_Start event. Autofac also requires some additional configuration in the web.config file, so already, Autofac is going to demand a little more of a setup process.
The other core need is to allow developers to wire up additional mappings in any new features they develop, and it would be nice for there not to be a need to always modify the web.config file everytime a new feature with new mappings is developed. So storing mappings in SharePoint might be a good idea.
Wiring Autofac up
The following code is a Farm-scoped Feature EventReceiver that wires up Autofac on all the web front-ends within the farm. The code takes care of registering Autofac and unregistering Autofac. It primarily does 2 things:
- Makes all configuration changes to the web.config file necessary to support Autofac
- Alters the global.asax file on all web front-ends
IISRESET is required after installing the feature
- [Guid("9578f389-4e0b-40ef-9e8e-5358003e64d4")]
- public class AutofacIntegrationEventReceiver : SPFeatureReceiver
- {
- public override void FeatureActivated(SPFeatureReceiverProperties properties)
- {
- // generally equivalent to: SPFarm.Local.Services.GetValue<SPWebService>()
- var webService = properties.Feature.Parent as SPWebService;
- if (webService == null)
- {
- throw new ArgumentNullException("properties.Feature.Parent");
- }
- // let's register an autofac module (saves the config in the farm properties)
- SPContainerBuilder.RegisterModule(typeof(Playground.PlaygroundModule2).AssemblyQualifiedName, null, null, true);
- // let's make the necessary changes to register autofac in web.config and global.asax
- var globalAsaxFiles = new List<string>();
- var zones = Enum.GetValues(typeof (SPUrlZone)).Cast<SPUrlZone>().ToArray();
- foreach (var webApp in webService.WebApplications)
- {
- if (webApp.IsAdministrationWebApplication)
- continue; // don't mess with central admin 🙂
- // assuming every other web application "wants" to use Autofac
- // (this may not be the case, change logic here, or scope the feature differently)
- foreach (var entry in _autofacConfigEntries)
- {
- webApp.WebConfigModifications.Add(entry.Prepare());
- }
- webApp.WebService.Update();
- webApp.WebService.ApplyWebConfigModifications();
- var paths =
- zones.Select(z => Path.Combine(webApp.GetIisSettingsWithFallback(z).Path.ToString(), "global.asax"))
- .Distinct().Where(File.Exists).ToArray();
- globalAsaxFiles.AddRange(paths);
- }
- // Now replace the global.asax file(s) - I dare you!!
- try
- {
- var asaxFileContents =
- string.Format(
- @"<%@ Assembly Name=""Microsoft.SharePoint""%><%@ Assembly Name=""{0}""%><%@ Application Language=""C#"" Inherits=""{1}"" %>",
- typeof(IContainerProviderAccessor).Assembly.FullName,
- typeof(Autofac.Integration.SharePoint.GlobalApplication).AssemblyQualifiedName);
- foreach (var asax in globalAsaxFiles)
- {
- File.WriteAllText(asax, asaxFileContents, Encoding.UTF8);
- }
- }
- catch
- {
- // Modify the global.asax files manually (this shouldn't happen)
- }
- }
- public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
- {
- var webService = properties.Feature.Parent as SPWebService;
- if (webService == null)
- {
- // allow de-activation here
- return;
- }
- var globalAsaxFiles = new List<string>();
- var zones = Enum.GetValues(typeof (SPUrlZone)).Cast<SPUrlZone>().ToArray();
- foreach (var webApplication in webService.WebApplications)
- {
- var modsCollection = webApplication.WebConfigModifications;
- for (var i = modsCollection.Count - 1; i > -1; i--)
- {
- if (modsCollection[i].Owner == ConfigModsOwnerName)
- {
- // Remove it and save the change to the configuration database
- modsCollection.Remove(modsCollection[i]);
- }
- }
- webApplication.Update();
- // Reapply all the configuration modifications
- webApplication.WebService.Update();
- webApplication.WebService.ApplyWebConfigModifications();
- var paths =
- zones.Select(z => Path.Combine(webApplication.GetIisSettingsWithFallback(z).Path.ToString(), "global.asax"))
- .Distinct().Where(File.Exists).ToArray();
- globalAsaxFiles.AddRange(paths);
- }
- // Now replace the global.asax file(s)
- try
- {
- var asaxFileContents =
- @"<%@ Assembly Name=""Microsoft.SharePoint""%><%@ Application Language=""C#"" Inherits=""Microsoft.SharePoint.ApplicationRuntime.SPHttpApplication"" %>";
- foreach (var asax in globalAsaxFiles)
- {
- File.WriteAllText(asax, asaxFileContents, Encoding.UTF8);
- }
- }
- catch
- {
- // Modify the global.asax files manually (this shouldn't happen)
- }
- }
- #region Web.Config Modification Entries
- private const string ConfigModsOwnerName = "Autofac Integration For SharePoint";
- private readonly WebConfigEntry[] _autofacConfigEntries =
- {
- // configSections entry
- new WebConfigEntry(
- "section[@name='autofac']"
- ,"configuration/configSections"
- ,"<section name=\"autofac\" type=\"" + typeof(Autofac.Configuration.SectionHandler).AssemblyQualifiedName + "\"/>"
- ,SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode
- ,false)
- // autofac entry
- ,new WebConfigEntry(
- "autofac"
- ,"configuration"
- ,"<autofac/>"
- ,SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode
- ,false)
- // autofac modules entry
- ,new WebConfigEntry(
- "modules"
- ,"configuration/autofac"
- ,"<modules/>"
- ,SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode
- ,false)
- // This is just a sample module
- ,new WebConfigEntry(
- "module[@type='" + typeof(Playground.PlaygroundModule).AssemblyQualifiedName + "']"
- ,"configuration/autofac/modules"
- ,"<module type=\"" + typeof(Playground.PlaygroundModule).AssemblyQualifiedName + "\"/>"
- ,SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode
- , false)
- // autofac httpModules entries
- ,new WebConfigEntry(
- "add[@name='ContainerDisposal']"
- ,"configuration/system.web/httpModules"
- ,"<add name=\"ContainerDisposal\" type=\"" + typeof(ContainerDisposalModule).AssemblyQualifiedName + "\" />"
- ,SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode
- ,false)
- ,new WebConfigEntry(
- "add[@name='PropertyInjection']"
- ,"configuration/system.web/httpModules"
- ,"<add name=\"PropertyInjection\" type=\"" + typeof(PropertyInjectionModule).AssemblyQualifiedName + "\" />"
- ,SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode
- ,false)
- ,new WebConfigEntry(
- "add[@name='AttributeInjection']"
- ,"configuration/system.web/httpModules"
- ,"<add name=\"AttributeInjection\" type=\"" + typeof(AttributedInjectionModule).AssemblyQualifiedName + "\" />"
- ,SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode
- ,false)
- // autofac iis7 modules entries
- ,new WebConfigEntry(
- "add[@name='ContainerDisposal']"
- ,"configuration/system.webServer/modules"
- ,"<add name=\"ContainerDisposal\" type=\"" + typeof(ContainerDisposalModule).AssemblyQualifiedName + "\" preCondition=\"managedHandler\" />"
- ,SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode
- ,false)
- ,new WebConfigEntry(
- "add[@name='PropertyInjection']"
- ,"configuration/system.webServer/modules"
- ,"<add name=\"PropertyInjection\" type=\"" + typeof(PropertyInjectionModule).AssemblyQualifiedName + "\" preCondition=\"managedHandler\" />"
- ,SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode
- ,false)
- ,new WebConfigEntry(
- "add[@name='AttributeInjection']"
- ,"configuration/system.webServer/modules"
- ,"<add name=\"AttributeInjection\" type=\"" + typeof(AttributedInjectionModule).AssemblyQualifiedName + "\" preCondition=\"managedHandler\" />"
- ,SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode
- ,false)
- // autofac assemblies entries
- ,new WebConfigEntry(
- "add[@assembly='" + typeof(Autofac.IContainer).Assembly.FullName + "']"
- ,"configuration/system.web/compilation/assemblies"
- ,"<add assembly=\"" + typeof(Autofac.IContainer).Assembly.FullName + "\" />"
- ,SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode
- ,false)
- ,new WebConfigEntry(
- "add[@assembly='" + typeof(Autofac.Configuration.ComponentElement).Assembly.FullName + "']"
- ,"configuration/system.web/compilation/assemblies"
- ,"<add assembly=\"" + typeof(Autofac.Configuration.ComponentElement).Assembly.FullName + "\" />"
- ,SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode
- ,false)
- ,new WebConfigEntry(
- "add[@assembly='" + typeof(IContainerProviderAccessor).Assembly.FullName + "']"
- ,"configuration/system.web/compilation/assemblies"
- ,"<add assembly=\"" + typeof(IContainerProviderAccessor).Assembly.FullName + "\" />"
- ,SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode
- ,false)
- // add some safe control entries as well (assuming you'll be building some controls in Autofac.Integration.SharePoint)
- ,new WebConfigEntry(
- "SafeControl[@Assembly='" + typeof(Autofac.Integration.SharePoint.GlobalApplication).Assembly.FullName + "']"
- ,"configuration/SharePoint/SafeControls"
- ,"<SafeControl Assembly=\"" + typeof(Autofac.Integration.SharePoint.GlobalApplication).Assembly.FullName + "\" Namespace=\"Autofac.Integration.SharePoint\" TypeName=\"*\" Safe=\"True\" SafeAgainstScript=\"False\" />"
- ,SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode
- ,false)
- };
- /// <summary>
- /// Container to hold info about our modifications to the web.config.
- /// </summary>
- public class WebConfigEntry
- {
- public string Name;
- public string XPath;
- public string Value;
- public SPWebConfigModification.SPWebConfigModificationType ModificationType;
- public bool KeepOnDeactivate;
- public WebConfigEntry(string name, string xPath, string value,
- SPWebConfigModification.SPWebConfigModificationType modificationType, bool keepOnDeactivate)
- {
- Name = name;
- XPath = xPath;
- Value = value;
- ModificationType = modificationType;
- KeepOnDeactivate = keepOnDeactivate;
- }
- public SPWebConfigModification Prepare()
- {
- var modification = new SPWebConfigModification(Name, XPath)
- {
- Owner = ConfigModsOwnerName,
- Sequence = 0,
- Type = ModificationType,
- Value = Value
- };
- return modification;
- }
- }
- #endregion
- }
After the event receiver installs Autofac … you’ll see the following summarized list of changes to the root web.config files on all front-end web applications:
- <?xml version="1.0" encoding="utf-8"?>
- <configuration>
- <configSections>
- <section name="autofac" type="Autofac.Configuration.SectionHandler, Autofac.Configuration, Version=2.5.2.830, Culture=neutral, PublicKeyToken=17863af14b0044da" />
- </configSections>
- <SharePoint>
- <SafeControls>
- <SafeControl Assembly="Autofac.Integration.SharePoint, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3a3b1e988e6ba16f" Namespace="Autofac.Integration.SharePoint" TypeName="*" Safe="True" SafeAgainstScript="False" />
- </SafeControls>
- </SharePoint>
- <system.web>
- <httpModules>
- <add name="AttributeInjection" type="Autofac.Integration.Web.Forms.AttributedInjectionModule, Autofac.Integration.Web, Version=2.5.2.830, Culture=neutral, PublicKeyToken=17863af14b0044da" />
- <add name="ContainerDisposal" type="Autofac.Integration.Web.ContainerDisposalModule, Autofac.Integration.Web, Version=2.5.2.830, Culture=neutral, PublicKeyToken=17863af14b0044da" />
- <add name="PropertyInjection" type="Autofac.Integration.Web.Forms.PropertyInjectionModule, Autofac.Integration.Web, Version=2.5.2.830, Culture=neutral, PublicKeyToken=17863af14b0044da" />
- </httpModules>
- <compilation batch="true" debug="true" optimizeCompilations="true">
- <assemblies>
- <add assembly="Autofac, Version=2.5.2.830, Culture=neutral, PublicKeyToken=17863af14b0044da" />
- <add assembly="Autofac.Configuration, Version=2.5.2.830, Culture=neutral, PublicKeyToken=17863af14b0044da" />
- <add assembly="Autofac.Integration.Web, Version=2.5.2.830, Culture=neutral, PublicKeyToken=17863af14b0044da" />
- </assemblies>
- </compilation>
- </system.web>
- <system.webServer>
- <modules runAllManagedModulesForAllRequests="true">
- <add name="AttributeInjection" type="Autofac.Integration.Web.Forms.AttributedInjectionModule, Autofac.Integration.Web, Version=2.5.2.830, Culture=neutral, PublicKeyToken=17863af14b0044da" preCondition="managedHandler" />
- <add name="ContainerDisposal" type="Autofac.Integration.Web.ContainerDisposalModule, Autofac.Integration.Web, Version=2.5.2.830, Culture=neutral, PublicKeyToken=17863af14b0044da" preCondition="managedHandler" />
- <add name="PropertyInjection" type="Autofac.Integration.Web.Forms.PropertyInjectionModule, Autofac.Integration.Web, Version=2.5.2.830, Culture=neutral, PublicKeyToken=17863af14b0044da" preCondition="managedHandler" />
- </modules>
- </system.webServer>
- <autofac>
- <modules>
- <module type="Autofac.Integration.SharePoint.Playground.PlaygroundModule, Autofac.Integration.SharePoint, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3a3b1e988e6ba16f" />
- </modules>
- </autofac>
- </configuration>
You’ll notice that this implementation has the “modules” functionality already wired-up at the very end of the web.config file to add new modules (see here for more details). But this isn’t the only way you can add new modules. In the “Autofac Integration Code” above, I’ve added the ability to store module configuration in SharePoint (using a Farm property) that also gets added to the modules wired up during Application_Start.
The functions that developers can use in their feature event receivers to register (and unregister) Autofac modules into SharePoint are in the SPContainerBuilder class in the download, and have the following signatures:
public static void RegisterModule(string moduleType, IDictionary<string, string> properties, IDictionary<string, string> parameters, bool replaceIfExists){ … } public static void RemoveModule(string moduleType){ … }
Developers integrating new features into SharePoint can leverage these two functions in their feature event receivers to add new Autofac mappings without modifying the web.config files going-forward.
By modifying the global.asax file, Autofac fires during Application startup and calls an initialize method on a static service locator class. The initialization of the Autofac container looks like the following.
- /// <summary>
- /// This method is available for testing, or to use the ServiceLocator capability outside of
- /// the SharePoint HttpContext (i.e.: console app, linqpad, powershell, etc...)
- /// </summary>
- /// <param name="configFile">Autofac configuration file</param>
- /// <param name="additionalBuild">Any additional build information desired (will override the web.config and farm properties)</param>
- public static void InitializeContainer(string configFile, Action<ContainerBuilder> additionalBuild)
- {
- if (_initialized)
- return;
- if (!string.IsNullOrEmpty(configFile) && !File.Exists(configFile))
- throw new FileNotFoundException("The autofac configuration file could not be found.", configFile);
- var builder = new SPContainerBuilder();
- // register modules from config file
- builder.RegisterModule(string.IsNullOrEmpty(configFile)
- ? new ConfigurationSettingsReader("autofac")
- : new ConfigurationSettingsReader("autofac", configFile));
- // register modules from SPFarm.Properties
- var modules = SPContainerBuilder.GetFarmPropertyConfiguredModules();
- foreach (var module in modules)
- builder.RegisterModule(module);
- if (additionalBuild != null)
- additionalBuild.Invoke(builder);
- _containerProvider = new SPContainerProvider(builder.Build());
- _initialized = true;
- }
Using Autofac within Application Pages, Web Parts, User Controls, Event Receivers
Application pages and user controls can use the standard injection techniques that come with Autofac with ASP.NET (in the Autofac.Integration.Web assembly).
- [InjectProperties]
- public partial class Play : LayoutsPageBase, IPlaygroundView
- {
- private PlaygroundPresenter _presenter;
- public PlaygroundPresenterFactory PresenterFactory { get; set; }
- // these are here just to test property injection
- public ILogger Logger { get; set; }
- public IPlayInterface PlayInterface { get; set; }
- protected void Page_Load(object sender, EventArgs e)
- {
- _presenter = PresenterFactory(this);
- }
In SharePoint, there are various scenarios that need to be supported. The following is a non-comprehensive list, along with various usages that support each.
- protected override void Load(ContainerBuilder builder)
- {
- // presenters and views
- //builder.RegisterType<UlsLogger>().AsImplementedInterfaces().SingleInstance(); // logger singleton
- builder.RegisterType<UlsLogger>().As<ILogger>().WithParameter("categoryName", "Autofac Logger").SingleInstance(); // logger singleton
- builder.RegisterType<PlaygroundPresenter>().AsSelf().InstancePerDependency();
- builder.RegisterGeneratedFactory<PlaygroundPresenterFactory>();
- }
- protected override void Load(ContainerBuilder builder)
- {
- // InstancePerLifetimeScope implementation
- builder.RegisterType<PlayInterface>().As<IPlayInterface>().InstancePerLifetimeScope();
- }
- // old-school detection:
- var cpa = (IContainerProviderAccessor) HttpContext.Current.ApplicationInstance;
- IContainerProvider cp = cpa.ContainerProvider;
- _logger = cp.RequestLifetime.Resolve<ILogger>();
- // or, an easier way:
- this._logger = SPServiceLocator.GetRequestLifetime().Resolve<ILogger>();
- SPServiceLocator.GetRequestLifetime().InjectProperties(this);
- SPSecurity.RunWithElevatedPrivileges(delegate {
- AnotherLogger = SPServiceLocator.GetRequestLifetime().Resolve<ILogger>();
- if (AnotherLogger == null)
- {
- _view.ErrorMessage = "Elevated privileges was NOT successful";
- } else {
- _view.SuccessMessage = "Elevated privileges was successful";
- AnotherLogger.Log(typeof (PlaygroundPresenter), _view.SuccessMessage);
- }
- });
- HttpContext context = HttpContext.Current;
- HttpContext.Current = null;
- try
- {
- using (ILifetimeScope container = SPServiceLocator.NewDisposableLifetime())
- {
- var logger = container.Resolve<ILogger>();
- ...
- }
- }
- catch (Exception ex)
- {
- _view.ErrorMessage = ex.Message;
- }
- HttpContext.Current = context;
- public void TestInBackgroundThread()
- {
- // page must be async for the following (which still hangs the page)
- var bw = new BackgroundWorker {WorkerSupportsCancellation = false, WorkerReportsProgress = true};
- bw.DoWork += BwDoWork;
- bw.ProgressChanged += BwProgressChanged;
- bw.RunWorkerCompleted += BwRunWorkerCompleted;
- bw.RunWorkerAsync();
- }
- private static void BwDoWork(object sender, DoWorkEventArgs e)
- {
- ILifetimeScope container = null;
- try
- {
- container = SPServiceLocator.NewDisposableLifetime("worker");
- var bwLogger = container.Resolve<ILogger>();
- var worker = sender as BackgroundWorker;
- for (int i = 1; (i <= 10); i++)
- {
- // Perform a time consuming operation and report progress.
- Thread.Sleep(500);
- if (worker != null)
- worker.ReportProgress((i * 10));
- bwLogger.Log(typeof(PlaygroundPresenter), string.Format("bw worker progress: {0}%", (i * 10)));
- // watch this in ULS
- }
- e.Result = "Super, background worker has completed";
- }
- finally
- {
- if (container != null)
- container.Dispose();
- }
- }
Using Autofac on the Server
You can reference and override the registrations from a console app, powershell, and/or Linqpad, and as long as you are on the server, access service implementations.
General Comments
If you’re interested in this code, go ahead and download the sample attached to this post. I’ve included the feature and feature event receiver project, and a library project which has SPContainerBuilder, SPServiceLocator, SPContainerProvider classes and more that help with managing Autofac capabilities.
BTW: I coded all this in 1 day, as a prototype, so I can’t promise it’s 100% full-proof, but it does work in my use-cases. Do download the code and play with it, and if you decide to improve this, let me know. Or take it into a new direction all-together, that’s fine too .
Thanks for reading as always.
[…] ways, you could explore to use Autofac with SharePoint: • Deployment with a farm feature, see this site for more details. • Add a global.asax file to your SP solution. This is the asp.net approach, not […]
[…] that integrated Autofac into his SharePoint 2010 environment. I really want to encourage you to read his post, as he gives you a good insight in the pains that led him to using autofac, and also contains links […]
does autofac injects in OWSTIMER ?
Autofac wouldn’t inject in code executed within the OWSTIMER. You would need to initialize an ILifetimeScope, and use the service locator technique ( .Resolve(…) ) for that.
[…] Using Autofac in SharePoint 2010 (Matt Cowan) […]