Search code examples
asp.net-mvcloggingasp.net-coredependency-injectionbenchmarking

How can I log benchmark tests to CSV file in MVC controllers?


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
        }
    }
}

Solution

  • 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>();
        // ....
    }