ASP.NET Core 6.0 - Analytics Schema and Integration
This article describes the ASP.NET Core 6.0 - Homegrown Analytics Project V2 integration with existing projects and the SQL scripts for schema and tables. I will assume you have downloaded the ASP.NET Core 6.0 - Homegrown Analytics Project.
Homegrown Analytics Project and Article Series
The Homegrown Analytics Project (HAP) is designed and organized to allow easy integration with existing projects. V2 simplifies the process with an Analytics area folder. The analytics schema can be implemented with SQL scripts or EF Core migration. The HAP details page includes the version change log.
ASP.NET Core 6.0
- ASP.NET Core 6.0 - Homegrown Analytics
- ASP.NET Core 6.0 - Analytics Schema and Integration
- ASP.NET Core 6.0 - Data Collection
- ASP.NET Core 6.0 - Data Filters
- ASP.NET Core 6.0 - Data Visualization
- ASP.NET Core 6.0 - World Heat Map
- ASP.NET Core 6.0 - Homegrown TagHelpers
- ASP.NET Core 6.0 - Client End Of Day
ASP.NET Core 5.0
- ASP.NET Core 5.0 - Homegrown Analytics
- ASP.NET Core 5.0 - Analytics Schema and Integration
- ASP.NET Core 5.0 - Cookie Consent and GDPR
- ASP.NET Core 5.0 - Analytic Data Collection
- ASP.NET Core 5.0 - Not Found Errors
- ASP.NET Core 5.0 - Preferred UserAgents
- ASP.NET Core 5.0 - Analytic Dashboard
- ASP.NET Core 5.0 - Online User Count with SignalR
- ASP.NET Core 5.0 - Is Human with Google reCAPTCHA
- ASP.NET Core 5.0 - Log Maintenance with Hangfire
Namespaces and Folder Layout
After implementing and updating analytics with my live sites multiple times, I have refactored the HAP V2 with an updated analytics schema and UI. The integration process is simplified with an Analytics area folder. The namespaces match the folder layout.
Copy or merge the required files and update namespaces.
wwwroot Folder
The analytics.css should be merged with your site.css and analytics.js should be merged with your site.js. The images/ analytics/ worldKashmirLow.svg and dynamic-modals.js are required. You can copy or merge libman.json to implement the required client libraries. The analytics.js includes code for the SignalR online user count. I address integrating Hubs and SignalR in the ASP.NET Core 5.0 - Online User Count with SignalR article.
Areas Folder and Analytics Settings
Copy the entire Analytics folder to the Areas project folder. See MSDocs - Areas in ASP.NET Core - Areas with Razor Pages. Copy AnalyticsSettings.cs to the project's root folder. This allows easy updates to the analytics UI while maintaining your custom settings. Scope Find and Replace to Current Project and replace HomegrownAnalyticsNet6 with the existing project's name. Include migrations and cookie names with the replace.
Install NuGet Packages
Microsoft. EntityFrameworkCore. SqlServer is required. Microsoft. EntityFrameworkCore. Tools is required for migrations. MaxMind.Db is required to lookup country info from an ip address. The Hangfire packages are only required when you implement the Hangfire service. The HAP implements the KenHaggerty. Com. SingleUser. Net6 NuGet package which provides log in and log out pages for a single user to access the Admin pages.
Connection String
If you use an existing connection string, you need to add the analytics schema to your existing database. You can implement a separate database by setting a new connection string.
appsettings.json:
"ConnectionStrings": { "AnalyticsConnection": "Server=(localdb)\\mssqllocaldb;Database=HomegrownAnalyticsNet6;Trusted_Connection=True;MultipleActiveResultSets=true;" }
Implement services and middleware.
Code Samples
Implement the AnalyticsDbContext. Replace the analyticsConnection with the DefaultConnection to add the Analytics Schema to your existing database. After the AnalyticsDbContext is configured, you can use the Package Manager Console to to execute "update-database -Context AnalyticsDbContext" to apply the analytics schema migration.
Program.cs:
var analyticsConnection = builder.Configuration.GetConnectionString("AnalyticsConnection"); builder.Services.AddDbContext<AnalyticsDbContext>(options => options.UseSqlServer(analyticsConnection));
Startup.cs > ConfigureServices:
var analyticsConnection = Configuration.GetConnectionString("AnalyticsConnection"); services.AddDbContext<AnalyticsDbContext>(options => options.UseSqlServer(analyticsConnection));
Implement CheckConsentNeeded from Microsoft. AspNetCore. CookiePolicy to request user consent for non-essential cookies.
Program.cs:
if (AppSettings.EnableCookieConsent) builder.Services.Configure<CookiePolicyOptions>(options => { // Sets the display of the Cookie Consent banner (/Pages/Shared/_CookieConsentPartial.cshtml). // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.Strict; });
Startup.cs > ConfigureServices:
if (AppSettings.EnableCookieConsent) services.Configure<CookiePolicyOptions>(options => { // Sets the display of the Cookie Consent banner (/Pages/Shared/_CookieConsentPartial.cshtml). // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.Strict; });
Add the AnalyticsService and EnsureAnalyticsDefaultData HostedService to the services collection.
Program.cs:
if (AnalyticsSettings.EnableAnalytics) { builder.Services.AddTransient<IAnalyticsService, AnalyticsService>(); builder.Services.AddHostedService<EnsureAnalyticsDefaultData>(); }
Startup.cs > ConfigureServices:
if (AnalyticsSettings.EnableAnalytics) { services.AddTransient<IAnalyticsService, AnalyticsService>(); services.AddHostedService<EnsureAnalyticsDefaultData>(); }
Users should at least be authenticated to access the Areas/ Analytics folder. The AuthorizeAreaFolder method accepts an authorization policy for the third parameter. The Analytics UI expects proper naming convention for Json serialization.
Program.cs:
builder.Services.AddRazorPages(options => { ... options.Conventions.AuthorizeAreaFolder("Analytics", "/"); }) .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = null; });
Startup.cs > ConfigureServices:
services.AddRazorPages(options => { ... options.Conventions.AuthorizeAreaFolder("Analytics", "/"); }) .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = null; });
Add the UseCookiePolicy extension to apply the CheckConsentNeeded option to the pipeline. Add the UseAnalytics extension after UseAuthentication to apply the AnalyticsMiddleware to the pipeline.
Program.cs or Startup.cs > Configure:
app.UseCookiePolicy(); app.UseAuthentication(); if (AnalyticsSettings.EnableAnalytics) app.UseAnalytics();
The HAP includes a _CookieConsentPartial.cshtml in the Areas/ Analytics/ Pages/ Shared folder.
_CookieConsentPartial.cshtml:
@using Microsoft.AspNetCore.Http.Features; @{ var consentFeature = Context.Features.Get<ITrackingConsentFeature>(); var showBanner = !consentFeature?.CanTrack ?? false; var cookieString = consentFeature?.CreateConsentCookie(); } @if (showBanner) { <div id="CookieConsent" class="alert alert-info fade show border border-primary" role="alert"> Consent is required for the non-essential ScreenSize and TimeOffset cookies used with Analytics. <a class="d-inline-flex" asp-page="/Privacy">Privacy Policy</a> <button type="button" class="btn btn-primary d-block mt-1 mx-auto" data-bs-dismiss="alert" aria-label="Close" data-cookie-string="@cookieString"> <span aria-hidden="true">Accept</span> </button> </div> <script> // Restricts the use of non-essential cookies. var mustAcceptCookies = true; (function () { var button = document.querySelector("#CookieConsent button[data-cookie-string]"); button.addEventListener("click", function (event) { document.cookie = button.dataset.cookieString; mustAcceptCookies = false; if(location.pathname.toLowerCase() == '/privacy'){ location.reload(); return false; } }, false); })(); </script> }
The partial sets a mustAcceptCookies JavaScript global variable which is referenced from site.js to enable the TimeOffset and ScreenSize cookies.
document.addEventListener('DOMContentLoaded', function () { // !window.mustAcceptCookies indicates it is not declared by _CookieConsentPartial.cshtml, or is false. if (!window.mustAcceptCookies) { // client timezone if (getCookie('HomegrownAnalyticsNet6.TimeOffset') === '') { setCookie('HomegrownAnalyticsNet6.TimeOffset', new Date().getTimezoneOffset(), 1, '/', 'strict'); } // client screen width and height if (getCookie('HomegrownAnalyticsNet6.ScreenSize') === '') { setCookie('HomegrownAnalyticsNet6.ScreenSize', window.screen.availWidth.toString() + 'X' + window.screen.availHeight.toString(), 365, '/', 'strict'); } } });
The Areas/ Analytics/ Pages/ Shared folder is not automatically searched. See MSDocs - Partial views in ASP.NET Core - Partial view discovery. Add a partial tag to the _Layout.cshtml after the RenderBody() main. Notice the cshtml extension when the path is used. Copy _CookieConsentPartial.cshtml to the project's Pages/ Shared folder to use the short name, _CookieConsentPartial, for the name attribute.
_Layout.cshtml:
<div class="container"> <main role="main" class="pb-3"> @RenderBody() </main> <partial name="/Areas/Analytics/Pages/Shared/_CookieConsentPartial.cshtml" /> </div>
The HAP includes a sample privacy policy page with a revoke cookie consent feature.
Create the analytics schema and tables.
After the AnalyticsDbContext is configured, you can use the Package Manager Console to to execute "update-database -Context AnalyticsDbContext" to apply the analytics schema migration. The CreateDatabaseAndAnalyticsSchema.sql and CreateAnalyticsSchema.sql scripts are located in the Areas/ Analytics/ Data/ SqlScripts folder. The PurgeExpiredAnalytics script deletes expired related records. The RemoveAnalyticsSchema will remove all analytics and Hangfire data and schema. The EnsureAnalyticsDefaultData HostedService sets the AnalyticsSchema version and creates the required default UserAgent and Session.
Optional Features
The AnalyticsSettings class has static properties for options. I address integrating the options in their related articles.
Comments(0)