← Back to list

AOP — .NET — PostSharp Aspect: Leveraging Methods Defined in Class with Parameterized Constructor

Hi Everyone,

Rahul Ranjan · 2024-04-07 15:56 · 1 claps · 3.6 min read
#postsharp #aop #dotnet-core
Open on Medium ↗
Wiki topics: RAG · RAG & Retrieval CRY · Crypto & Web3

AOP — .NET — PostSharp Aspect: Leveraging Methods Defined in Class with Parameterized Constructor

Hi Everyone,

Aspect Oriented Programming as you all must be aware of, is a programing paradigm which typically addresses cross cutting concerns by adding additional behavior (an aspect/advice) to existing code without modifying the code itself. We can use that aspect separately by decorating the needed method via a kind of a “pointcut” specification such as an attribute.

A typical cross cutting concern could be a scenario where we intent to log the results and few metrics characterizing the processer business logic performance on some dashboard like Grafana before returning processed results to the user. Another scenario could be like “logging all function calls when the function’s name begins with ‘set’”

Firstly, it sad that .NET does not natively support any clean built-in support of writing ideal AOP at least till .NET 8. However, there can be ways in which it can be achieved for simple scenarios by using Interceptors and bit of business logic improvement. However, there are plenty of mature and powerful frameworks to do AOP in a cleaner way, one of those frameworks is PostSharp.

PostSharp is a freemium framework which provides PostSharp Essentials as its free edition. Although PostSharp Essentials provides two mostly used aspects namely, OnMethodBoundaryAspect & MethodInterceptionAspect aspects in its free tier, it has some real limitations which as inability to use it over async methods & its limitation to be applied over only 1000 lines of codes.

Now, if that's sorted out and you are really looking up to use PostSharp, there could be a scenario, where you have a class injecting several parameters through its constructor, and the class has the methods defined to log the results (typical cross cutting concern).

  [Export(typeof(IDashboardCollectionMetrics))]    
  public class DashboardCollectionMetricsProcess: BaseDALProcess, IDashboardCollectionMetrics
  {
      private readonly IInterface1 _interface1;
      private readonly IInterface2 _interface2;
      private readonly IInterface _interface3;
      private readonly ILogger _logger;
      private readonly IHostingEnvironment _hostingEnv;
      private readonly IHttpContextAccessor _httpContextAccessor;

      [ImportingConstructor]
      public MetricCollectionProcess(IProcessesFactory processFactory,
          ISomeDatasFactory dataFactory, ISomeClientsFactory clientFactory,ILogger logger
          )
          : base(processFactory, dataFactory, clientFactory)
      {
          _interface1= processFactory.GetObjectInstance<IInterface1>();
          _interface2= dataFactory.GetData<IInterface2>();
          _logger= logger;
          _hostingEnv = hostingEnv;
          _httpContextAccessor = httpContextAccessor;
      }

       public async Task SaveAllProcessMetricsAsync(ApiResponse respItems,int apiResponseTimeInSecs)
     { 
      List<Task> tasksSaveProcessMetrics = new();
      // logic to saving the response and response time to cloud + other spaces
     await Task.WhenAll(tasksSaveProcessMetrics).ConfigureAwait(false);

     }
}

Now, if you’ve decided to use PostSharp, you might encounter a scenario where you have a class that injects several parameters through its constructor, and the class has the methods defined to log the results (a typical cross-cutting concern).

In this scenario, you might want to use an Aspect attribute to consume the class method to log the results, which should run just before the processing method is ready to return the results to the controller. This is not possible in PostSharp’s constructor because attributes in general and aspects in particular can only have constant expressions in the constructor. However, there are workarounds available. One way to consume that class in Aspect is by instantiating the dependencies of the class using ServiceLocator.

