Log Navigation fire&forget con Attribute

In questo post vedremo come realizzare un sistema per effettuare log della navigazione in modalità fire&forget, cioè facendo in modo che venga dato il comando di scrittura del log e che l'esecuzione della chiamata prosegua senza occuparsi dei tempi e dell'esito di tale scrittura.
Questa soluzione può quindi essere adottata solo in scenari in cui il risultato della chiamata non dipenda dal successo o meno del salvataggio del log.

Supponiamo quindi di avere un frontend utilizzato dagli utenti e delle Web API da chiamare per effettuare il log.
Per poter gestire al meglio ogni chiamata, creiamo un attributo per decorare le Action e/o i Controller che ci interessano:

             
public class NavigationLogAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            Task task = Task.Run(() => ExecuteTaskAsync(() => LogNavigation(filterContext.HttpContext.Request)));
        }

        /// Wrapper for async execution and log error
        private async Task ExecuteTaskAsync(Func code)
        {
            try
            {
                await code();
            }
            catch (Exception exception)
            {
                //do something to log error 
            }
        }

        public async Task LogNavigation(HttpRequestBase request)
        {
            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri(ConfigurationManager.AppSettings["MyStatisticsAPIUrl"]);

                var browser = request.Browser;

                var stat = new MySite.Models.NavigationLog();

                stat.Agent = request.UserAgent;
                stat.Browser = browser.Browser;
                stat.Platform = browser.Platform;
                stat.EcmaScriptVersion = browser.EcmaScriptVersion.ToString();
                stat.ActiveXControls = browser.ActiveXControls;
                stat.Beta = browser.Beta;
                stat.Cookies = browser.Cookies;
                stat.Frames = browser.Frames;
                stat.IsMobileDevice = browser.IsMobileDevice;
                stat.JavaApplets = browser.JavaApplets;
                stat.SupportsCallback = browser.SupportsCallback;
                stat.SupportsCss = browser.SupportsCss;
                stat.SupportsXmlHttp = browser.SupportsXmlHttp;
                stat.Tables = browser.Tables;
                stat.VBScript = browser.VBScript;
                stat.Win16 = browser.Win16;
                stat.Win32 = browser.Win32;

                var jsonNavigationLog = JsonConvert.SerializeObject(stat);
                var postContent = new StringContent(jsonNavigationLog);

                var response = await client.PostAsync("api/Statistics/AddUsageStats", postContent);

                if (!response.IsSuccessStatusCode)
                {
                    //do something to log error
                }
            }
        }
    }
    
Adesso possiamo utilizzare il nostro attributo per decorare i punti che ci interessano. Per esempio se vogliamo loggare le chiamate a Product/Index:
public class ProductController : Controller
{
    [NavigationLogAttribute]
    public ActionResult Index()
    {
        return View();
    }
}

Volendo invece loggare ogni chiamata, possiamo creare un Controller base da cui far ereditare tutti quelli di cui ci interessa tracciare l'utilizzo:

[NavigationLogAttribute]
public class BaseLogController : Controller
{
}

public class ProductController : BaseLogController
{
    public ActionResult Index()
    {
        return View();
    }
}