If all aspect instances use the same global dependency injection container, the dependencies of all instances are likely to resolve to the same service implementation. Therefore, storing dependencies in an instance field may waste memory, especially for aspects that are applied to a very high number of code elements.
Alternatively, you can store dependencies in static fields and initialize them in the static constructor of the aspect.
Tip
Use the IsPostSharpRunning property to make sure that this part of the static constructor is executed at run time only, when PostSharp is not running.
In this case, you cannot use dependency injection methods such as SatisfyImportsOnce(ComposablePart). Instead, you must use the container as a service locator. For instance, MEF exposes the method GetExport.
Important
The service locator must be initialized before the execution of any class that is enhanced by the aspect. Therefore, you cannot use the aspect on the entry-point class (typically Program or App). To relax this constraint, you can initialize the dependency on demand, for instance by using Lazy<T>.
Example: testable aspect with a global MEF service locator
The following code snippet shows a logging aspect and how it could be used in production code:
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using PostSharp.Aspects;
using PostSharp.Extensibility;
using PostSharp.Serialization;
namespace DependencyResolution.GlobalServiceLocator
{
public interface ILogger
{
void Log(string message);
}
public static class AspectServiceLocator
{
private static CompositionContainer container;
public static void Initialize(ComposablePartCatalog catalog)
{
container = new CompositionContainer(catalog);
}
public static Lazy<T> GetService<T>() where T : class
{
return new Lazy<T>(GetServiceImpl<T>);
}
private static T GetServiceImpl<T>()
{
if (container == null)
throw new InvalidOperationException();
return container.GetExport<T>().Value;
}
}
[PSerializable]
public class LogAspect : OnMethodBoundaryAspect
{
private static readonly Lazy<ILogger> logger;
static LogAspect()
{
if (!PostSharpEnvironment.IsPostSharpRunning)
{
logger = AspectServiceLocator.GetService<ILogger>();
}
}
public override void OnEntry(MethodExecutionArgs args)
{
logger.Value.Log("OnEntry");
}
}
internal class Program
{
private static void Main(string[] args)
{
AspectServiceLocator.Initialize(new TypeCatalog(typeof (ConsoleLogger)));
LoggedMethod();
}
[LogAspect]
public static void LoggedMethod()
{
Console.WriteLine("Hello, world.");
}
}
[Export(typeof (ILogger))]
internal class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
}
The following code snippet shows how the logging aspect can be tested:
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace DependencyResolution.GlobalServiceLocator.Test
{
[TestClass]
public class TestLogAspect
{
static TestLogAspect()
{
AspectServiceLocator.Initialize(new TypeCatalog(typeof (TestLogger)));
}
[TestMethod]
public void TestMethod()
{
TestLogger.Clear();
TargetMethod();
Assert.AreEqual("OnEntry" + Environment.NewLine, TestLogger.GetLog());
}
[LogAspect]
private void TargetMethod()
{
}
}
[Export(typeof (ILogger))]
internal class TestLogger : ILogger
{
public static readonly StringBuilder stringBuilder = new StringBuilder();
public void Log(string message)
{
stringBuilder.AppendLine(message);
}
public static string GetLog()
{
return stringBuilder.ToString();
}
public static void Clear()
{
stringBuilder.Clear();
}
}
}