So, steps to perform the use-case.

  1. Decorate the logging class with Export Attribute & the constructor with ImportingConstructoras shown above.
  2. Define a Service Locator class that explicitly mentions the parameters to be injected in the constructor of Logging class as properties, like below.
 public static class SaveProcessMetricsAspectServiceLocator
 { 
     private static CompositionContainer container;

     [Export]
     public static IProcessesFactory ProcessFactory { get; set; }

     [Export]
     public static ISomeDatasFactory DataFactory { get; set; }

     [Export]
     public static ISomeClientsFactory ClientFactory { get; set; }

     [Export]
     public static ILogger Logger { get; set; }

     public static void Initialize(ComposablePartCatalog catalog)
     {
         container = new CompositionContainer(catalog);

         container.ComposeExportedValue<IProcessesFactory>(ProcessFactory);
         container.ComposeExportedValue(DataFactory);
         container.ComposeExportedValue(ClientFactory); 
         container.ComposeExportedValue(Logger);
     }

    [Pure]
    public static Func<T> GetService<T>() where T : class
    {
        return GetServiceImpl<T>;
    }

    private static T GetServiceImpl<T>()
    {
        if (container is not null)
        {
            T serviceRef = container.GetExportedValue<T>();

            return serviceRef;
        }
        return default;
    }

}
  1. Instantiate the properties of the ServiceLocator in the StartUp.cs — Configure() method.

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, AppConfiguration apiConfiguration)
    {
        var metricSavingProcess= container.GetInstance<IDashboardCollectionMetrics>();               
        SaveProcessMetricsAspectServiceLocator.ProcessFactory = container.GetInstance<IProcessesFactory>();
        SaveProcessMetricsAspectServiceLocator.ClientFactory = container.GetInstance<ISomeClientsFactory>();
        SaveProcessMetricsAspectServiceLocator.DataFactory = container.GetInstance<ISomeDatasFactory>();             

        SaveProcessMetricsAspectServiceLocator.Initialize(new TypeCatalog(new List<Type>
        {
           metricSavingProcess.GetType()
        }));
    }
  1. Now, while writing the Aspect that will save metrics just before the Process Layer returns the result to the controller, without changing any existing code, we can consume MetricCollectionProcess class’s object directly as like below.
  [PSerializable]
  public sealed class SaveProcessMetricsAspect : OnMethodBoundaryAspect
  {
      private static readonly IDashboardCollectionMetrics _metricCollectionProcess;

      private static readonly IContextPropertiesAccessor _contextPropertiesAccessor;

      private static readonly HttpContext HttpContext;

      static SaveProcessMetricsAspect()
      {
          if (!PostSharpEnvironment.IsPostSharpRunning)
          {
              _metricCollectionProcess = SaveProcessMetricsAspectServiceLocator.GetService<IDashboardCollectionMetrics>().Invoke();

              _contextPropertiesAccessor= SaveProcessMetricsAspectServiceLocator.GetService<IContextPropertiesAccessor>().Invoke();
          }
      }

      public override void OnException(MethodExecutionArgs args)
      {
          throw new NotImplementedException();
      }

      public override void OnExit(MethodExecutionArgs args)
      {
          throw new NotImplementedException();
      }

      public async override void OnSuccess(MethodExecutionArgs args)
      {
          var metricsForProcessedAction = args.ReturnValue as dynamic;

          var user = args.Instance;

          if (metricsForExecutedAction is not null)
          {
              try
              {
                  CancellationTokenSource cts = new();
                  cts.CancelAfter(TimeSpan.FromMinutes(1));

                  object requestStartDateTime = null;
                  var currentRequestHttpContext = _contextPropertiesAccessor.GetDynamicHttpContext<HttpContext>();
                  currentRequestHttpContext?.Items.TryGetValue("EndpointRequestStartingTime", out requestStartDateTime);

                  await Task.Run(() => _metricCollectionProcess?.SaveAllProcessMetricsAsync( metricsForProcessedAction.Response, requestStartDateTime), cts.Token);
              }
              catch(Exception ex) 
              {
                 //log exception
              }
          }            
      }
  }
  1. To consume the aspect, you now only need to decorate the corresponding method with SaveProcessMetricsAspect as like below.
 [SaveProcessMetricsAspect]
 public async Task<ResponseWithMetrics<ChatCompletionsResponse>> PostEmployees(EmployeeDTO request)
 {
    // logic to process request body 
    // calls database 
    // prepares final response to be given to the controller
 }

Hope you have found the article useful. Happy Coding :)


메타데이터
post_id
07f16c0fef4c
slug
aop-net-postsharp-aspect-leveraging-methods-defined-in-class-with-parameterized-constructor-07f16c0fef4c
url
https://medium.com/@ambitiousrahul.r/aop-net-postsharp-aspect-leveraging-methods-defined-in-class-with-parameterized-constructor-07f16c0fef4c
canonical_url
https://medium.com/@ambitiousrahul.r/aop-net-postsharp-aspect-leveraging-methods-defined-in-class-with-parameterized-constructor-07f16c0fef4c
author_url
https://medium.com/@ambitiousrahul.r
status
ok
fetched_at
2026-08-06 06:41:47