2

I have an ASP.NET Core app with various controllers that inherit from BaseController. I need to implement some basic benchmarking tests, using Stopwatch, it will just start at the beginning of an action method and finish at the end. I can turn this on and off via appsettings.json. There is an ILogger factory in Startup.cs:

public void Configure ( IApplicationBuilder app, IHostingEnvironment env,     ILoggerFactory loggerFactory ) {
        loggerFactory.AddConsole( Configuration.GetSection( "Logging" ) );
        loggerFactory.AddDebug();
        loggerFactory.AddFile(@"C:\Logs\Portal\portal-{Date}.txt"); 

I have added ILogger to my BaseController (below), I am hoping this will be supplied via DI. Given the above, can I use this to log my benchmark results to file in a different location to the startup file? I would like a .csv file with certain columns which i can populate with results. Is this possible?

public class BaseController : Controller {
    protected AppSettings AppSettings;
    protected IMapper Mapper;
    protected IPortalApiService PortalApiService;
    protected ILogger Logger;
    protected UserManager<ApplicationUser> UserManager;
    private static Stopwatch _stopWatch = new Stopwatch();
    private static long _seconds;

    public BaseController ( IMapper mapper,
                            IOptions<AppSettings> appSettings,
                            UserManager<ApplicationUser> userManager,
                            IPortalApiService PortalApiService,
                            ILogger logger) {
        Mapper = mapper;
        AppSettings = appSettings.Value;
        UserManager = userManager;
        PortalApiService = PortalApiService;
        Logger = logger;
    }

    public BaseController ( IMapper mapper,
                            IOptions<AppSettings> appSettings,
                            UserManager<ApplicationUser> userManager,
                            ILogger logger) {
        Mapper = mapper;
        AppSettings = appSettings.Value;
        UserManager = userManager;
        Logger = logger;
    }

    protected Task<ApplicationUser> GetCurrentUserAsync () {
        return UserManager.GetUserAsync( HttpContext.User );
    }

    public void StartBenchmark()
    {

        if (AppSettings.EnableBenchmarkLogging)
        {
            _stopWatch = Stopwatch.StartNew();
        }

    }

    public void EndBenchmark()
    {
        if (_stopWatch.IsRunning)
        {
            _stopWatch.Stop();
            _seconds = _stopWatch.ElapsedMilliseconds;
            //logging to do
        }
    }
}
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
WhatTheDeuce
  • 143
  • 1
  • 9

1 Answers1

3

It is not a good idea to use a BaseController in MVC. There are better ways to implement crosscutting concerns. In this particular case, you could use a global filter.

public class BenchmarkFilter : IActionFilter
{
    private readonly ILogger Logger;

    // DON'T DECLARE STATIC!! 
    private Stopwatch _stopWatch = new Stopwatch();

    public BenchmarkFilter(ILogger logger)
    {
        _logger = logger ?? 
            throw new ArgumentNullException(nameof(logger));
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        _stopWatch = Stopwatch.StartNew();
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        if (_stopWatch.IsRunning)
        {
            _stopWatch.Stop();
            var seconds = _stopWatch.ElapsedMilliseconds;
            //logging to do
        }
    }
}

This allows you to inject services via DI through the constructor without having to add those parameters to every controller that subclasses a common BaseController, separating the concern of benchmarking from the controller entirely.

Usage

In Startup.cs, add the filter in the ConfigureServices method.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Filters.Add(typeof(BenchmarkFilter)); // runs on every action method call
    });

    services.AddScoped<BenchmarkFilter>();
    // ....
}
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • Okay i like the look of this. Is there a way to toggle this on and off via appsettings? I guess i would then be able to access what controller + action was run for logging purposes via the context? – WhatTheDeuce Feb 21 '18 at 21:37
  • No problem. You can use the [options pattern](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options) to inject configuration settings to the constructor via DI, such as an on/off setting and the filename of a `.csv` file to write to. And yes, the context contains the controller and action names being run - see [this question](https://stackoverflow.com/questions/41103694/get-controller-and-action-name-from-authorizationhandlercontext-object). – NightOwl888 Feb 21 '18 at 21:43