├── .config └── dotnet-tools.json ├── .gitattributes ├── .gitignore ├── .nuke ├── Directory.Build.targets ├── GoogleAnalyticsTracker.AspNetCore ├── AspNetCoreTracker.cs ├── AspNetCoreTrackerEnvironment.cs ├── AspNetCoreTrackerExtensions.cs ├── CookieBasedAnalyticsSession.cs ├── GoogleAnalyticsTracker.AspNetCore.csproj ├── GoogleAnalyticsTracker.snk ├── GoogleAnalyticsTrackerExtensions.cs ├── GoogleAnalyticsTrackerMiddleware.cs ├── GoogleAnalyticsTrackerOptions.cs ├── TrackEventAttribute.cs ├── TrackPageViewAttribute.cs └── TrackRequests.cs ├── GoogleAnalyticsTracker.Core ├── AnalyticsSession.cs ├── DateTimeExtensions.cs ├── EnumExtensions.cs ├── GoogleAnalyticsEndpoints.cs ├── GoogleAnalyticsTracker.Core.csproj ├── GoogleAnalyticsTracker.snk ├── HttpWebRequestExtensions.cs ├── Interface │ ├── IAnalyticsSession.cs │ └── ITrackerEnvironment.cs ├── TrackerBase.cs ├── TrackerParameters │ ├── Beacon.cs │ ├── BeaconAttribute.cs │ ├── BeaconComparer.cs │ ├── BeaconList.cs │ ├── ContentExperiments.cs │ ├── CustomDimension.cs │ ├── ECommerceItem.cs │ ├── ECommerceTransaction.cs │ ├── EnhancedECommerceProduct.cs │ ├── EnhancedECommerceTransaction.cs │ ├── EventTracking.cs │ ├── ExceptionTracking.cs │ ├── GeneralParameters.cs │ ├── HitType.cs │ ├── Interface │ │ ├── IAppTrackingParameters.cs │ │ ├── IContentExperimentsParameters.cs │ │ ├── IContentInformationParameters.cs │ │ ├── ICustomDimension.cs │ │ ├── ICustomDimensionParameters.cs │ │ ├── ICustomMetricParameters.cs │ │ ├── IECommerceItemParameters.cs │ │ ├── IECommerceParameters.cs │ │ ├── IECommerceTransactionParameters.cs │ │ ├── IEnhancedECommerceProduct.cs │ │ ├── IEnhancedECommerceTransactionParameters.cs │ │ ├── IEventTrackingParameters.cs │ │ ├── IExceptionParameters.cs │ │ ├── IGeneralParameters.cs │ │ ├── IHitParameters.cs │ │ ├── IProvideBeaconParameters.cs │ │ ├── IProvideProductsParameters.cs │ │ ├── IRefundTrackingParameters.cs │ │ ├── ISessionParameters.cs │ │ ├── ISocialInteractionsParameters.cs │ │ ├── ISystemInfoParameters.cs │ │ ├── ITimingParameters.cs │ │ ├── ITrafficSourcesParameters.cs │ │ └── IUserParameters.cs │ ├── PageView.cs │ ├── ProductAction.cs │ ├── RefundTracking.cs │ ├── ScreenviewTracking.cs │ ├── SessionControl.cs │ ├── SocialInteractionsParameters.cs │ └── UserTimings.cs └── TrackingResult.cs ├── GoogleAnalyticsTracker.SampleWebApplication ├── Controllers │ └── HomeController.cs ├── GoogleAnalyticsTracker.SampleWebApplication.csproj ├── GoogleAnalyticsTracker.snk ├── Models │ └── ErrorViewModel.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Startup.cs ├── Views │ ├── Home │ │ ├── About.cshtml │ │ ├── Contact.cshtml │ │ ├── Index.cshtml │ │ └── Privacy.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ ├── _CookieConsentPartial.cshtml │ │ ├── _Layout.cshtml │ │ └── _ValidationScriptsPartial.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── appsettings.Development.json ├── appsettings.json └── wwwroot │ ├── css │ ├── site.css │ └── site.min.css │ ├── favicon.ico │ ├── images │ ├── banner1.svg │ ├── banner2.svg │ └── banner3.svg │ ├── js │ ├── site.js │ └── site.min.js │ └── lib │ ├── bootstrap │ ├── .bower.json │ ├── LICENSE │ └── dist │ │ ├── css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.css.map │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap-theme.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ │ └── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ └── npm.js │ ├── jquery-validation-unobtrusive │ ├── .bower.json │ ├── LICENSE.txt │ ├── jquery.validate.unobtrusive.js │ └── jquery.validate.unobtrusive.min.js │ ├── jquery-validation │ ├── .bower.json │ ├── LICENSE.md │ └── dist │ │ ├── additional-methods.js │ │ ├── additional-methods.min.js │ │ ├── jquery.validate.js │ │ └── jquery.validate.min.js │ └── jquery │ ├── .bower.json │ ├── LICENSE.txt │ └── dist │ ├── jquery.js │ ├── jquery.min.js │ └── jquery.min.map ├── GoogleAnalyticsTracker.Simple ├── EventViewTrackerExtensions.cs ├── GoogleAnalyticsTracker.Simple.csproj ├── GoogleAnalyticsTracker.snk ├── PageViewTrackerExtensions.cs ├── ScreenviewTrackerExtension.cs ├── SimpleTracker.cs └── SimpleTrackerEnvironment.cs ├── GoogleAnalyticsTracker.sln ├── GoogleAnalyticsTracker.sln.DotSettings ├── LICENSE.md ├── README.md ├── build.cmd ├── build.ps1 ├── build.sh ├── build ├── .editorconfig ├── Build.cs ├── _build.csproj └── _build.csproj.DotSettings └── global.json /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "nuke.globaltool": { 6 | "version": "5.3.0", 7 | "commands": [ 8 | "nuke" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /GoogleAnalyticsTracker.sln.DotSettings.user 2 | /GoogleAnalyticsTracker.sln.docstates.suo 3 | /GoogleAnalyticsTracker.suo 4 | /GoogleAnalyticsTracker/GoogleAnalyticsTracker.csproj.user 5 | /GoogleAnalyticsTracker.WindowsPhone.sln.DotSettings.user 6 | /GoogleAnalyticsTracker.WindowsPhone.sln.docstates.suo 7 | /GoogleAnalyticsTracker.WindowsPhone.suo 8 | /GoogleAnalyticsTracker.WindowsPhone/GoogleAnalyticsTracker.WindowsPhone.csproj.user 9 | /msbuild.log 10 | /_ReSharper.* 11 | /GoogleAnalyticsTracker/bin/* 12 | /GoogleAnalyticsTracker/obj/* 13 | /packages/* 14 | /Build/* 15 | obj/ 16 | bin/ 17 | *.suo 18 | *.user 19 | .idea/* 20 | .tmp/* 21 | artifacts/* -------------------------------------------------------------------------------- /.nuke: -------------------------------------------------------------------------------- 1 | GoogleAnalyticsTracker.sln -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 4 | enable 5 | 6 | 7 | True 8 | True 9 | true 10 | snupkg 11 | 12 | GoogleAnalyticsTracker.snk 13 | false 14 | 15 | GoogleAnalyticsTracker - A C# library for tracking Google Analytics. 16 | Maarten Balliauw and contributors 17 | Maarten Balliauw and contributors 18 | https://github.com/maartenba/GoogleAnalyticsTracker 19 | https://github.com/maartenba/GoogleAnalyticsTracker 20 | Git 21 | google analytics ga mvc api rest client tracker stats statistics track usage feature telemetry 22 | MS-PL 23 | 24 | true 25 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 26 | 27 | GoogleAnalyticsTracker 28 | 29 | 30 | 31 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.AspNetCore/AspNetCoreTracker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using GoogleAnalyticsTracker.Core; 4 | using GoogleAnalyticsTracker.Core.Interface; 5 | using GoogleAnalyticsTracker.Core.TrackerParameters; 6 | using JetBrains.Annotations; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.Options; 9 | 10 | namespace GoogleAnalyticsTracker.AspNet; 11 | 12 | [PublicAPI] 13 | public class AspNetCoreTracker : TrackerBase 14 | { 15 | private readonly IHttpContextAccessor _contextAccessor; 16 | 17 | private static IAnalyticsSession SetupAnalyticsSession( 18 | IHttpContextAccessor contextAccessor, 19 | IOptions optionsAccessor) 20 | { 21 | var session = new CookieBasedAnalyticsSession(contextAccessor); 22 | optionsAccessor.Value.CustomizeAnalyticsSession?.Invoke(session); 23 | return session; 24 | } 25 | 26 | private static ITrackerEnvironment SetupTrackerEnvironment( 27 | IOptions optionsAccessor) 28 | { 29 | var trackerEnvironment = new AspNetCoreTrackerEnvironment(); 30 | optionsAccessor.Value.CustomizeTrackerEnvironment?.Invoke(trackerEnvironment); 31 | return trackerEnvironment; 32 | } 33 | 34 | public AspNetCoreTracker( 35 | IHttpContextAccessor contextAccessor, 36 | IOptions optionsAccessor) 37 | : base( 38 | optionsAccessor.Value.TrackerId, 39 | SetupAnalyticsSession(contextAccessor, optionsAccessor), 40 | SetupTrackerEnvironment(optionsAccessor)) 41 | { 42 | _contextAccessor = contextAccessor; 43 | } 44 | 45 | [UsedImplicitly] 46 | public Task TrackEventAsync(Exception ex) 47 | { 48 | return TrackEventAsync("Errors", ex.Message, ex.HResult); 49 | } 50 | 51 | [UsedImplicitly] 52 | public async Task TrackEventAsync(string category, string action, int value) 53 | { 54 | // If no HTTP context available, bail out... 55 | if (_contextAccessor.HttpContext == null) return; 56 | 57 | var eventTrackingParameters = new EventTracking 58 | { 59 | Category = category, 60 | Action = action, 61 | Label = GetRelativeUrl(), 62 | Value = value, 63 | DocumentHostName = _contextAccessor.HttpContext.Request.Host.Value, 64 | UserAgent = _contextAccessor.HttpContext.Request.Headers["User-Agent"], 65 | UserLanguage = _contextAccessor.HttpContext.Request.Headers["Accept-Language"], 66 | DocumentReferrer = _contextAccessor.HttpContext.Request.Headers["Referrer"], 67 | IpOverride = Environment.GetEnvironmentVariable("server.RemoteIpAddress"), 68 | UserId = _contextAccessor.HttpContext.User.Identity?.Name 69 | }; 70 | 71 | await TrackAsync(eventTrackingParameters); 72 | } 73 | 74 | [UsedImplicitly] 75 | public Task TrackPageViewAsync() 76 | { 77 | return TrackPageViewAsync(null); 78 | } 79 | 80 | [UsedImplicitly] 81 | public async Task TrackPageViewAsync(string? customTitle) 82 | { 83 | // If no HTTP context available, bail out... 84 | if (_contextAccessor.HttpContext == null) return; 85 | 86 | var pageviewTrackingParameters = new PageView 87 | { 88 | DocumentTitle = customTitle ?? _contextAccessor.HttpContext.Request.Path.ToString(), 89 | DocumentLocationUrl = GetRelativeUrl(), 90 | DocumentHostName = _contextAccessor.HttpContext.Request.Host.Value, 91 | UserAgent = _contextAccessor.HttpContext.Request.Headers["User-Agent"], 92 | UserLanguage = _contextAccessor.HttpContext.Request.Headers["Accept-Language"], 93 | DocumentReferrer = _contextAccessor.HttpContext.Request.Headers["Referrer"], 94 | IpOverride = Environment.GetEnvironmentVariable("server.RemoteIpAddress"), 95 | UserId = _contextAccessor.HttpContext.User.Identity?.Name 96 | }; 97 | 98 | await TrackAsync(pageviewTrackingParameters); 99 | } 100 | 101 | private string GetRelativeUrl() 102 | { 103 | var requestPath = _contextAccessor.HttpContext?.Request.Path.ToString() ?? string.Empty; 104 | var requestQueryString = _contextAccessor.HttpContext?.Request.QueryString.ToString(); 105 | 106 | // ReSharper disable once UseStringInterpolation 107 | return string.IsNullOrEmpty(requestQueryString) 108 | ? requestPath 109 | : string.Format("{0}{1}", requestPath, requestQueryString); 110 | } 111 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.AspNetCore/AspNetCoreTrackerEnvironment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using GoogleAnalyticsTracker.Core.Interface; 4 | using JetBrains.Annotations; 5 | 6 | namespace GoogleAnalyticsTracker.AspNet; 7 | 8 | /// An ASP.NET Core tracker environment. 9 | [PublicAPI] 10 | public class AspNetCoreTrackerEnvironment : ITrackerEnvironment 11 | { 12 | /// 13 | /// Initializes a new instance of the AspNetTrackerEnvironment class. 14 | /// 15 | public AspNetCoreTrackerEnvironment() 16 | { 17 | Hostname = Dns.GetHostName(); 18 | OsPlatform = Environment.OSVersion.Platform.ToString(); 19 | OsVersion = Environment.OSVersion.Version.ToString(); 20 | OsVersionString = Environment.OSVersion.VersionString; 21 | } 22 | 23 | /// Gets or sets the hostname. 24 | /// The hostname. 25 | // ReSharper disable once MemberCanBePrivate.Global 26 | public string Hostname { get; set; } 27 | 28 | /// Gets or sets the operating system platform. 29 | /// The operating system platform. 30 | public string OsPlatform { get; set; } 31 | 32 | /// Gets or sets the operating system version. 33 | /// The operating system version. 34 | public string OsVersion { get; set; } 35 | 36 | /// Gets or sets the operating system version string. 37 | /// The operating system version string. 38 | public string OsVersionString { get; set; } 39 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.AspNetCore/AspNetCoreTrackerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using GoogleAnalyticsTracker.Core; 3 | using GoogleAnalyticsTracker.Core.TrackerParameters; 4 | using JetBrains.Annotations; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.Net.Http.Headers; 7 | 8 | namespace GoogleAnalyticsTracker.AspNet; 9 | 10 | [PublicAPI] 11 | public static class AspNetCoreTrackerExtensions 12 | { 13 | public static async Task TrackUserTimingAsync(this AspNetCoreTracker tracker, HttpContext httpContext, string pageTitle, string pageUrl, string category, string var, long value, string? label = null) 14 | { 15 | var userTimingParameters = new UserTimings 16 | { 17 | DocumentTitle = pageTitle, 18 | DocumentLocationUrl = pageUrl, 19 | UserAgent = httpContext.Request.Headers[HeaderNames.UserAgent].ToString(), 20 | DocumentHostName = httpContext.Request.Host.Value, 21 | UserLanguage = httpContext.Request.Headers[HeaderNames.AcceptLanguage].ToString().ToLower(), 22 | UserTimingCategory = category, 23 | UserTimingVariable = var, 24 | UserTimingTime = value, 25 | UserTimingLabel = label 26 | }; 27 | 28 | return await tracker.TrackAsync(userTimingParameters); 29 | } 30 | 31 | public static async Task TrackPageViewAsync(this AspNetCoreTracker tracker, HttpContext httpContext, string pageTitle, string? pageUrl = null) 32 | { 33 | pageUrl ??= httpContext.Request.Path; 34 | 35 | var pageViewParameters = new PageView 36 | { 37 | DocumentTitle = pageTitle, 38 | DocumentLocationUrl = pageUrl, 39 | UserAgent = httpContext.Request.Headers[HeaderNames.UserAgent].ToString(), 40 | DocumentHostName = httpContext.Request.Host.Value, 41 | UserLanguage = httpContext.Request.Headers[HeaderNames.AcceptLanguage].ToString().ToLower(), 42 | IpOverride = httpContext.Connection.RemoteIpAddress?.ToString() 43 | }; 44 | 45 | return await tracker.TrackAsync(pageViewParameters); 46 | } 47 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.AspNetCore/CookieBasedAnalyticsSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GoogleAnalyticsTracker.Core; 3 | using JetBrains.Annotations; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.AspNetCore.Http.Features; 6 | 7 | namespace GoogleAnalyticsTracker.AspNet; 8 | 9 | [PublicAPI] 10 | public class CookieBasedAnalyticsSession : AnalyticsSession 11 | { 12 | /// Unique identifier for the storage key. 13 | private const string StorageKeyUniqueId = "_GAT_uqid"; 14 | 15 | /// The storage key for first visit time. 16 | private const string StorageKeyFirstVisitTime = "_GAT_fvt"; 17 | 18 | /// The storage key for previous visit time. 19 | private const string StorageKeyPreviousVisitTime = "_GAT_pvt"; 20 | 21 | /// The storage key for last session id. 22 | private const string StorageKeySessionId = "_GAT_si"; 23 | 24 | /// The storage key for number of sessions. 25 | private const string StorageKeySessionCount = "_GAT_sc"; 26 | 27 | private readonly IHttpContextAccessor _contextAccessor; 28 | 29 | private readonly bool _sessionFeatureAvailable; 30 | 31 | /// 32 | /// Initializes a new instance of the CookieBasedAnalyticsSession class. 33 | /// 34 | /// The HTTP context accessor. 35 | public CookieBasedAnalyticsSession(IHttpContextAccessor contextAccessor) 36 | { 37 | _contextAccessor = contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor)); 38 | _sessionFeatureAvailable = _contextAccessor.HttpContext?.Features.Get() != null; 39 | 40 | // Create properties at once. 41 | GetOrCreateUniqueVisitorId(true); 42 | GetOrCreateFirstVisitTime(true); 43 | GetOrCreatePreviousVisitTime(true); 44 | GetOrCreateSessionCount(true); 45 | } 46 | 47 | /// Gets the unique visitor identifier. 48 | /// The unique visitor identifier. 49 | protected override string GetUniqueVisitorId() => GetOrCreateUniqueVisitorId(false); 50 | 51 | /// Gets the first visit time. 52 | /// The first visit time. 53 | protected override int GetFirstVisitTime() => GetOrCreateFirstVisitTime(false); 54 | 55 | /// Gets the previous visit time. 56 | /// The previous visit time. 57 | protected override int GetPreviousVisitTime() => GetOrCreatePreviousVisitTime(false); 58 | 59 | /// Gets the session count. 60 | /// The session count. 61 | protected override int GetSessionCount() => GetOrCreateSessionCount(false); 62 | 63 | /// Gets or creates the unique visitor identifier. 64 | /// true to create cookie. 65 | /// The unique visitor identifier. 66 | private string GetOrCreateUniqueVisitorId(bool createCookie) 67 | { 68 | var v = base.GetUniqueVisitorId(); 69 | 70 | // If no HTTP context available, bail out... 71 | if (_contextAccessor.HttpContext == null) return v; 72 | 73 | // If visitor has cookie, return value from cookie. 74 | // ReSharper disable once InvertIf 75 | if (string.IsNullOrEmpty(_contextAccessor.HttpContext.Request.Cookies[StorageKeyUniqueId])) 76 | { 77 | if (createCookie) 78 | { 79 | _contextAccessor.HttpContext.Response.Cookies.Append(StorageKeyUniqueId, v); 80 | } 81 | 82 | return v; 83 | } 84 | 85 | return _contextAccessor.HttpContext.Request.Cookies[StorageKeyUniqueId]!; 86 | } 87 | 88 | /// Gets or creates the first visit time. 89 | /// true to create cookie. 90 | /// The first visit time. 91 | private int GetOrCreateFirstVisitTime(bool createCookie) 92 | { 93 | // If no HTTP context available, bail out... 94 | if (_contextAccessor.HttpContext == null) return base.GetFirstVisitTime(); 95 | 96 | // ReSharper disable once InvertIf 97 | if (int.TryParse(_contextAccessor.HttpContext.Request.Cookies[StorageKeyFirstVisitTime], out var v) && v == 0) 98 | { 99 | v = base.GetFirstVisitTime(); 100 | 101 | if (createCookie) 102 | { 103 | _contextAccessor.HttpContext.Response.Cookies.Append(StorageKeyFirstVisitTime, v.ToString()); 104 | } 105 | } 106 | 107 | return v; 108 | } 109 | 110 | /// Gets or creates the previous visit time. 111 | /// true to create cookie. 112 | /// The previous visit time. 113 | private int GetOrCreatePreviousVisitTime(bool createCookie) 114 | { 115 | // If no HTTP context available, bail out... 116 | if (_contextAccessor.HttpContext == null) return base.GetCurrentVisitTime(); 117 | 118 | int.TryParse(_contextAccessor.HttpContext.Request.Cookies[StorageKeyPreviousVisitTime], out var v); 119 | 120 | if (createCookie) 121 | { 122 | _contextAccessor.HttpContext.Response.Cookies.Append(StorageKeyPreviousVisitTime, GetCurrentVisitTime().ToString()); 123 | } 124 | 125 | if (v == 0) 126 | { 127 | v = GetCurrentVisitTime(); 128 | } 129 | 130 | return v; 131 | } 132 | 133 | /// Gets or creates the session count. 134 | /// true to create cookie. 135 | /// The session count. 136 | private int GetOrCreateSessionCount(bool createCookie) 137 | { 138 | // If no HTTP context available/no session ID available, bail out... 139 | if (_contextAccessor.HttpContext == null || !_sessionFeatureAvailable) return 0; 140 | 141 | // Get current session count 142 | int.TryParse(_contextAccessor.HttpContext.Request.Cookies[StorageKeySessionCount], out var v); 143 | 144 | // Does the session match the previous session? If not, increment session count. 145 | if (!string.IsNullOrEmpty(_contextAccessor.HttpContext.Request.Cookies[StorageKeySessionId]) && 146 | _contextAccessor.HttpContext.Request.Cookies[StorageKeySessionId] != _contextAccessor.HttpContext.Session.Id) 147 | { 148 | v += 1; 149 | } 150 | 151 | // ReSharper disable once InvertIf 152 | if (createCookie) 153 | { 154 | _contextAccessor.HttpContext.Response.Cookies.Append(StorageKeySessionId, _contextAccessor.HttpContext.Session.Id); 155 | _contextAccessor.HttpContext.Response.Cookies.Append(StorageKeySessionCount, v.ToString()); 156 | } 157 | 158 | return v; 159 | } 160 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.AspNetCore/GoogleAnalyticsTracker.AspNetCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp3.1;net6.0 4 | GoogleAnalyticsTracker.AspNet 5 | True 6 | true 7 | GoogleAnalyticsTracker.AspNet 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.AspNetCore/GoogleAnalyticsTracker.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartenba/GoogleAnalyticsTracker/cd2842e2efcfb2eb74379ee84b1ac89048cb49f8/GoogleAnalyticsTracker.AspNetCore/GoogleAnalyticsTracker.snk -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.AspNetCore/GoogleAnalyticsTrackerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GoogleAnalyticsTracker.AspNet; 3 | using JetBrains.Annotations; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.DependencyInjection.Extensions; 7 | 8 | // ReSharper disable once CheckNamespace 9 | namespace Microsoft.AspNetCore.Builder; 10 | 11 | [PublicAPI] 12 | public static class GoogleAnalyticsTrackerExtensions 13 | { 14 | public static IServiceCollection AddGoogleAnalyticsTracker( 15 | this IServiceCollection services, Action options) 16 | { 17 | services.AddOptions(); 18 | services.TryAddSingleton(); 19 | 20 | services.TryAddScoped(); 21 | services.Configure(options); 22 | 23 | return services; 24 | } 25 | 26 | public static IApplicationBuilder UseGoogleAnalyticsTracker( 27 | this IApplicationBuilder builder) 28 | { 29 | return builder.UseMiddleware(); 30 | } 31 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.AspNetCore/GoogleAnalyticsTrackerMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using JetBrains.Annotations; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Extensions.Options; 5 | 6 | namespace GoogleAnalyticsTracker.AspNet; 7 | 8 | [PublicAPI] 9 | public class GoogleAnalyticsTrackerMiddleware 10 | { 11 | public const string TrackPageViewHandledMarker = nameof(GoogleAnalyticsTrackerMiddleware) + "." + nameof(TrackPageViewHandledMarker); 12 | 13 | private readonly RequestDelegate _next; 14 | 15 | public GoogleAnalyticsTrackerMiddleware(RequestDelegate next) 16 | { 17 | _next = next; 18 | } 19 | 20 | public async Task InvokeAsync( 21 | HttpContext context, 22 | AspNetCoreTracker tracker, 23 | IOptions optionsAccessor) 24 | { 25 | await _next(context); 26 | 27 | if (optionsAccessor.Value.ShouldTrackRequestInMiddleware == null || 28 | optionsAccessor.Value.ShouldTrackRequestInMiddleware(context)) 29 | { 30 | await tracker.TrackPageViewAsync(); 31 | context.Items[TrackPageViewHandledMarker] = true; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.AspNetCore/GoogleAnalyticsTrackerOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GoogleAnalyticsTracker.Core.Interface; 3 | using JetBrains.Annotations; 4 | using Microsoft.AspNetCore.Http; 5 | 6 | namespace GoogleAnalyticsTracker.AspNet; 7 | 8 | [PublicAPI] 9 | public class GoogleAnalyticsTrackerOptions 10 | { 11 | /// 12 | /// Google Analytics ID / tracking identifier. 13 | /// 14 | public string TrackerId { get; set; } = default!; 15 | 16 | /// 17 | /// Tracker environment customization. 18 | /// 19 | public Func? CustomizeTrackerEnvironment { get; set; } 20 | 21 | /// 22 | /// Analytics session customization. 23 | /// 24 | public Func? CustomizeAnalyticsSession { get; set; } 25 | 26 | /// 27 | /// Should the request be tracked? 28 | /// 29 | public Func? ShouldTrackRequestInMiddleware { get; set; } = TrackRequests.Yes; 30 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.AspNetCore/TrackEventAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using JetBrains.Annotations; 4 | using Microsoft.AspNetCore.Mvc.Controllers; 5 | using Microsoft.AspNetCore.Mvc.Filters; 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | namespace GoogleAnalyticsTracker.AspNet; 9 | 10 | /// 11 | /// Track ASP.NET MVC vent. 12 | /// 13 | [PublicAPI] 14 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] 15 | public class TrackEventAttribute : Attribute, IAsyncActionFilter 16 | { 17 | /// 18 | /// Is tracking for this action enabled? Defaults to true. 19 | /// 20 | public bool Enabled { get; set; } = true; 21 | 22 | /// 23 | /// Category for the event. Defaults to current controller. 24 | /// 25 | public string? Category { get; set; } 26 | 27 | /// 28 | /// Action for the event. Defaults to current action name. 29 | /// 30 | public string? Action { get; set; } 31 | 32 | /// 33 | /// Value for the event. 34 | /// 35 | public int Value { get; set; } = 1; 36 | 37 | public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) 38 | { 39 | await next(); 40 | 41 | if (Enabled) 42 | { 43 | var tracker = context.HttpContext.RequestServices.GetRequiredService(); 44 | await tracker.TrackEventAsync( 45 | Category ?? context.Controller.GetType().Name.Replace("Controller", string.Empty), 46 | Action ?? (context.ActionDescriptor as ControllerActionDescriptor)?.ActionName ?? context.ActionDescriptor.DisplayName ?? context.ActionDescriptor.Id, 47 | Value); 48 | } 49 | 50 | context.HttpContext.Items[GoogleAnalyticsTrackerMiddleware.TrackPageViewHandledMarker] = true; 51 | } 52 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.AspNetCore/TrackPageViewAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using JetBrains.Annotations; 4 | using Microsoft.AspNetCore.Mvc.Filters; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace GoogleAnalyticsTracker.AspNet; 8 | 9 | /// 10 | /// Track ASP.NET MVC page view. 11 | /// 12 | [PublicAPI] 13 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] 14 | public class TrackPageViewAttribute : Attribute, IAsyncActionFilter 15 | { 16 | /// 17 | /// Is tracking for this action enabled? Defaults to true. 18 | /// 19 | public bool Enabled { get; set; } = true; 20 | 21 | /// 22 | /// Custom document title to track. 23 | /// 24 | public string? CustomTitle { get; set; } 25 | 26 | public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) 27 | { 28 | await next(); 29 | 30 | if (Enabled) 31 | { 32 | var tracker = context.HttpContext.RequestServices.GetRequiredService(); 33 | await tracker.TrackPageViewAsync(CustomTitle); 34 | } 35 | 36 | context.HttpContext.Items[GoogleAnalyticsTrackerMiddleware.TrackPageViewHandledMarker] = true; 37 | } 38 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.AspNetCore/TrackRequests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace GoogleAnalyticsTracker.AspNet; 6 | 7 | [PublicAPI] 8 | public static class TrackRequests 9 | { 10 | /// Track all requests. 11 | public static readonly Func Yes = _ => true; 12 | 13 | /// Track all requests that have not yet been tracked. Use this option when decorating controllers and actions with . 14 | public static readonly Func OnlyWhenNotYetTracked = context => 15 | context.Items.ContainsKey(GoogleAnalyticsTrackerMiddleware.TrackPageViewHandledMarker); 16 | 17 | /// Ignore all requests. 18 | public static readonly Func No = _ => false; 19 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/AnalyticsSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using GoogleAnalyticsTracker.Core.Interface; 4 | using JetBrains.Annotations; 5 | 6 | namespace GoogleAnalyticsTracker.Core; 7 | 8 | [PublicAPI] 9 | public class AnalyticsSession : IAnalyticsSession 10 | { 11 | protected string? SessionId { get; set; } 12 | protected int SessionCount { get; set; } 13 | 14 | protected virtual string GetUniqueVisitorId() 15 | { 16 | var random = new Random((int)DateTime.UtcNow.Ticks); 17 | // ReSharper disable once UseStringInterpolation 18 | return string.Format("{0}{1}", random.Next(100000000, 999999999), "00145214523"); 19 | } 20 | 21 | protected virtual int GetFirstVisitTime() => DateTime.UtcNow.ToUnixTime(); 22 | 23 | protected virtual int GetPreviousVisitTime() => DateTime.UtcNow.ToUnixTime(); 24 | 25 | protected virtual int GetCurrentVisitTime() => DateTime.UtcNow.ToUnixTime(); 26 | 27 | protected virtual int GetSessionCount() => ++SessionCount; 28 | 29 | public virtual string GenerateSessionId() => SessionId ??= Guid.NewGuid().ToString(); 30 | 31 | public virtual string GenerateCacheBuster() 32 | { 33 | var random = new Random((int)DateTime.UtcNow.Ticks); 34 | return random.Next(100000000, 999999999).ToString(CultureInfo.InvariantCulture); 35 | } 36 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/DateTimeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GoogleAnalyticsTracker.Core; 4 | 5 | public static class DateTimeExtensions 6 | { 7 | private static readonly DateTime Epoch = new(1970, 1, 1); 8 | 9 | public static int ToUnixTime(this DateTime current) 10 | => (int)current.Subtract(Epoch).TotalSeconds; 11 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/EnumExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace GoogleAnalyticsTracker.Core; 5 | 6 | public static class EnumExtensions 7 | { 8 | public static bool IsNullableEnum(this Type t) 9 | { 10 | var u = Nullable.GetUnderlyingType(t); 11 | return u != null && u.GetTypeInfo().IsEnum; 12 | } 13 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/GoogleAnalyticsEndpoints.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace GoogleAnalyticsTracker.Core; 5 | 6 | [PublicAPI] 7 | public static class GoogleAnalyticsEndpoints 8 | { 9 | /// Default Google Endpoint URL. 10 | public const string Default = "https://www.google-analytics.com/collect"; 11 | 12 | /// Non-secure Google Endpoint URL, not recommended. 13 | /// It is not recommended to use this endpoint, as it uses no secure connection. 14 | [Obsolete("It is not recommended to use this endpoint, as it uses no secure connection.")] 15 | public const string NonSecure = "http://www.google-analytics.com/collect"; 16 | 17 | /// Debug, validating Google Endpoint. 18 | public const string Debug = "https://www.google-analytics.com/debug/collect"; 19 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/GoogleAnalyticsTracker.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp3.1;net6.0;netstandard2.0 4 | GoogleAnalyticsTracker.Core 5 | True 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/GoogleAnalyticsTracker.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartenba/GoogleAnalyticsTracker/cd2842e2efcfb2eb74379ee84b1ac89048cb49f8/GoogleAnalyticsTracker.Core/GoogleAnalyticsTracker.snk -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/HttpWebRequestExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Reflection; 3 | using JetBrains.Annotations; 4 | 5 | namespace GoogleAnalyticsTracker.Core; 6 | 7 | [PublicAPI] 8 | public static class HttpWebRequestExtensions 9 | { 10 | /// 11 | /// Workaround for PCL "Additional information: The '.....' header must be modified using the appropriate property or method." exception. 12 | /// 13 | /// Request 14 | /// Header name 15 | /// Value for the header 16 | public static void SetHeader(this HttpWebRequest request, string header, string value) 17 | { 18 | // Retrieve the property through reflection. 19 | var propertyInfo = request.GetType().GetRuntimeProperty(header.Replace("-", string.Empty)); 20 | 21 | // Check if the property is available. 22 | if (propertyInfo != null) 23 | // Set the value of the header. 24 | propertyInfo.SetValue(request, value, null); 25 | else 26 | // Set the value of the header. 27 | request.Headers[header] = value; 28 | } 29 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/Interface/IAnalyticsSession.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.Interface; 4 | 5 | [PublicAPI] 6 | public interface IAnalyticsSession 7 | { 8 | string GenerateSessionId(); 9 | string GenerateCacheBuster(); 10 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/Interface/ITrackerEnvironment.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.Interface; 4 | 5 | [PublicAPI] 6 | public interface ITrackerEnvironment 7 | { 8 | string OsPlatform { get; set; } 9 | string OsVersion { get; set; } 10 | string OsVersionString { get; set; } 11 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using GoogleAnalyticsTracker.Core.Interface; 7 | using GoogleAnalyticsTracker.Core.TrackerParameters; 8 | using System.Linq; 9 | using System.Net.Http; 10 | using System.Reflection; 11 | using GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 12 | using JetBrains.Annotations; 13 | 14 | namespace GoogleAnalyticsTracker.Core; 15 | 16 | [PublicAPI] 17 | public class TrackerBase : IDisposable 18 | { 19 | private static readonly HttpClient DefaultHttpClient = new(); 20 | 21 | public string TrackingAccount { get; set; } 22 | public IAnalyticsSession AnalyticsSession { get; set; } 23 | 24 | public string UserAgent { get; set; } 25 | 26 | public bool ThrowOnErrors { get; set; } 27 | public string EndpointUrl { get; set; } 28 | 29 | /// 30 | /// Use HTTP GET (not recommended) instead of POST. 31 | /// When switched on, the 32 | /// sets the , too. 33 | /// 34 | public bool UseHttpGet { get; set; } 35 | 36 | private HttpClient? _customHttpClient; 37 | 38 | /// 39 | /// Makes it possible to set a custom HTTP client. If not set, the internal, static default one will be used. 40 | /// 41 | public HttpClient HttpClient 42 | { 43 | get => _customHttpClient ?? DefaultHttpClient; 44 | set => _customHttpClient = value; 45 | } 46 | 47 | public TrackerBase(string trackingAccount, ITrackerEnvironment trackerEnvironment) 48 | : this(trackingAccount, new AnalyticsSession(), trackerEnvironment) 49 | { 50 | } 51 | 52 | public TrackerBase(string trackingAccount, IAnalyticsSession analyticsSession, ITrackerEnvironment trackerEnvironment) 53 | { 54 | TrackingAccount = trackingAccount; 55 | AnalyticsSession = analyticsSession; 56 | 57 | EndpointUrl = GoogleAnalyticsEndpoints.Default; 58 | 59 | // ReSharper disable once UseStringInterpolation 60 | UserAgent = string.Format("GoogleAnalyticsTracker/7.0 ({0}; {1}; {2})", trackerEnvironment.OsPlatform, trackerEnvironment.OsVersion, trackerEnvironment.OsVersionString); 61 | } 62 | 63 | private async Task RequestUrlAsync(string url, IDictionary parameters, string userAgent) 64 | { 65 | // Create GET string 66 | var data = string.Join("&", parameters 67 | .OrderBy(p => p.Key, new BeaconComparer()) 68 | // ReSharper disable once UseStringInterpolation 69 | .Select(p => string.Format("{0}={1}", p.Key, Uri.EscapeDataString(p.Value))) 70 | ); 71 | 72 | // Build TrackingResult 73 | var returnValue = new TrackingResult(url, parameters, data); 74 | 75 | // Create request 76 | HttpRequestMessage request; 77 | try 78 | { 79 | request = UseHttpGet 80 | ? CreateGetWebRequest(url, data) 81 | : CreatePostWebRequest(url, data); 82 | 83 | if (!string.IsNullOrEmpty(userAgent)) 84 | { 85 | 86 | request.Headers.TryAddWithoutValidation("User-Agent", userAgent); 87 | } 88 | } 89 | catch (Exception ex) 90 | { 91 | if (ThrowOnErrors) 92 | { 93 | throw; 94 | } 95 | 96 | returnValue.Success = false; 97 | returnValue.Exception = ex; 98 | 99 | return returnValue; 100 | } 101 | 102 | // Perform request 103 | HttpResponseMessage? response = null; 104 | try 105 | { 106 | response = await HttpClient.SendAsync(request); 107 | returnValue.Success = true; 108 | } 109 | catch (Exception ex) 110 | { 111 | if (ThrowOnErrors) 112 | { 113 | throw; 114 | } 115 | else 116 | { 117 | returnValue.Success = false; 118 | returnValue.Exception = ex; 119 | } 120 | } 121 | finally 122 | { 123 | response?.Dispose(); 124 | } 125 | 126 | return returnValue; 127 | } 128 | 129 | private static HttpRequestMessage CreateGetWebRequest(string url, string data) 130 | => new(HttpMethod.Get, $"{url}?{data}"); 131 | 132 | private static HttpRequestMessage CreatePostWebRequest(string url, string data) 133 | { 134 | var request = new HttpRequestMessage(HttpMethod.Post, url); 135 | 136 | var dataBytes = Encoding.UTF8.GetBytes(data); 137 | request.Content = new ByteArrayContent(dataBytes); 138 | 139 | return request; 140 | } 141 | 142 | private static IDictionary GetParametersDictionary(IProvideBeaconParameters parameters) 143 | { 144 | var beaconList = new BeaconList(); 145 | 146 | foreach (var p in parameters.GetType().GetRuntimeProperties()) 147 | { 148 | if (p.GetCustomAttribute(typeof(BeaconAttribute), true) is not BeaconAttribute attr) 149 | { 150 | continue; 151 | } 152 | 153 | object? value; 154 | var underlyingType = Nullable.GetUnderlyingType(p.PropertyType); 155 | 156 | if ((p.PropertyType.GetTypeInfo().IsEnum || p.PropertyType.IsNullableEnum()) && attr.IsEnumByValueBased) 157 | { 158 | value = GetValueFromEnum(p, parameters) ?? p.GetMethod?.Invoke(parameters, null); 159 | } 160 | else if (p.PropertyType.GetTypeInfo().IsEnum || p.PropertyType.IsNullableEnum()) 161 | { 162 | value = GetLowerCaseValueFromEnum(p, parameters) ?? p.GetMethod?.Invoke(parameters, null); 163 | } 164 | // ReSharper disable once ArrangeRedundantParentheses 165 | else if (p.PropertyType == typeof(bool) || (underlyingType != null && underlyingType == typeof(bool))) 166 | { 167 | value = p.GetMethod?.Invoke(parameters, null); 168 | if (value != null) 169 | value = (bool)value ? "1" : "0"; 170 | } 171 | else 172 | { 173 | value = p.GetMethod?.Invoke(parameters, null); 174 | } 175 | 176 | if (value == null) 177 | { 178 | continue; 179 | } 180 | 181 | beaconList.Add(attr.Name, Convert.ToString(value, CultureInfo.InvariantCulture)!); 182 | } 183 | 184 | var parametersType = parameters.GetType(); 185 | // ReSharper disable once InvertIf 186 | if (typeof(IProvideProductsParameters).IsAssignableFrom(parametersType)) 187 | { 188 | beaconList.AddRange(GetProductsParameters((IProvideProductsParameters)parameters)); 189 | } 190 | 191 | return beaconList.ToDictionary(key => key.Item1, value => value.Item2); 192 | } 193 | 194 | private static BeaconList GetProductsParameters(IProvideProductsParameters? parameters) 195 | { 196 | var result = new BeaconList(); 197 | 198 | if (parameters?.Products.Any() != true) return result; 199 | 200 | var productIndex = 1; 201 | foreach (var product in parameters.Products) 202 | { 203 | var parameterList = GetParametersDictionary(product); 204 | foreach (var customDimension in product.GetCustomDimensions()) 205 | { 206 | parameterList.Add(customDimension.Name, customDimension.Value); 207 | } 208 | 209 | parameterList = 210 | parameterList.ToDictionary(key => $"pr{productIndex}{key.Key}", value => value.Value); 211 | result.AddRange(parameterList); 212 | 213 | productIndex++; 214 | } 215 | 216 | return result; 217 | } 218 | 219 | private static object? GetValueFromEnum(PropertyInfo propertyInfo, IProvideBeaconParameters parameters) 220 | { 221 | var value = propertyInfo.GetMethod?.Invoke(parameters, null); 222 | 223 | if (value == null) return null; 224 | 225 | var propertyType = propertyInfo.PropertyType.IsNullableEnum() 226 | ? Nullable.GetUnderlyingType(propertyInfo.PropertyType) 227 | : propertyInfo.PropertyType; 228 | 229 | if (propertyType == null) return null; 230 | 231 | var enumValue = Enum.Parse(propertyType, value.ToString()!); 232 | 233 | return enumValue.GetHashCode().ToString(CultureInfo.InvariantCulture); 234 | } 235 | 236 | private static object? GetLowerCaseValueFromEnum(PropertyInfo propertyInfo, IProvideBeaconParameters parameters) 237 | { 238 | var value = propertyInfo.GetMethod?.Invoke(parameters, null); 239 | return value?.ToString()?.ToLowerInvariant(); 240 | } 241 | 242 | /// 243 | /// Set base, required parameters if they are empty: TrackingId, ClientId. 244 | /// 245 | /// GA request parameters. 246 | private void SetRequiredParameters(IGeneralParameters parameters) 247 | { 248 | if (string.IsNullOrEmpty(parameters.ProtocolVersion)) 249 | { 250 | throw new ArgumentException("No ProtocolVersion", nameof(parameters)); 251 | } 252 | 253 | if (string.IsNullOrEmpty(parameters.TrackingId)) 254 | { 255 | parameters.TrackingId = TrackingAccount; 256 | } 257 | 258 | if (string.IsNullOrEmpty(parameters.ClientId)) 259 | { 260 | parameters.ClientId = AnalyticsSession.GenerateSessionId(); 261 | } 262 | } 263 | 264 | /// 265 | /// Set additional properties in parameters if they are empty: CacheBuster. 266 | /// The CacheBuster is set when UseHttpGet flag is set. 267 | /// 268 | /// Override to change other properties. 269 | /// 270 | /// GA request parameters. 271 | protected virtual void AmendParameters(IGeneralParameters parameters) 272 | { 273 | if (UseHttpGet && string.IsNullOrEmpty(parameters.CacheBuster)) 274 | { 275 | parameters.CacheBuster = AnalyticsSession.GenerateCacheBuster(); 276 | } 277 | } 278 | 279 | /// 280 | /// Send parameters to GA endpoint. 281 | /// 282 | /// GA request parameters. 283 | /// Result of the request. 284 | public async Task TrackAsync(IGeneralParameters generalParameters) 285 | { 286 | AmendParameters(generalParameters); 287 | // Set required must come after amend. 288 | SetRequiredParameters(generalParameters); 289 | 290 | var parameters = GetParametersDictionary(generalParameters); 291 | 292 | return await RequestUrlAsync(EndpointUrl, parameters, generalParameters.UserAgent ?? UserAgent); 293 | } 294 | 295 | #region IDisposable Members 296 | 297 | public void Dispose() 298 | { 299 | GC.SuppressFinalize(this); 300 | } 301 | 302 | #endregion 303 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Beacon.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 5 | 6 | internal sealed class Beacon : Tuple 7 | { 8 | /// 9 | /// Initializes a new instance of the class. 10 | /// 11 | /// The value of the tuple's first component.The value of the tuple's second component. 12 | public Beacon(TKey item1, TValue item2) : base(item1, item2) { } 13 | 14 | public override bool Equals(object? other) => Equals(other as Beacon); 15 | 16 | // ReSharper disable once SuggestBaseTypeForParameter 17 | private bool Equals(Beacon? other) 18 | { 19 | if (other == null) { return false; } 20 | if (ReferenceEquals(this, other)) { return true; } 21 | var comparer = Comparer.Default; 22 | return comparer.Compare(Item1, other.Item1) == 0; 23 | } 24 | 25 | public override int GetHashCode() => base.GetHashCode(); 26 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/BeaconAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 5 | 6 | [PublicAPI] 7 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Enum)] 8 | public class BeaconAttribute : Attribute 9 | { 10 | /// 11 | /// Beacon name 12 | /// 13 | public string Name { get; } 14 | 15 | /// 16 | /// Indicates that beacon is a Enum and must use its value. 17 | /// 18 | public bool IsEnumByValueBased { get; } 19 | 20 | /// 21 | /// Indicates whether is required or not. 22 | /// 23 | public bool IsRequired { get; } 24 | 25 | /// 26 | /// ctor 27 | /// 28 | /// Beacon name 29 | /// Beacon is required? 30 | /// Beacon is a enum that must be get by value? 31 | public BeaconAttribute(string name, bool isRequired = false, bool isEnumByValueBased = false) 32 | { 33 | Name = name; 34 | IsEnumByValueBased = isEnumByValueBased; 35 | IsRequired = isRequired; 36 | } 37 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/BeaconComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using JetBrains.Annotations; 3 | 4 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 5 | 6 | [PublicAPI] 7 | public class BeaconComparer : IComparer 8 | { 9 | private static readonly List Ordered = new() { "t", "cid", "tid", "v" }; // reverse order 10 | private static readonly List LastOrdered = new() { "z" }; // direct order 11 | 12 | public int Compare(string? x, string? y) 13 | { 14 | if (x == null && y == null) 15 | return 0; 16 | if (x != null && y == null) 17 | return -1; 18 | if (x == null && y != null) 19 | return 1; 20 | 21 | var xi = Ordered.IndexOf(x!); 22 | var yi = Ordered.IndexOf(y!); 23 | if (xi > yi) 24 | return -1; 25 | if (xi < yi) 26 | return 1; 27 | 28 | xi = LastOrdered.IndexOf(x!); 29 | yi = LastOrdered.IndexOf(y!); 30 | if (xi > yi) 31 | return 1; 32 | if (xi < yi) 33 | return -1; 34 | 35 | return string.CompareOrdinal(x, y); 36 | } 37 | 38 | public bool Equals(string? x, string? y) 39 | { 40 | if (x != null) 41 | return y != null && x.Equals(y); 42 | 43 | // ReSharper disable once ConvertIfStatementToReturnStatement 44 | if (y != null) 45 | return false; 46 | 47 | return true; 48 | } 49 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/BeaconList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 5 | 6 | internal class BeaconList : IList> 7 | where TKey : notnull 8 | { 9 | private readonly IList> _list; 10 | 11 | public BeaconList() 12 | { 13 | _list = new List>(); 14 | } 15 | 16 | #region Implementation of IEnumerable 17 | 18 | /// 19 | /// Returns an enumerator that iterates through the collection. 20 | /// 21 | /// 22 | /// A that can be used to iterate through the collection. 23 | /// 24 | public IEnumerator> GetEnumerator() 25 | { 26 | return _list.GetEnumerator(); 27 | } 28 | 29 | /// 30 | /// Returns an enumerator that iterates through a collection. 31 | /// 32 | /// 33 | /// An object that can be used to iterate through the collection. 34 | /// 35 | IEnumerator IEnumerable.GetEnumerator() 36 | { 37 | return GetEnumerator(); 38 | } 39 | 40 | #endregion Implementation of IEnumerable 41 | 42 | #region Implementation of ICollection> 43 | 44 | /// 45 | /// Adds an item to the . 46 | /// 47 | /// The object to add to the .The is read-only. 48 | public void Add(Beacon item) 49 | { 50 | _list.Add(item); 51 | } 52 | 53 | /// 54 | /// Removes all items from the . 55 | /// 56 | /// The is read-only. 57 | public void Clear() 58 | { 59 | _list.Clear(); 60 | } 61 | 62 | /// 63 | /// Determines whether the contains a specific value. 64 | /// 65 | /// 66 | /// true if is found in the ; otherwise, false. 67 | /// 68 | /// The object to locate in the . 69 | public bool Contains(Beacon item) 70 | { 71 | return _list.Contains(item); 72 | } 73 | 74 | /// 75 | /// Copies the elements of the to an , starting at a particular index. 76 | /// 77 | /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing.The zero-based index in at which copying begins. is null. is less than 0.The number of elements in the source is greater than the available space from to the end of the destination . 78 | public void CopyTo(Beacon[] array, int arrayIndex) 79 | { 80 | _list.CopyTo(array, arrayIndex); 81 | } 82 | 83 | /// 84 | /// Removes the first occurrence of a specific object from the . 85 | /// 86 | /// 87 | /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . 88 | /// 89 | /// The object to remove from the .The is read-only. 90 | public bool Remove(Beacon item) 91 | { 92 | return _list.Remove(item); 93 | } 94 | 95 | /// 96 | /// Gets the number of elements contained in the . 97 | /// 98 | /// 99 | /// The number of elements contained in the . 100 | /// 101 | public int Count => _list.Count; 102 | 103 | /// 104 | /// Gets a value indicating whether the is read-only. 105 | /// 106 | /// 107 | /// true if the is read-only; otherwise, false. 108 | /// 109 | public bool IsReadOnly => _list.IsReadOnly; 110 | 111 | #endregion Implementation of ICollection> 112 | 113 | #region Implementation of IList> 114 | 115 | /// 116 | /// Determines the index of a specific item in the . 117 | /// 118 | /// 119 | /// The index of if found in the list; otherwise, -1. 120 | /// 121 | /// The object to locate in the . 122 | public int IndexOf(Beacon item) 123 | => _list.IndexOf(item); 124 | 125 | /// 126 | /// Inserts an item to the at the specified index. 127 | /// 128 | /// The zero-based index at which should be inserted.The object to insert into the . is not a valid index in the .The is read-only. 129 | public void Insert(int index, Beacon item) 130 | { 131 | _list.Insert(index, item); 132 | } 133 | 134 | /// 135 | /// Removes the item at the specified index. 136 | /// 137 | /// The zero-based index of the item to remove. is not a valid index in the .The is read-only. 138 | public void RemoveAt(int index) 139 | { 140 | _list.RemoveAt(index); 141 | } 142 | 143 | /// 144 | /// Gets or sets the element at the specified index. 145 | /// 146 | /// 147 | /// The element at the specified index. 148 | /// 149 | /// The zero-based index of the element to get or set. is not a valid index in the .The property is set and the is read-only. 150 | public Beacon this[int index] 151 | { 152 | get => _list[index]; 153 | set => _list[index] = value; 154 | } 155 | 156 | #endregion Implementation of IList> 157 | 158 | public void Add(TKey key, TValue value) 159 | { 160 | _list.Add(new Beacon(key, value)); 161 | } 162 | 163 | public void AddRange(IDictionary sourceList) 164 | { 165 | foreach (var parameter in sourceList) 166 | { 167 | Add(parameter.Key, parameter.Value); 168 | } 169 | } 170 | 171 | public void AddRange(BeaconList sourceList) 172 | { 173 | foreach (var parameter in sourceList) 174 | { 175 | Add(parameter); 176 | } 177 | } 178 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/ContentExperiments.cs: -------------------------------------------------------------------------------- 1 | using GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 2 | using JetBrains.Annotations; 3 | 4 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 5 | 6 | [PublicAPI] 7 | public class ContentExperiments : GeneralParameters, IContentExperimentsParameters 8 | { 9 | /// 10 | /// Creates a new . 11 | /// 12 | /// The type of hit. Must be one of 'pageview', 'screenview', 'event', 'transaction', 'item', 'social', 'exception', 'timing'. 13 | /// Specifies the experiment id. 14 | /// Specifies the experiment variant id. 15 | public ContentExperiments( 16 | HitType hitType, 17 | string experimentId, 18 | string experimentVariant) 19 | { 20 | HitType = hitType; 21 | ExperimentId = experimentId; 22 | ExperimentVariant = experimentVariant; 23 | } 24 | 25 | #region Overrides of GeneralParameters 26 | 27 | public override HitType HitType { get; } 28 | 29 | #endregion 30 | 31 | #region Implementation of IContentExperimentsParameters 32 | 33 | /// 34 | /// Specifies the experiment id. 35 | /// Required for experiment tracking 36 | /// K7Q-9lpLSd21prp9vIhdoA 37 | /// 38 | [Beacon("xid", true)] 39 | public string ExperimentId { get; set; } 40 | 41 | /// 42 | /// Specifies the experiment variant id. 43 | /// Required for content experiment tracking 44 | /// 1 45 | /// 46 | [Beacon("xvar", true)] 47 | public string ExperimentVariant { get; set; } 48 | 49 | #endregion 50 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/CustomDimension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 3 | using JetBrains.Annotations; 4 | 5 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 6 | 7 | [PublicAPI] 8 | public class CustomDimension : ICustomDimension 9 | { 10 | public CustomDimension(int id, string value) 11 | { 12 | Id = id; 13 | Value = value; 14 | } 15 | 16 | private int _id = 1; 17 | public int Id 18 | { 19 | get => _id; 20 | set 21 | { 22 | if (value is < 1 or > 200) 23 | { 24 | throw new ArgumentOutOfRangeException(nameof(Id), "Must be between 1 and 200 inclusive"); 25 | } 26 | 27 | _id = value; 28 | } 29 | } 30 | 31 | public string Name => $"cd{_id}"; 32 | 33 | private string _value = string.Empty; 34 | public string Value 35 | { 36 | get => _value; 37 | set => _value = value.Length > 149 ? value.Substring(0, 149) : value; 38 | } 39 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/ECommerceItem.cs: -------------------------------------------------------------------------------- 1 | using GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 2 | using JetBrains.Annotations; 3 | 4 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 5 | 6 | [PublicAPI] 7 | public class ECommerceItem : GeneralParameters, IECommerceParameters, IECommerceItemParameters 8 | { 9 | /// 10 | /// Creates a new . 11 | /// 12 | /// A unique identifier for the transaction. 13 | /// Specifies the item name. 14 | public ECommerceItem( 15 | string transactionId, 16 | string itemName) 17 | { 18 | TransactionId = transactionId; 19 | ItemName = itemName; 20 | } 21 | 22 | #region Overrides of GeneralParameters 23 | 24 | /// 25 | /// The type of hit. Must be one of 'pageview', 'screenview', 'event', 'transaction', 'item', 'social', 'exception', 'timing'. 26 | /// Required for all hit types 27 | /// HitType.Pageview 28 | /// 29 | public override HitType HitType => HitType.Item; 30 | 31 | #endregion 32 | 33 | #region Implementation of IECommerceParameters 34 | 35 | /// 36 | /// A unique identifier for the transaction. 37 | /// This value should be the same for both the Transaction hit and Items hits associated to the particular transaction. 38 | /// Required for both transaction and item hit type. 39 | /// OD564 40 | /// 41 | [Beacon("ti", true)] 42 | public string TransactionId { get; set; } 43 | 44 | /// 45 | /// When present indicates the local currency for all transaction currency values. 46 | /// Value should be a valid ISO 4217 currency code. 47 | /// Optional 48 | /// EUR 49 | /// 50 | [Beacon("cu")] 51 | public string? CurrencyCode { get; set; } 52 | 53 | #endregion 54 | 55 | #region Implementation of IECommerceItemParameters 56 | 57 | /// 58 | /// Specifies the item name. 59 | /// Required for item hit type. 60 | /// Shoe 61 | /// 62 | [Beacon("in", true)] 63 | public string ItemName { get; set; } 64 | 65 | /// 66 | /// Specifies the price for a single item / unit. 67 | /// Optional 68 | /// 3.50 69 | /// 70 | [Beacon("ip")] 71 | public decimal ItemPrice { get; set; } 72 | 73 | /// 74 | /// Specifies the number of items purchased. 75 | /// Optional 76 | /// 4 77 | /// 78 | [Beacon("iq")] 79 | public long ItemQuantity { get; set; } 80 | 81 | /// 82 | /// Specifies the SKU or item code. 83 | /// Optional 84 | /// SKU47 85 | /// 86 | [Beacon("ic")] 87 | public string? ItemCode { get; set; } 88 | 89 | /// 90 | /// Specifies the category that the item belongs to. 91 | /// Optional 92 | /// Blue 93 | /// 94 | [Beacon("iv")] 95 | public string? ItemCategory { get; set; } 96 | 97 | #endregion 98 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/ECommerceTransaction.cs: -------------------------------------------------------------------------------- 1 | using GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 2 | using JetBrains.Annotations; 3 | 4 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 5 | 6 | [PublicAPI] 7 | public class ECommerceTransaction : GeneralParameters, IECommerceParameters, IECommerceTransactionParameters 8 | { 9 | public ECommerceTransaction(string transactionId) 10 | { 11 | TransactionId = transactionId; 12 | } 13 | 14 | #region Overrides of GeneralParameters 15 | 16 | /// 17 | /// The type of hit. Must be one of 'pageview', 'screenview', 'event', 'transaction', 'item', 'social', 'exception', 'timing'. 18 | /// Required for all hit types 19 | /// HitType.Pageview 20 | /// 21 | public override HitType HitType => HitType.Transaction; 22 | 23 | #endregion 24 | 25 | #region Implementation of IECommerceParameters 26 | 27 | /// 28 | /// A unique identifier for the transaction. 29 | /// This value should be the same for both the Transaction hit and Items hits associated to the particular transaction. 30 | /// Required for both transaction and item hit type. 31 | /// OD564 32 | /// 33 | [Beacon("ti", true)] 34 | public string TransactionId { get; set; } 35 | 36 | /// 37 | /// When present indicates the local currency for all transaction currency values. 38 | /// Value should be a valid ISO 4217 currency code. 39 | /// Optional 40 | /// EUR 41 | /// 42 | [Beacon("cu")] 43 | public string? CurrencyCode { get; set; } //TODO: Implement enum based 44 | 45 | #endregion 46 | 47 | #region Implementation of IECommerceTransactionParameters 48 | 49 | /// 50 | /// Specifies the affiliation or store name. 51 | /// Optional 52 | /// Member 53 | /// 54 | [Beacon("ta")] 55 | public string? TransactionAffiliation { get; set; } 56 | 57 | /// 58 | /// Specifies the total revenue associated with the transaction. This value should include any shipping or tax costs. 59 | /// Optional 60 | /// 15.47 61 | /// 62 | [Beacon("tr")] 63 | public decimal TransactionRevenue { get; set; } 64 | 65 | /// 66 | /// Specifies the total shipping cost of the transaction. 67 | /// Optional 68 | /// 3.50 69 | /// 70 | [Beacon("ts")] 71 | public decimal TransactionShipping { get; set; } 72 | 73 | /// 74 | /// Specifies the total tax of the transaction. 75 | /// Optional 76 | /// 11.20 77 | /// 78 | [Beacon("tt")] 79 | public decimal TransactionTax { get; set; } 80 | 81 | #endregion 82 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/EnhancedECommerceProduct.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 5 | using JetBrains.Annotations; 6 | 7 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 8 | 9 | [PublicAPI] 10 | public class EnhancedECommerceProduct : IEnhancedECommerceProduct 11 | { 12 | public EnhancedECommerceProduct() 13 | { 14 | _customDimensions = new List(); 15 | } 16 | 17 | public void AddCustomDimension(int id, string value) 18 | { 19 | if (_customDimensions.Any(cd => cd.Id == id)) 20 | { 21 | throw new ArgumentException($"Product already have custom dimension with Id = {id}"); 22 | } 23 | 24 | _customDimensions.Add(new CustomDimension(id, value)); 25 | } 26 | 27 | public List GetCustomDimensions() 28 | { 29 | return _customDimensions; 30 | } 31 | 32 | [Beacon("id")] 33 | public string? Sku { get; set; } 34 | 35 | [Beacon("nm")] 36 | public string? Name { get; set; } 37 | 38 | [Beacon("br")] 39 | public string? Brand { get; set; } 40 | 41 | [Beacon("ca")] 42 | public string? Category { get; set; } 43 | 44 | [Beacon("va")] 45 | public string? Variant { get; set; } 46 | 47 | [Beacon("pr")] 48 | public decimal? Price { get; set; } 49 | 50 | [Beacon("qt")] 51 | public int? Quantity { get; set; } 52 | 53 | [Beacon("cc")] 54 | public string? ProductCouponCode { get; set; } 55 | 56 | [Beacon("ps")] 57 | public int? Position { get; set; } 58 | 59 | private readonly List _customDimensions; 60 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/EnhancedECommerceTransaction.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 3 | using JetBrains.Annotations; 4 | 5 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 6 | 7 | [PublicAPI] 8 | public class EnhancedECommerceTransaction : ECommerceTransaction, IEnhancedECommerceTransactionParameters 9 | { 10 | public EnhancedECommerceTransaction(string transactionId) 11 | : base(transactionId) 12 | { 13 | Products = new List(); 14 | } 15 | 16 | #region Overrides of GeneralParameters 17 | 18 | /// 19 | /// The type of hit. Must be one of 'pageview', 'screenview', 'event', 'transaction', 'item', 'social', 'exception', 'timing'. 20 | /// Required for all hit types 21 | /// HitType.Pageview 22 | /// 23 | public override HitType HitType => HitType.Event; 24 | 25 | #endregion 26 | 27 | [Beacon("pa")] 28 | public ProductAction ProductAction { get; set; } 29 | 30 | [Beacon("tcc")] 31 | public string? CouponCode { get; set; } 32 | 33 | [Beacon("pal")] 34 | public string? ProductActionList { get; set; } 35 | 36 | [Beacon("cos")] 37 | public int? CheckoutStep { get; set; } 38 | 39 | [Beacon("col")] 40 | public string? CheckoutStepOption { get; set; } 41 | 42 | public List Products { get; set; } 43 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/EventTracking.cs: -------------------------------------------------------------------------------- 1 | using GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 2 | using JetBrains.Annotations; 3 | 4 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 5 | 6 | [PublicAPI] 7 | public class EventTracking : GeneralParameters, IEventTrackingParameters 8 | { 9 | #region Overrides of GeneralParameters 10 | 11 | /// 12 | /// The type of hit. Must be one of 'pageview', 'screenview', 'event', 'transaction', 'item', 'social', 'exception', 'timing'. 13 | /// Required for all hit types 14 | /// HitType.Pageview 15 | /// 16 | public override HitType HitType => HitType.Event; 17 | 18 | #endregion 19 | 20 | #region Implementation of IEventTrackingParameters 21 | 22 | /// 23 | /// Specifies the event category. Must not be empty. 24 | /// Required for event hit type 25 | /// Category 26 | /// 27 | [Beacon("ec", true)] 28 | public string? Category { get; set; } 29 | 30 | /// 31 | /// Specifies the event action. Must not be empty. 32 | /// Required for event hit type 33 | /// Action 34 | /// 35 | [Beacon("ea", true)] 36 | public string? Action { get; set; } 37 | 38 | /// 39 | /// Specifies the event label. 40 | /// Optional for event hit type 41 | /// Label 42 | /// 43 | [Beacon("el")] 44 | public string? Label { get; set; } 45 | 46 | /// 47 | /// Specifies the event value. Values must be non-negative. 48 | /// 55 49 | /// 50 | [Beacon("ev")] 51 | public long? Value { get; set; } 52 | 53 | #endregion 54 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/ExceptionTracking.cs: -------------------------------------------------------------------------------- 1 | using GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 2 | using JetBrains.Annotations; 3 | 4 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 5 | 6 | [PublicAPI] 7 | public class ExceptionTracking : GeneralParameters, IExceptionParameters 8 | { 9 | #region Overrides of GeneralParameters 10 | 11 | /// 12 | /// The type of hit. Must be one of 'pageview', 'screenview', 'event', 'transaction', 'item', 'social', 'exception', 'timing'. 13 | /// Required for all hit types 14 | /// HitType.Pageview 15 | /// 16 | public override HitType HitType => HitType.Exception; 17 | 18 | #endregion 19 | 20 | #region Implementation of IExceptionParameters 21 | 22 | /// 23 | /// Specifies the description of an exception. 24 | /// Optional 25 | /// DatabaseError 26 | /// 27 | [Beacon("exd")] 28 | public string? Description { get; set; } 29 | 30 | /// 31 | /// Specifies whether the exception was fatal. 32 | /// Optional, null value means the exception is fatal. 33 | /// false 34 | /// 35 | [Beacon("exf")] 36 | public bool? IsFatal { get; set; } 37 | 38 | #endregion 39 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/HitType.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 4 | 5 | [PublicAPI] 6 | public enum HitType 7 | { 8 | Pageview, 9 | Event, 10 | Social, 11 | Timing, 12 | Screenview, 13 | Transaction, 14 | Item, 15 | Exception, 16 | NonImplemented 17 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/IAppTrackingParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface IAppTrackingParameters 7 | { 8 | /// 9 | /// Specifies the application name. 10 | /// Optional 11 | /// My App 12 | /// 13 | string? ApplicationName { get; set; } 14 | 15 | /// 16 | /// Application identifier. 17 | /// Optional 18 | /// com.company.app 19 | /// 20 | string? ApplicationId { get; set; } 21 | 22 | /// 23 | /// Specifies the application version. 24 | /// Optional 25 | /// 1.2 26 | /// 27 | string? ApplicationVersion { get; set; } 28 | 29 | /// 30 | /// Application installer identifier. 31 | /// Optional 32 | /// com.platform.vending 33 | /// 34 | string? ApplicationInstallerId { get; set; } 35 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/IContentExperimentsParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface IContentExperimentsParameters 7 | { 8 | /// 9 | /// Specifies the experiment id. 10 | /// Required for experiment tracking 11 | /// K7Q-9lpLSd21prp9vIhdoA 12 | /// 13 | string ExperimentId { get; set; } 14 | 15 | /// 16 | /// Specifies the experiment variant id. 17 | /// Required for content experiment tracking 18 | /// 1 19 | /// 20 | string ExperimentVariant { get; set; } 21 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/IContentInformationParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface IContentInformationParameters 7 | { 8 | /// 9 | /// Use this parameter to send the full URL (document location) of the page on which content resides. 10 | /// You can use the &dh and &dp parameters to override the hostname and path + query portions of the document location, accordingly. 11 | /// The JavaScript clients determine this parameter using the concatenation of the document.location.origin + document.location.pathname + document.location.search browser parameters. 12 | /// Be sure to remove any user authentication or other private information from the URL if present. 13 | /// Optional (For 'pageview' hits, either &dl or both &dh and &dp have to be specified for the hit to be valid) 14 | /// http://foo.com/home?a=b 15 | /// 16 | string? DocumentLocationUrl { get; set; } 17 | 18 | /// 19 | /// Specifies the hostname from which content was hosted. 20 | /// Optional 21 | /// foo.com 22 | /// 23 | string? DocumentHostName { get; set; } 24 | 25 | /// 26 | /// he path portion of the page URL. 27 | /// Optional (Should begin with '/'. For 'pageview' hits, either &dl or both &dh and &dp have to be specified for the hit to be valid.) 28 | /// /foo 29 | /// 30 | string? DocumentPath { get; set; } 31 | 32 | /// 33 | /// The title of the page / document. 34 | /// Optional 35 | /// Settings 36 | /// 37 | string? DocumentTitle { get; set; } 38 | 39 | /// 40 | /// If not specified, this will default to the unique URL of the page by either using the &dl parameter as-is or assembling it from &dh and &dp. 41 | /// App tracking makes use of this for the 'Screen Name' of the screenview hit. 42 | /// Optional 43 | /// High Scores 44 | /// 45 | string? ScreenName { get; set; } 46 | 47 | /// 48 | /// The ID of a clicked DOM element, used to disambiguate multiple links to the same URL in In-Page Analytics reports when Enhanced Link Attribution is enabled for the property. 49 | /// Optional 50 | /// nav_bar 51 | /// 52 | string? LinkId { get; set; } 53 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/ICustomDimension.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface ICustomDimension 7 | { 8 | /// 9 | /// Index of custom dimension (from 1 to 200). 10 | /// 11 | int Id { get; set; } 12 | 13 | /// 14 | /// Parameter name of the custom dimension. 15 | /// cd4 16 | /// 17 | string Name { get; } 18 | 19 | /// 20 | /// Value of custom dimension (max 150 bytes). 21 | /// 22 | string Value { get; set; } 23 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/ICustomDimensionParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | /// 6 | /// Custom dimension parameters. Currently, 20 indices for the standard GA account is supported only. 7 | /// 8 | [PublicAPI] 9 | public interface ICustomDimensionParameters 10 | { 11 | string? CustomDimension1 { get; set; } 12 | 13 | string? CustomDimension2 { get; set; } 14 | 15 | string? CustomDimension3 { get; set; } 16 | 17 | string? CustomDimension4 { get; set; } 18 | 19 | string? CustomDimension5 { get; set; } 20 | 21 | string? CustomDimension6 { get; set; } 22 | 23 | string? CustomDimension7 { get; set; } 24 | 25 | string? CustomDimension8 { get; set; } 26 | 27 | string? CustomDimension9 { get; set; } 28 | 29 | string? CustomDimension10 { get; set; } 30 | 31 | string? CustomDimension11 { get; set; } 32 | 33 | string? CustomDimension12 { get; set; } 34 | 35 | string? CustomDimension13 { get; set; } 36 | 37 | string? CustomDimension14 { get; set; } 38 | 39 | string? CustomDimension15 { get; set; } 40 | 41 | string? CustomDimension16 { get; set; } 42 | 43 | string? CustomDimension17 { get; set; } 44 | 45 | string? CustomDimension18 { get; set; } 46 | 47 | string? CustomDimension19 { get; set; } 48 | 49 | string? CustomDimension20 { get; set; } 50 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/ICustomMetricParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | /// 6 | /// Custom metric parameters. Currently, 20 indices for the standard GA account is supported only. 7 | /// 8 | [PublicAPI] 9 | public interface ICustomMetricParameters 10 | { 11 | long? CustomMetric1 { get; set; } 12 | 13 | long? CustomMetric2 { get; set; } 14 | 15 | long? CustomMetric3 { get; set; } 16 | 17 | long? CustomMetric4 { get; set; } 18 | 19 | long? CustomMetric5 { get; set; } 20 | 21 | long? CustomMetric6 { get; set; } 22 | 23 | long? CustomMetric7 { get; set; } 24 | 25 | long? CustomMetric8 { get; set; } 26 | 27 | long? CustomMetric9 { get; set; } 28 | 29 | long? CustomMetric10 { get; set; } 30 | 31 | long? CustomMetric11 { get; set; } 32 | 33 | long? CustomMetric12 { get; set; } 34 | 35 | long? CustomMetric13 { get; set; } 36 | 37 | long? CustomMetric14 { get; set; } 38 | 39 | long? CustomMetric15 { get; set; } 40 | 41 | long? CustomMetric16 { get; set; } 42 | 43 | long? CustomMetric17 { get; set; } 44 | 45 | long? CustomMetric18 { get; set; } 46 | 47 | long? CustomMetric19 { get; set; } 48 | 49 | long? CustomMetric20 { get; set; } 50 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/IECommerceItemParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface IECommerceItemParameters 7 | { 8 | /// 9 | /// Specifies the item name. 10 | /// Required for item hit type. 11 | /// Shoe 12 | /// 13 | string ItemName { get; set; } 14 | 15 | /// 16 | /// Specifies the price for a single item / unit. 17 | /// Optional 18 | /// 3.50 19 | /// 20 | decimal ItemPrice { get; set; } 21 | 22 | /// 23 | /// Specifies the number of items purchased. 24 | /// Optional 25 | /// 4 26 | /// 27 | long ItemQuantity { get; set; } 28 | 29 | /// 30 | /// Specifies the SKU or item code. 31 | /// Optional 32 | /// SKU47 33 | /// 34 | string? ItemCode { get; set; } 35 | 36 | /// 37 | /// Specifies the category that the item belongs to. 38 | /// Optional 39 | /// Blue 40 | /// 41 | string? ItemCategory { get; set; } 42 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/IECommerceParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface IECommerceParameters 7 | { 8 | /// 9 | /// A unique identifier for the transaction. 10 | /// This value should be the same for both the Transaction hit and Items hits associated to the particular transaction. 11 | /// Required for both transaction and item hit type. 12 | /// OD564 13 | /// 14 | string TransactionId { get; set; } 15 | 16 | /// 17 | /// When present indicates the local currency for all transaction currency values. 18 | /// Value should be a valid ISO 4217 currency code. 19 | /// Optional 20 | /// EUR 21 | /// 22 | string? CurrencyCode { get; set; } //TODO: Implement enum based 23 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/IECommerceTransactionParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface IECommerceTransactionParameters 7 | { 8 | /// 9 | /// Specifies the affiliation or store name. 10 | /// Optional 11 | /// Member 12 | /// 13 | string? TransactionAffiliation { get; set; } 14 | 15 | /// 16 | /// Specifies the total revenue associated with the transaction. This value should include any shipping or tax costs. 17 | /// Optional 18 | /// 15.47 19 | /// 20 | decimal TransactionRevenue { get; set; } 21 | 22 | /// 23 | /// Specifies the total shipping cost of the transaction. 24 | /// Optional 25 | /// 3.50 26 | /// 27 | decimal TransactionShipping { get; set; } 28 | 29 | /// 30 | /// Specifies the total tax of the transaction. 31 | /// Optional 32 | /// 11.20 33 | /// 34 | decimal TransactionTax { get; set; } 35 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/IEnhancedECommerceProduct.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using JetBrains.Annotations; 3 | 4 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 5 | 6 | [PublicAPI] 7 | public interface IEnhancedECommerceProduct : IProvideBeaconParameters 8 | { 9 | /// 10 | /// The SKU or identifier of the product. 11 | /// Optional 12 | /// P12345 13 | /// 14 | string? Sku { get; set; } 15 | 16 | /// 17 | /// The name of the product. 18 | /// Optional 19 | /// Android T-Shirt 20 | /// 21 | string? Name { get; set; } 22 | 23 | /// 24 | /// The brand associated with the product. 25 | /// Optional 26 | /// Google 27 | /// 28 | string? Brand { get; set; } 29 | 30 | /// 31 | /// The category to which the product belongs. 32 | /// Optional. Can be hierarchical with the "/" delimiter up to 5 levels. 33 | /// Apparel/Men/T-Shirts 34 | /// 35 | string? Category { get; set; } 36 | 37 | /// 38 | /// The variant of the product. 39 | /// Optional 40 | /// Black 41 | /// 42 | string? Variant { get; set; } 43 | 44 | /// 45 | /// The unit price of a product. 46 | /// Optional 47 | /// 29.20 48 | /// 49 | decimal? Price { get; set; } 50 | 51 | /// 52 | /// The quantity of a product. 53 | /// Optional 54 | /// 2 55 | /// 56 | int? Quantity { get; set; } 57 | 58 | /// 59 | /// The coupon code associated with a product. 60 | /// Optional 61 | /// SUMMER_SALE13 62 | /// 63 | string? ProductCouponCode { get; set; } 64 | 65 | /// 66 | /// The product's position in a list or collection. 67 | /// Optional 68 | /// 2 69 | /// 70 | int? Position { get; set; } 71 | 72 | /// 73 | /// Add a custom dimension value with check if this dimension value already set. 74 | /// 75 | /// 76 | /// 77 | void AddCustomDimension(int id, string value); 78 | 79 | /// 80 | /// Get all custom dimensions of product 81 | /// 82 | /// 83 | List GetCustomDimensions(); 84 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/IEnhancedECommerceTransactionParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface IEnhancedECommerceTransactionParameters : IECommerceTransactionParameters, IProvideProductsParameters 7 | { 8 | /// 9 | /// Specifies the transaction coupon redeemed with the transaction. 10 | /// Optional. Can be sent when Product Action is set to 'purchase' or 'refund'. 11 | /// SUMMER08 12 | /// 13 | string? CouponCode { get; set; } 14 | 15 | /// 16 | /// Specifies the list or collection from which a product action occurred. 17 | /// Optional. Can be sent when Product Action is set to 'detail' or 'click'. 18 | /// Search Results 19 | /// 20 | string? ProductActionList { get; set; } 21 | 22 | /// 23 | /// Specifies the list or collection from which a product action occurred. 24 | /// Optional. Can be sent when Product Action is set to 'detail' or 'click'. 25 | /// 2 26 | /// 27 | int? CheckoutStep { get; set; } 28 | 29 | /// 30 | /// Additional information about a checkout step. 31 | /// Optional. Can be sent when Product Action is set to 'checkout'. 32 | /// Visa 33 | /// 34 | string? CheckoutStepOption { get; set; } 35 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/IEventTrackingParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface IEventTrackingParameters 7 | { 8 | /// 9 | /// Specifies the event category. Must not be empty. 10 | /// Optional 11 | /// Category 12 | /// 13 | string? Category { get; set; } 14 | 15 | /// 16 | /// Specifies the event action. Must not be empty. 17 | /// Optional 18 | /// Action 19 | /// 20 | string? Action { get; set; } 21 | 22 | /// 23 | /// Specifies the event label. 24 | /// Optional 25 | /// Label 26 | /// 27 | string? Label { get; set; } 28 | 29 | /// 30 | /// Specifies the event value. Values must be non-negative. 31 | /// Optional 32 | /// 55 33 | /// 34 | long? Value { get; set; } 35 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/IExceptionParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface IExceptionParameters 7 | { 8 | /// 9 | /// Specifies the description of an exception. 10 | /// Optional 11 | /// DatabaseError 12 | /// 13 | string? Description { get; set; } 14 | 15 | /// 16 | /// Specifies whether the exception was fatal. 17 | /// Optional, null value means the exception is fatal. 18 | /// false 19 | /// 20 | bool? IsFatal { get; set; } 21 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/IGeneralParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface IGeneralParameters : IHitParameters, IUserParameters, ISystemInfoParameters, 7 | IContentInformationParameters, 8 | ISessionParameters, ITrafficSourcesParameters, 9 | IAppTrackingParameters, ICustomDimensionParameters, 10 | ICustomMetricParameters, IProvideBeaconParameters 11 | { 12 | /// 13 | /// The Protocol version. The current value is '1'. This will only change when there are changes made that are not backwards compatible. 14 | /// Required for all hit types 15 | /// 16 | string ProtocolVersion { get; } 17 | 18 | /// 19 | /// The tracking ID / web property ID. The format is UA-XXXX-Y. All collected data is associated by this ID. 20 | /// Required for all hit types 21 | /// UA-XXXX-Y 22 | /// 23 | string? TrackingId { get; set; } 24 | 25 | /// 26 | /// When present, the IP address of the sender will be anonymized. 27 | /// For example, the IP will be anonymized if any of the following parameters are present in the payload: &aip=, &aip=0, or &aip=1 28 | /// Optional 29 | /// GoogleBoolean.True 30 | /// 31 | bool? AnonymizeIp { get; set; } 32 | 33 | /// 34 | /// Used to collect offline / latent hits. 35 | /// The value represents the time delta (in milliseconds) between when the hit being reported occurred and the time the hit was sent. 36 | /// The value must be greater than or equal to 0. Values greater than four hours may lead to hits not being processed. 37 | /// Optional 38 | /// 560 39 | /// 40 | long? QueueTime { get; set; } 41 | 42 | /// 43 | /// Used to send a random number in GET requests to ensure browsers and proxies don't cache hits. 44 | /// It should be sent as the final parameter of the request since we've seen some 3rd party internet filtering software add additional parameters to HTTP requests incorrectly. This value is not used in reporting. 45 | /// Optional 46 | /// 289372387623 47 | /// 48 | string? CacheBuster { get; set; } 49 | 50 | /// 51 | /// Is a formatted user agent string that is used to compute the following dimensions: browser, platform, and mobile capabilities. 52 | /// Required 53 | /// Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405 54 | /// 55 | string? UserAgent { get; set; } 56 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/IHitParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface IHitParameters 7 | { 8 | /// 9 | /// The type of hit. Must be one of 'pageview', 'screenview', 'event', 'transaction', 'item', 'social', 'exception', 'timing'. 10 | /// Required for all hit types 11 | /// HitType.Pageview 12 | /// 13 | HitType HitType { get; } 14 | 15 | /// 16 | /// Specifies that a hit be considered non-interactive. 17 | /// Optional 18 | /// GoogleBoolean.True 19 | /// 20 | bool? NonInteractionHit { get; set; } 21 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/IProvideBeaconParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | /// 6 | /// Marker interface used by GoogleAnalyticsTracker to suggest 7 | /// beacon parameters can be extracted from a given object. 8 | /// 9 | [PublicAPI] 10 | public interface IProvideBeaconParameters 11 | { 12 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/IProvideProductsParameters.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using JetBrains.Annotations; 3 | 4 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 5 | 6 | /// 7 | /// Marker interface used by GoogleAnalyticsTracker to suggest 8 | /// beacon parameters can be extracted from a given object. 9 | /// 10 | [PublicAPI] 11 | public interface IProvideProductsParameters : IProvideBeaconParameters 12 | { 13 | /// 14 | /// List of products to send in scope of this transaction. 15 | /// Optional 16 | /// 17 | List Products { get; set; } 18 | 19 | /// 20 | /// Specifies the role of the products included in a hit. 21 | /// If a product action is not specified, all product definitions included with the hit will be ignored. 22 | /// Optional 23 | /// purchase 24 | /// 25 | ProductAction ProductAction { get; set; } 26 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/IRefundTrackingParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface IRefundTrackingParameters : IProvideProductsParameters 7 | { 8 | /// 9 | /// Specifies the transaction Id to refund. Must not be empty. 10 | /// Required for refund 11 | /// T12345 12 | /// 13 | string TransactionId { get; set; } 14 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/ISessionParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface ISessionParameters 7 | { 8 | /// 9 | /// Used to control the session duration. 10 | /// A value of 'start' forces a new session to start with this hit and 'end' forces the current session to end with this hit. 11 | /// All other values are ignored. 12 | /// Optional 13 | /// SessionControl.Start 14 | /// 15 | SessionControl? SessionControl { get; set; } 16 | 17 | /// 18 | /// The IP address of the user. This should be a valid IP address. It will always be anonymized just as though &aip (anonymize IP) had been used. 19 | /// Optional 20 | /// 1.2.3.4 21 | /// 22 | string? IpOverride { get; set; } 23 | 24 | /// 25 | /// The User Agent of the browser. Note that Google has libraries to identify real user agents. 26 | /// Hand crafting your own agent could break at any time. 27 | /// Optional 28 | /// Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14 29 | /// 30 | string? UserAgentOverride { get; set; } 31 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/ISocialInteractionsParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface ISocialInteractionsParameters 7 | { 8 | /// 9 | /// Specifies the social network, for example Facebook or Google Plus. 10 | /// Required for social hit type. 11 | /// facebook 12 | /// 13 | string SocialNetwork { get; set; } 14 | 15 | /// 16 | /// Specifies the social interaction action. For example on Google Plus when a user clicks the +1 button, the social action is 'plus'. 17 | /// Required for social hit type. 18 | /// like 19 | /// 20 | string SocialAction { get; set; } 21 | 22 | /// 23 | /// Specifies the target of a social interaction. This value is typically a URL but can be any text. 24 | /// Required for social hit type. 25 | /// http://foo.com 26 | /// 27 | string SocialActionTarget { get; set; } 28 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/ISystemInfoParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface ISystemInfoParameters 7 | { 8 | /// 9 | /// Specifies the screen resolution 10 | /// Optional 11 | /// 800x600 12 | /// 13 | string? ScreenResolution { get; set; } 14 | 15 | /// 16 | /// Specifies the viewable area of the browser / device. 17 | /// Optional 18 | /// 123x456 19 | /// 20 | string? ViewportSize { get; set; } 21 | 22 | /// 23 | /// Specifies the character set used to encode the page / document. 24 | /// Optional 25 | /// UTF-8 26 | /// 27 | string? DocumentEncoding { get; set; } 28 | 29 | /// 30 | /// Specifies the screen color depth. 31 | /// Optional 32 | /// 24-bits 33 | /// 34 | string? ScreenColors { get; set; } 35 | 36 | /// 37 | /// Specifies the language. 38 | /// Optional 39 | /// en-us 40 | /// 41 | string? UserLanguage { get; set; } 42 | 43 | /// 44 | /// Specifies whether Java was enabled. 45 | /// Optional 46 | /// GoogleBoolean.True 47 | /// 48 | bool? JavaEnabled { get; set; } 49 | 50 | /// 51 | /// Specifies the flash version. 52 | /// Optional 53 | /// 10 1 r103 54 | /// 55 | string? FlashVersion { get; set; } 56 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/ITimingParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface ITimingParameters 7 | { 8 | /// 9 | /// Specifies the user timing category. 10 | /// Optional 11 | /// category 12 | /// 13 | string? UserTimingCategory { get; set; } 14 | 15 | /// 16 | /// Specifies the user timing variable. 17 | /// Optional 18 | /// lookup 19 | /// 20 | string? UserTimingVariable { get; set; } 21 | 22 | /// 23 | /// Specifies the user timing value. The value is in milliseconds. 24 | /// Optional 25 | /// 123 26 | /// 27 | long UserTimingTime { get; set; } 28 | 29 | /// 30 | /// Specifies the user timing label. 31 | /// Optional 32 | /// label 33 | /// 34 | string? UserTimingLabel { get; set; } 35 | 36 | /// 37 | /// Specifies the time it took for a page to load. The value is in milliseconds. 38 | /// Optional 39 | /// 3554 40 | /// 41 | long? PageLoadTime { get; set; } 42 | 43 | /// 44 | /// Specifies the time it took to do a DNS lookup.The value is in milliseconds. 45 | /// Optional 46 | /// 43 47 | /// 48 | long? DnsTime { get; set; } 49 | 50 | /// 51 | /// Specifies the time it took for the page to be downloaded. The value is in milliseconds. 52 | /// Optional 53 | /// 500 54 | /// 55 | long? PageDownloadTime { get; set; } 56 | 57 | /// 58 | /// Specifies the time it took for any redirects to happen. The value is in milliseconds. 59 | /// Optional 60 | /// 500 61 | /// 62 | long? RedirectResponseTime { get; set; } 63 | 64 | /// 65 | /// Specifies the time it took for a TCP connection to be made. The value is in milliseconds. 66 | /// Optional 67 | /// 500 68 | /// 69 | long? TcpConnectTime { get; set; } 70 | 71 | /// 72 | /// Specifies the time it took for the server to respond after the connect time. The value is in milliseconds. 73 | /// Optional 74 | /// 500 75 | /// 76 | long? ServerResponseTime { get; set; } 77 | 78 | /// 79 | /// Specifies the time it took for Document.readyState to be 'interactive'. The value is in milliseconds. 80 | /// Optional 81 | /// 500 82 | /// 83 | long? DomInteractiveTime { get; set; } 84 | 85 | /// 86 | /// Specifies the time it took for the DOMContentLoaded Event to fire. The value is in milliseconds. 87 | /// Optional 88 | /// 500 89 | /// 90 | long? ContentLoadTime { get; set; } 91 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/ITrafficSourcesParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface ITrafficSourcesParameters 7 | { 8 | /// 9 | /// Specifies which referral source brought traffic to a website. 10 | /// This value is also used to compute the traffic source. The format of this value is a URL. 11 | /// Optional 12 | /// http://example.com 13 | /// 14 | string? DocumentReferrer { get; set; } 15 | 16 | /// 17 | /// Specifies the campaign name. 18 | /// Optional 19 | /// (direct) 20 | /// 21 | string? CampaignName { get; set; } 22 | 23 | /// 24 | /// Specifies the campaign source. 25 | /// Optional 26 | /// (direct) 27 | /// 28 | string? CampaignSource { get; set; } 29 | 30 | /// 31 | /// Specifies the campaign medium. 32 | /// Optional 33 | /// organic 34 | /// 35 | string? CampaignMedium { get; set; } 36 | 37 | /// 38 | /// Specifies the campaign keyword. 39 | /// Optional 40 | /// Blue Shoes 41 | /// 42 | string? CampaignKeyword { get; set; } 43 | 44 | /// 45 | /// Specifies the campaign content. 46 | /// Optional 47 | /// content 48 | /// 49 | string? CampaignContent { get; set; } 50 | 51 | /// 52 | /// Specifies the campaign ID. 53 | /// Optional 54 | /// ID 55 | /// 56 | string? CampaignId { get; set; } 57 | 58 | /// 59 | /// Specifies the Google AdWords Id. 60 | /// Optional 61 | /// CL6Q-OXyqKUCFcgK2goddQuoHg 62 | /// 63 | string? GoogleAdWordsId { get; set; } 64 | 65 | /// 66 | /// Specifies the Google Display Ads Id. 67 | /// Optional 68 | /// d_click_id 69 | /// 70 | string? GoogleDisplayAdsId { get; set; } 71 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/Interface/IUserParameters.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 4 | 5 | [PublicAPI] 6 | public interface IUserParameters 7 | { 8 | /// 9 | /// This anonymously identifies a particular user, device, or browser instance. 10 | /// For the web, this is generally stored as a first-party cookie with a two-year expiration. For mobile apps, this is randomly generated for each particular instance of an application install. 11 | /// The value of this field should be a random UUID (version 4) as described in http://www.ietf.org/rfc/rfc4122.txt 12 | /// Required for all hit types 13 | /// 35009a79-1a05-49d7-b876-2b884d0f825b 14 | /// 15 | string? ClientId { get; set; } 16 | 17 | /// 18 | /// This is intended to be a known identifier for a user provided by the site owner/tracking library user. 19 | /// It may not itself be PII (personally identifiable information). 20 | /// The value should never be persisted in GA cookies or other Analytics provided storage. 21 | /// Optional 22 | /// as8eknlll 23 | /// 24 | string? UserId { get; set; } 25 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/PageView.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 4 | 5 | [PublicAPI] 6 | public class PageView : GeneralParameters 7 | { 8 | #region Overrides of GeneralParameters 9 | 10 | /// 11 | /// The type of hit. Must be one of 'pageview', 'screenview', 'event', 'transaction', 'item', 'social', 'exception', 'timing'. 12 | /// Required for all hit types 13 | /// HitType.Pageview 14 | /// 15 | public override HitType HitType => HitType.Pageview; 16 | 17 | #endregion 18 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/ProductAction.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 4 | 5 | [PublicAPI] 6 | public enum ProductAction 7 | { 8 | Detail, 9 | Click, 10 | Add, 11 | Remove, 12 | Checkout, 13 | CheckoutOption, 14 | Purchase, 15 | Refund 16 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/RefundTracking.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 3 | using JetBrains.Annotations; 4 | 5 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 6 | 7 | [PublicAPI] 8 | public class RefundTracking : EventTracking, IRefundTrackingParameters 9 | { 10 | public RefundTracking(string transactionId) 11 | { 12 | TransactionId = transactionId; 13 | Category = "Ecommerce"; 14 | Action = "Refund"; 15 | Products = new List(); 16 | ProductAction = ProductAction.Refund; 17 | } 18 | 19 | #region Overrides of GeneralParameters 20 | 21 | public override HitType HitType => HitType.Event; 22 | 23 | #endregion 24 | 25 | #region Implementation of IRefundTrackingParameters 26 | 27 | [Beacon("ti", true)] 28 | public string TransactionId { get; set; } 29 | 30 | [Beacon("pa", true)] 31 | public ProductAction ProductAction { get; set; } 32 | 33 | public List Products { get; set; } 34 | 35 | #endregion 36 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/ScreenviewTracking.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 4 | 5 | [PublicAPI] 6 | public class ScreenviewTracking : GeneralParameters 7 | { 8 | #region Overrides of GeneralParameters 9 | 10 | /// 11 | /// The type of hit. Must be one of 'pageview', 'screenview', 'event', 'transaction', 'item', 'social', 'exception', 'timing'. 12 | /// Required for all hit types 13 | /// HitType.Pageview 14 | /// 15 | public override HitType HitType => HitType.Screenview; 16 | 17 | #endregion Overrides of GeneralParameters 18 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/SessionControl.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 4 | 5 | [PublicAPI] 6 | public enum SessionControl 7 | { 8 | Start, 9 | End 10 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/SocialInteractionsParameters.cs: -------------------------------------------------------------------------------- 1 | using GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 2 | using JetBrains.Annotations; 3 | 4 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 5 | 6 | [PublicAPI] 7 | public class SocialInteractionsParameters : GeneralParameters, ISocialInteractionsParameters 8 | { 9 | /// 10 | /// Creates a new . 11 | /// 12 | /// Specifies the social network, for example Facebook or Google Plus. 13 | /// Specifies the social interaction action. For example on Google Plus when a user clicks the +1 button, the social action is 'plus'. 14 | /// Specifies the target of a social interaction. This value is typically a URL but can be any text. 15 | public SocialInteractionsParameters( 16 | string socialNetwork, 17 | string socialAction, 18 | string socialActionTarget) 19 | { 20 | SocialNetwork = socialNetwork; 21 | SocialAction = socialAction; 22 | SocialActionTarget = socialActionTarget; 23 | } 24 | 25 | #region Overrides of GeneralParameters 26 | 27 | /// 28 | /// The type of hit. Must be one of 'pageview', 'screenview', 'event', 'transaction', 'item', 'social', 'exception', 'timing'. 29 | /// Required for all hit types 30 | /// HitType.Pageview 31 | /// 32 | public override HitType HitType => HitType.Social; 33 | 34 | #endregion 35 | 36 | #region Implementation of ISocialInteractionsParameters 37 | 38 | /// 39 | /// Specifies the social network, for example Facebook or Google Plus. 40 | /// Required for social hit type. 41 | /// facebook 42 | /// 43 | [Beacon("sn", true)] 44 | public string SocialNetwork { get; set; } 45 | 46 | /// 47 | /// Specifies the social interaction action. For example on Google Plus when a user clicks the +1 button, the social action is 'plus'. 48 | /// Required for social hit type. 49 | /// like 50 | /// 51 | [Beacon("sa", true)] 52 | public string SocialAction { get; set; } 53 | 54 | /// 55 | /// Specifies the target of a social interaction. This value is typically a URL but can be any text. 56 | /// Required for social hit type. 57 | /// http://foo.com 58 | /// 59 | [Beacon("st", true)] 60 | public string SocialActionTarget { get; set; } 61 | 62 | #endregion 63 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackerParameters/UserTimings.cs: -------------------------------------------------------------------------------- 1 | using GoogleAnalyticsTracker.Core.TrackerParameters.Interface; 2 | using JetBrains.Annotations; 3 | 4 | namespace GoogleAnalyticsTracker.Core.TrackerParameters; 5 | 6 | [PublicAPI] 7 | public class UserTimings : GeneralParameters, ITimingParameters 8 | { 9 | public UserTimings() 10 | { 11 | NonInteractionHit = true; 12 | } 13 | 14 | #region Overrides of GeneralParameters 15 | 16 | /// 17 | /// The type of hit. Must be one of 'pageview', 'screenview', 'event', 'transaction', 'item', 'social', 'exception', 'timing'. 18 | /// Required for all hit types 19 | /// HitType.Pageview 20 | /// 21 | public override HitType HitType => HitType.Timing; 22 | 23 | #endregion 24 | 25 | #region Implementation of ITimingParameters 26 | 27 | /// 28 | /// Specifies the user timing category. 29 | /// Required for timing hit type 30 | /// category 31 | /// 32 | [Beacon("utc", true)] 33 | public string? UserTimingCategory { get; set; } 34 | 35 | /// 36 | /// Specifies the user timing variable. 37 | /// Required for timing hit type 38 | /// lookup 39 | /// 40 | [Beacon("utv", true)] 41 | public string? UserTimingVariable { get; set; } 42 | 43 | /// 44 | /// Specifies the user timing value. The value is in milliseconds. 45 | /// Required for timing hit type 46 | /// 123 47 | /// 48 | [Beacon("utt", true)] 49 | public long UserTimingTime { get; set; } 50 | 51 | /// 52 | /// Specifies the user timing label. 53 | /// Optional for timing hit type 54 | /// label 55 | /// 56 | [Beacon("utl")] 57 | public string? UserTimingLabel { get; set; } 58 | 59 | /// 60 | /// Specifies the time it took for a page to load. The value is in milliseconds. 61 | /// Optional 62 | /// 3554 63 | /// 64 | [Beacon("plt")] 65 | public long? PageLoadTime { get; set; } 66 | 67 | /// 68 | /// Specifies the time it took to do a DNS lookup.The value is in milliseconds. 69 | /// Optional 70 | /// 43 71 | /// 72 | [Beacon("dns")] 73 | public long? DnsTime { get; set; } 74 | 75 | /// 76 | /// Specifies the time it took for the page to be downloaded. The value is in milliseconds. 77 | /// Optional 78 | /// 500 79 | /// 80 | [Beacon("pdt")] 81 | public long? PageDownloadTime { get; set; } 82 | 83 | /// 84 | /// Specifies the time it took for any redirects to happen. The value is in milliseconds. 85 | /// Optional 86 | /// 500 87 | /// 88 | [Beacon("rrt")] 89 | public long? RedirectResponseTime { get; set; } 90 | 91 | /// 92 | /// Specifies the time it took for a TCP connection to be made. The value is in milliseconds. 93 | /// Optional 94 | /// 500 95 | /// 96 | [Beacon("tcp")] 97 | public long? TcpConnectTime { get; set; } 98 | 99 | /// 100 | /// Specifies the time it took for the server to respond after the connect time. The value is in milliseconds. 101 | /// Optional 102 | /// 500 103 | /// 104 | [Beacon("srt")] 105 | public long? ServerResponseTime { get; set; } 106 | 107 | /// 108 | /// Specifies the time it took for Document.readyState to be 'interactive'. The value is in milliseconds. 109 | /// Optional 110 | /// 500 111 | /// 112 | [Beacon("dit")] 113 | public long? DomInteractiveTime { get; set; } 114 | 115 | /// 116 | /// Specifies the time it took for the DOMContentLoaded Event to fire. The value is in milliseconds. 117 | /// Optional 118 | /// 500 119 | /// 120 | [Beacon("clt")] 121 | public long? ContentLoadTime { get; set; } 122 | 123 | #endregion 124 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Core/TrackingResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using JetBrains.Annotations; 4 | 5 | namespace GoogleAnalyticsTracker.Core; 6 | 7 | [PublicAPI] 8 | public class TrackingResult 9 | { 10 | public TrackingResult( 11 | string url, 12 | IDictionary parameters, 13 | string query) 14 | { 15 | Url = url; 16 | Parameters = parameters; 17 | Query = query; 18 | } 19 | 20 | public string Url { get; set; } 21 | public IDictionary Parameters { get; set; } 22 | public string Query { get; set; } 23 | public bool Success { get; set; } 24 | public Exception? Exception { get; set; } 25 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using GoogleAnalyticsTracker.AspNet; 3 | using Microsoft.AspNetCore.Mvc; 4 | using GoogleAnalyticsTracker.SampleWebApplication.Models; 5 | 6 | namespace GoogleAnalyticsTracker.SampleWebApplication.Controllers; 7 | 8 | [TrackPageView(Enabled = false)] 9 | public class HomeController : Controller 10 | { 11 | [TrackPageView(Enabled = true, CustomTitle = "HOME - INDEX")] 12 | public IActionResult Index() 13 | { 14 | return View(); 15 | } 16 | 17 | [TrackPageView(Enabled = false, CustomTitle = "HOME - ABOUT")] 18 | [TrackEvent] 19 | public IActionResult About() 20 | { 21 | ViewData["Message"] = "Your application description page."; 22 | 23 | return View(); 24 | } 25 | 26 | [TrackPageView(Enabled = true)] 27 | public IActionResult Contact() 28 | { 29 | ViewData["Message"] = "Your contact page."; 30 | 31 | return View(); 32 | } 33 | 34 | [TrackPageView(Enabled = true)] 35 | public IActionResult Privacy() 36 | { 37 | return View(); 38 | } 39 | 40 | [TrackPageView(Enabled = false)] 41 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 42 | public IActionResult Error() 43 | { 44 | return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); 45 | } 46 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/GoogleAnalyticsTracker.SampleWebApplication.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1;net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/GoogleAnalyticsTracker.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartenba/GoogleAnalyticsTracker/cd2842e2efcfb2eb74379ee84b1ac89048cb49f8/GoogleAnalyticsTracker.SampleWebApplication/GoogleAnalyticsTracker.snk -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/Models/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace GoogleAnalyticsTracker.SampleWebApplication.Models; 2 | 3 | public class ErrorViewModel 4 | { 5 | public string RequestId { get; set; } = string.Empty; 6 | 7 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 8 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace GoogleAnalyticsTracker.SampleWebApplication; 5 | 6 | // ReSharper disable once ClassNeverInstantiated.Global 7 | public class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | CreateHostBuilder(args).Build().Run(); 12 | } 13 | 14 | public static IHostBuilder CreateHostBuilder(string[] args) => 15 | Host.CreateDefaultBuilder(args) 16 | .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); 17 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:57116", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "GoogleAnalyticsTracker.SampleWebApplication": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "applicationUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/Startup.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using GoogleAnalyticsTracker.AspNet; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Hosting; 9 | 10 | namespace GoogleAnalyticsTracker.SampleWebApplication; 11 | 12 | [SuppressMessage("ReSharper", "ArgumentsStyleStringLiteral")] 13 | public class Startup 14 | { 15 | public Startup(IConfiguration configuration) 16 | { 17 | Configuration = configuration; 18 | } 19 | 20 | // ReSharper disable once MemberCanBePrivate.Global 21 | // ReSharper disable once UnusedAutoPropertyAccessor.Global 22 | public IConfiguration Configuration { get; } 23 | 24 | // This method gets called by the runtime. Use this method to add services to the container. 25 | public void ConfigureServices(IServiceCollection services) 26 | { 27 | services.AddRouting(options => options.LowercaseUrls = true); 28 | 29 | services.Configure(options => 30 | { 31 | // This lambda determines whether user consent for non-essential cookies is needed for a given request. 32 | options.CheckConsentNeeded = _ => true; 33 | options.MinimumSameSitePolicy = SameSiteMode.None; 34 | }); 35 | 36 | services.AddGoogleAnalyticsTracker(options => 37 | { 38 | // ReSharper disable once StringLiteralTypo 39 | options.TrackerId = "UA-XXXXXX-XX"; 40 | options.ShouldTrackRequestInMiddleware = TrackRequests.OnlyWhenNotYetTracked; 41 | }); 42 | 43 | services.AddMvc(); 44 | } 45 | 46 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 47 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 48 | { 49 | app.UseCookiePolicy(); 50 | app.UseRouting(); 51 | 52 | app.UseGoogleAnalyticsTracker(); 53 | 54 | if (env.IsDevelopment()) 55 | { 56 | app.UseDeveloperExceptionPage(); 57 | } 58 | else 59 | { 60 | app.UseExceptionHandler("/Home/Error"); 61 | } 62 | 63 | app.UseStaticFiles(); 64 | app.UseCookiePolicy(); 65 | 66 | app.UseEndpoints(endpoints => 67 | { 68 | 69 | endpoints.MapControllerRoute( 70 | name: "default", 71 | pattern: "{controller=Home}/{action=Index}/{id?}"); 72 | 73 | endpoints.MapRazorPages(); 74 | }); 75 | } 76 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/Views/Home/About.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "About"; 3 | } 4 |

@ViewData["Title"]

5 |

@ViewData["Message"]

6 | 7 |

Use this area to provide additional information.

8 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/Views/Home/Contact.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Contact"; 3 | } 4 |

@ViewData["Title"]

5 |

@ViewData["Message"]

6 | 7 |
8 | One Microsoft Way
9 | Redmond, WA 98052-6399
10 | P: 11 | 425.555.0100 12 |
13 | 14 |
15 | Support: Support@example.com
16 | Marketing: Marketing@example.com 17 |
18 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | 5 | 55 | 56 | 95 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/Views/Home/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Privacy Policy"; 3 | } 4 |

@ViewData["Title"]

5 | 6 |

Use this page to detail your site's privacy policy.

7 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model ErrorViewModel 2 | @{ 3 | ViewData["Title"] = "Error"; 4 | } 5 | 6 |

Error.

7 |

An error occurred while processing your request.

8 | 9 | @if (Model.ShowRequestId) 10 | { 11 |

12 | Request ID: @Model.RequestId 13 |

14 | } 15 | 16 |

Development Mode

17 |

18 | Swapping to Development environment will display more detailed information about the error that occurred. 19 |

20 |

21 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 22 |

23 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/Views/Shared/_CookieConsentPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Http.Features 2 | 3 | @{ 4 | var consentFeature = Context.Features.Get(); 5 | var showBanner = !consentFeature?.CanTrack ?? false; 6 | var cookieString = consentFeature?.CreateConsentCookie(); 7 | } 8 | 9 | @if (showBanner) 10 | { 11 | 33 | 41 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - GoogleAnalyticsTracker.SampleWebApplication 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 40 | 41 | 42 | 43 |
44 | @RenderBody() 45 |
46 |
47 |

GoogleAnalyticsTracker.SampleWebApplication

48 |
49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 69 | 70 | 71 | 72 | @* ReSharper disable once ArgumentsStyleLiteral *@ 73 | @* ReSharper disable once MethodHasAsyncOverload *@ 74 | @RenderSection("Scripts", required: false) 75 | 76 | 77 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using GoogleAnalyticsTracker.SampleWebApplication 2 | @using GoogleAnalyticsTracker.SampleWebApplication.Models 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification\ 2 | for details on configuring this project to bundle and minify static web assets. */ 3 | body { 4 | padding-top: 50px; 5 | padding-bottom: 20px; 6 | } 7 | 8 | /* Wrapping element */ 9 | /* Set some basic padding to keep content from hitting the edges */ 10 | .body-content { 11 | padding-left: 15px; 12 | padding-right: 15px; 13 | } 14 | 15 | /* Carousel */ 16 | .carousel-caption p { 17 | font-size: 20px; 18 | line-height: 1.4; 19 | } 20 | 21 | /* Make .svg files in the carousel display properly in older browsers */ 22 | .carousel-inner .item img[src$=".svg"] { 23 | width: 100%; 24 | } 25 | 26 | /* QR code generator */ 27 | #qrCode { 28 | margin: 15px; 29 | } 30 | 31 | /* Hide/rearrange for smaller screens */ 32 | @media screen and (max-width: 767px) { 33 | /* Hide captions */ 34 | .carousel-caption { 35 | display: none; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/css/site.min.css: -------------------------------------------------------------------------------- 1 | body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}#qrCode{margin:15px}@media screen and (max-width:767px){.carousel-caption{display:none}} -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartenba/GoogleAnalyticsTracker/cd2842e2efcfb2eb74379ee84b1ac89048cb49f8/GoogleAnalyticsTracker.SampleWebApplication/wwwroot/favicon.ico -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/images/banner1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/images/banner2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | // for details on configuring this project to bundle and minify static web assets. 3 | 4 | // Write your JavaScript code. 5 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/js/site.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartenba/GoogleAnalyticsTracker/cd2842e2efcfb2eb74379ee84b1ac89048cb49f8/GoogleAnalyticsTracker.SampleWebApplication/wwwroot/js/site.min.js -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/lib/bootstrap/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bootstrap", 3 | "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", 4 | "keywords": [ 5 | "css", 6 | "js", 7 | "less", 8 | "mobile-first", 9 | "responsive", 10 | "front-end", 11 | "framework", 12 | "web" 13 | ], 14 | "homepage": "http://getbootstrap.com", 15 | "license": "MIT", 16 | "moduleType": "globals", 17 | "main": [ 18 | "less/bootstrap.less", 19 | "dist/js/bootstrap.js" 20 | ], 21 | "ignore": [ 22 | "/.*", 23 | "_config.yml", 24 | "CNAME", 25 | "composer.json", 26 | "CONTRIBUTING.md", 27 | "docs", 28 | "js/tests", 29 | "test-infra" 30 | ], 31 | "dependencies": { 32 | "jquery": "1.9.1 - 3" 33 | }, 34 | "version": "3.3.7", 35 | "_release": "3.3.7", 36 | "_resolution": { 37 | "type": "version", 38 | "tag": "v3.3.7", 39 | "commit": "0b9c4a4007c44201dce9a6cc1a38407005c26c86" 40 | }, 41 | "_source": "https://github.com/twbs/bootstrap.git", 42 | "_target": "v3.3.7", 43 | "_originalSource": "bootstrap", 44 | "_direct": true 45 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2016 Twitter, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartenba/GoogleAnalyticsTracker/cd2842e2efcfb2eb74379ee84b1ac89048cb49f8/GoogleAnalyticsTracker.SampleWebApplication/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartenba/GoogleAnalyticsTracker/cd2842e2efcfb2eb74379ee84b1ac89048cb49f8/GoogleAnalyticsTracker.SampleWebApplication/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartenba/GoogleAnalyticsTracker/cd2842e2efcfb2eb74379ee84b1ac89048cb49f8/GoogleAnalyticsTracker.SampleWebApplication/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartenba/GoogleAnalyticsTracker/cd2842e2efcfb2eb74379ee84b1ac89048cb49f8/GoogleAnalyticsTracker.SampleWebApplication/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/lib/bootstrap/dist/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/lib/jquery-validation-unobtrusive/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-validation-unobtrusive", 3 | "homepage": "https://github.com/aspnet/jquery-validation-unobtrusive", 4 | "version": "3.2.9", 5 | "_release": "3.2.9", 6 | "_resolution": { 7 | "type": "version", 8 | "tag": "v3.2.9", 9 | "commit": "a91f5401898e125f10771c5f5f0909d8c4c82396" 10 | }, 11 | "_source": "https://github.com/aspnet/jquery-validation-unobtrusive.git", 12 | "_target": "^3.2.9", 13 | "_originalSource": "jquery-validation-unobtrusive", 14 | "_direct": true 15 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) .NET Foundation. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | these files except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js: -------------------------------------------------------------------------------- 1 | // Unobtrusive validation support library for jQuery and jQuery Validate 2 | // Copyright (C) Microsoft Corporation. All rights reserved. 3 | // @version v3.2.9 4 | !function(a){"function"==typeof define&&define.amd?define("jquery.validate.unobtrusive",["jquery.validation"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery-validation")):jQuery.validator.unobtrusive=a(jQuery)}(function(a){function e(a,e,n){a.rules[e]=n,a.message&&(a.messages[e]=a.message)}function n(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function t(a){return a.replace(/([!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function r(a){return a.substr(0,a.lastIndexOf(".")+1)}function i(a,e){return 0===a.indexOf("*.")&&(a=a.replace("*.",e)),a}function o(e,n){var r=a(this).find("[data-valmsg-for='"+t(n[0].name)+"']"),i=r.attr("data-valmsg-replace"),o=i?a.parseJSON(i)!==!1:null;r.removeClass("field-validation-valid").addClass("field-validation-error"),e.data("unobtrusiveContainer",r),o?(r.empty(),e.removeClass("input-validation-error").appendTo(r)):e.hide()}function d(e,n){var t=a(this).find("[data-valmsg-summary=true]"),r=t.find("ul");r&&r.length&&n.errorList.length&&(r.empty(),t.addClass("validation-summary-errors").removeClass("validation-summary-valid"),a.each(n.errorList,function(){a("
  • ").html(this.message).appendTo(r)}))}function s(e){var n=e.data("unobtrusiveContainer");if(n){var t=n.attr("data-valmsg-replace"),r=t?a.parseJSON(t):null;n.addClass("field-validation-valid").removeClass("field-validation-error"),e.removeData("unobtrusiveContainer"),r&&n.empty()}}function l(e){var n=a(this),t="__jquery_unobtrusive_validation_form_reset";if(!n.data(t)){n.data(t,!0);try{n.data("validator").resetForm()}finally{n.removeData(t)}n.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors"),n.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}}function u(e){var n=a(e),t=n.data(v),r=a.proxy(l,e),i=f.unobtrusive.options||{},u=function(n,t){var r=i[n];r&&a.isFunction(r)&&r.apply(e,t)};return t||(t={options:{errorClass:i.errorClass||"input-validation-error",errorElement:i.errorElement||"span",errorPlacement:function(){o.apply(e,arguments),u("errorPlacement",arguments)},invalidHandler:function(){d.apply(e,arguments),u("invalidHandler",arguments)},messages:{},rules:{},success:function(){s.apply(e,arguments),u("success",arguments)}},attachValidation:function(){n.off("reset."+v,r).on("reset."+v,r).validate(this.options)},validate:function(){return n.validate(),n.valid()}},n.data(v,t)),t}var m,f=a.validator,v="unobtrusiveValidation";return f.unobtrusive={adapters:[],parseElement:function(e,n){var t,r,i,o=a(e),d=o.parents("form")[0];d&&(t=u(d),t.options.rules[e.name]=r={},t.options.messages[e.name]=i={},a.each(this.adapters,function(){var n="data-val-"+this.name,t=o.attr(n),s={};void 0!==t&&(n+="-",a.each(this.params,function(){s[this]=o.attr(n+this)}),this.adapt({element:e,form:d,message:t,params:s,rules:r,messages:i}))}),a.extend(r,{__dummy__:!0}),n||t.attachValidation())},parse:function(e){var n=a(e),t=n.parents().addBack().filter("form").add(n.find("form")).has("[data-val=true]");n.find("[data-val=true]").each(function(){f.unobtrusive.parseElement(this,!0)}),t.each(function(){var a=u(this);a&&a.attachValidation()})}},m=f.unobtrusive.adapters,m.add=function(a,e,n){return n||(n=e,e=[]),this.push({name:a,params:e,adapt:n}),this},m.addBool=function(a,n){return this.add(a,function(t){e(t,n||a,!0)})},m.addMinMax=function(a,n,t,r,i,o){return this.add(a,[i||"min",o||"max"],function(a){var i=a.params.min,o=a.params.max;i&&o?e(a,r,[i,o]):i?e(a,n,i):o&&e(a,t,o)})},m.addSingleVal=function(a,n,t){return this.add(a,[n||"val"],function(r){e(r,t||a,r.params[n])})},f.addMethod("__dummy__",function(a,e,n){return!0}),f.addMethod("regex",function(a,e,n){var t;return!!this.optional(e)||(t=new RegExp(n).exec(a),t&&0===t.index&&t[0].length===a.length)}),f.addMethod("nonalphamin",function(a,e,n){var t;return n&&(t=a.match(/\W/g),t=t&&t.length>=n),t}),f.methods.extension?(m.addSingleVal("accept","mimtype"),m.addSingleVal("extension","extension")):m.addSingleVal("extension","extension","accept"),m.addSingleVal("regex","pattern"),m.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"),m.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range"),m.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength"),m.add("equalto",["other"],function(n){var o=r(n.element.name),d=n.params.other,s=i(d,o),l=a(n.form).find(":input").filter("[name='"+t(s)+"']")[0];e(n,"equalTo",l)}),m.add("required",function(a){"INPUT"===a.element.tagName.toUpperCase()&&"CHECKBOX"===a.element.type.toUpperCase()||e(a,"required",!0)}),m.add("remote",["url","type","additionalfields"],function(o){var d={url:o.params.url,type:o.params.type||"GET",data:{}},s=r(o.element.name);a.each(n(o.params.additionalfields||o.element.name),function(e,n){var r=i(n,s);d.data[r]=function(){var e=a(o.form).find(":input").filter("[name='"+t(r)+"']");return e.is(":checkbox")?e.filter(":checked").val()||e.filter(":hidden").val()||"":e.is(":radio")?e.filter(":checked").val()||"":e.val()}}),e(o,"remote",d)}),m.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&e(a,"minlength",a.params.min),a.params.nonalphamin&&e(a,"nonalphamin",a.params.nonalphamin),a.params.regex&&e(a,"regex",a.params.regex)}),m.add("fileextensions",["extensions"],function(a){e(a,"extension",a.params.extensions)}),a(function(){f.unobtrusive.parse(document)}),f.unobtrusive}); -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/lib/jquery-validation/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-validation", 3 | "homepage": "https://jqueryvalidation.org/", 4 | "repository": { 5 | "type": "git", 6 | "url": "git://github.com/jquery-validation/jquery-validation.git" 7 | }, 8 | "authors": [ 9 | "Jörn Zaefferer " 10 | ], 11 | "description": "Form validation made easy", 12 | "main": "dist/jquery.validate.js", 13 | "keywords": [ 14 | "forms", 15 | "validation", 16 | "validate" 17 | ], 18 | "license": "MIT", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "demo", 25 | "lib" 26 | ], 27 | "dependencies": { 28 | "jquery": ">= 1.7.2" 29 | }, 30 | "version": "1.17.0", 31 | "_release": "1.17.0", 32 | "_resolution": { 33 | "type": "version", 34 | "tag": "1.17.0", 35 | "commit": "fc9b12d3bfaa2d0c04605855b896edb2934c0772" 36 | }, 37 | "_source": "https://github.com/jzaefferer/jquery-validation.git", 38 | "_target": "^1.17.0", 39 | "_originalSource": "jquery-validation", 40 | "_direct": true 41 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/lib/jquery/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery", 3 | "main": "dist/jquery.js", 4 | "license": "MIT", 5 | "ignore": [ 6 | "package.json" 7 | ], 8 | "keywords": [ 9 | "jquery", 10 | "javascript", 11 | "browser", 12 | "library" 13 | ], 14 | "homepage": "https://github.com/jquery/jquery-dist", 15 | "version": "3.3.1", 16 | "_release": "3.3.1", 17 | "_resolution": { 18 | "type": "version", 19 | "tag": "3.3.1", 20 | "commit": "9e8ec3d10fad04748176144f108d7355662ae75e" 21 | }, 22 | "_source": "https://github.com/jquery/jquery-dist.git", 23 | "_target": "^3.3.1", 24 | "_originalSource": "jquery", 25 | "_direct": true 26 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.SampleWebApplication/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors, https://js.foundation/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Simple/EventViewTrackerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | using GoogleAnalyticsTracker.Core; 5 | using GoogleAnalyticsTracker.Core.TrackerParameters; 6 | using JetBrains.Annotations; 7 | 8 | namespace GoogleAnalyticsTracker.Simple; 9 | 10 | [PublicAPI] 11 | public static class EventTrackerExtensions 12 | { 13 | public static async Task TrackEventAsync(this SimpleTracker tracker, string category, string action, string label, IDictionary? customDimensions = null, IDictionary? customMetrics = null, long value = 1) 14 | { 15 | var eventTrackingParameters = new EventTracking 16 | { 17 | Category = category, 18 | Action = action, 19 | Label = label, 20 | Value = value 21 | }; 22 | 23 | eventTrackingParameters.SetCustomDimensions(customDimensions); 24 | if (customMetrics != null) { 25 | eventTrackingParameters.SetCustomMetrics(customMetrics); 26 | } 27 | 28 | return await tracker.TrackAsync(eventTrackingParameters); 29 | } 30 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Simple/GoogleAnalyticsTracker.Simple.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp3.1;net6.0;netstandard2.0 4 | GoogleAnalyticsTracker.Simple 5 | True 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Simple/GoogleAnalyticsTracker.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartenba/GoogleAnalyticsTracker/cd2842e2efcfb2eb74379ee84b1ac89048cb49f8/GoogleAnalyticsTracker.Simple/GoogleAnalyticsTracker.snk -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Simple/PageViewTrackerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using GoogleAnalyticsTracker.Core; 4 | using GoogleAnalyticsTracker.Core.TrackerParameters; 5 | using JetBrains.Annotations; 6 | 7 | namespace GoogleAnalyticsTracker.Simple; 8 | 9 | [PublicAPI] 10 | public static class PageViewTrackerExtensions 11 | { 12 | public static async Task TrackPageViewAsync(this SimpleTracker tracker, string pageTitle, string pageUrl, IDictionary? customDimensions = null, IDictionary? customMetrics = null) 13 | { 14 | var pageViewParameters = new PageView 15 | { 16 | DocumentTitle = pageTitle, 17 | DocumentLocationUrl = pageUrl 18 | }; 19 | 20 | pageViewParameters.SetCustomDimensions(customDimensions); 21 | if (customMetrics != null) { 22 | pageViewParameters.SetCustomMetrics(customMetrics); 23 | } 24 | 25 | return await tracker.TrackAsync(pageViewParameters); 26 | } 27 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Simple/ScreenviewTrackerExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using GoogleAnalyticsTracker.Core; 4 | using GoogleAnalyticsTracker.Core.TrackerParameters; 5 | using JetBrains.Annotations; 6 | 7 | namespace GoogleAnalyticsTracker.Simple; 8 | 9 | [PublicAPI] 10 | public static class ScreenviewTrackerExtension 11 | { 12 | public static async Task TrackScreenviewAsync(this SimpleTracker tracker, string appName, 13 | string appId, string appVersion, string appInstallerId, string screenName, IDictionary? customDimensions = null, IDictionary? customMetrics = null) 14 | { 15 | var screenviewParameters = new ScreenviewTracking 16 | { 17 | ApplicationName = appName, 18 | ApplicationId = appId, 19 | ApplicationVersion = appVersion, 20 | ApplicationInstallerId = appInstallerId, 21 | ScreenName = screenName 22 | }; 23 | 24 | screenviewParameters.SetCustomDimensions(customDimensions); 25 | if (customMetrics != null) { 26 | screenviewParameters.SetCustomMetrics(customMetrics); 27 | } 28 | 29 | return await tracker.TrackAsync(screenviewParameters); 30 | } 31 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Simple/SimpleTracker.cs: -------------------------------------------------------------------------------- 1 | using GoogleAnalyticsTracker.Core; 2 | using GoogleAnalyticsTracker.Core.Interface; 3 | using JetBrains.Annotations; 4 | 5 | namespace GoogleAnalyticsTracker.Simple; 6 | 7 | [PublicAPI] 8 | public class SimpleTracker : TrackerBase 9 | { 10 | /// 11 | /// Creates a new SimpleTracker. 12 | /// 13 | /// Google Analytics tracking account 14 | /// Tracking environment 15 | /// 16 | /// var tracker = new SimpleTracker("UA-XXXXX-XX", "example.com", 17 | /// new SimpleTrackerEnvironment( 18 | /// Dns.GetHostName(), 19 | /// Environment.OSVersion.Platform.ToString(), 20 | /// Environment.OSVersion.Version.ToString(), 21 | /// Environment.OSVersion.VersionString 22 | /// )); 23 | /// 24 | public SimpleTracker(string trackingAccount, ITrackerEnvironment trackerEnvironment) 25 | : base(trackingAccount, trackerEnvironment) 26 | { 27 | } 28 | 29 | /// 30 | /// Creates a new SimpleTracker. See for details. 31 | /// 32 | /// 33 | /// 34 | /// 35 | public SimpleTracker(string trackingAccount, IAnalyticsSession analyticsSession, ITrackerEnvironment trackerEnvironment) 36 | : base(trackingAccount, analyticsSession, trackerEnvironment) 37 | { 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.Simple/SimpleTrackerEnvironment.cs: -------------------------------------------------------------------------------- 1 | using GoogleAnalyticsTracker.Core.Interface; 2 | using System; 3 | using JetBrains.Annotations; 4 | 5 | namespace GoogleAnalyticsTracker.Simple; 6 | 7 | [PublicAPI] 8 | public class SimpleTrackerEnvironment : ITrackerEnvironment 9 | { 10 | /// 11 | /// Create a new SimpleTrackerEnvironment. 12 | /// 13 | /// OS platform, e.g. Environment.OSVersion.Platform.ToString() 14 | /// OS version, e.g. Environment.OSVersion.Version.ToString() 15 | /// OS version string, e.g. Environment.OSVersion.VersionString 16 | /// 17 | /// var simpleTrackerEnvironment = new SimpleTrackerEnvironment( 18 | /// Environment.OSVersion.Platform.ToString(), 19 | /// Environment.OSVersion.Version.ToString(), 20 | /// Environment.OSVersion.VersionString 21 | /// ); 22 | /// 23 | public SimpleTrackerEnvironment(string osPlatform, string osVersion, string osVersionString) 24 | { 25 | OsPlatform = osPlatform ?? throw new ArgumentNullException(nameof(osPlatform)); 26 | OsVersion = osVersion ?? throw new ArgumentNullException(nameof(osVersion)); 27 | OsVersionString = osVersionString ?? throw new ArgumentNullException(nameof(osVersionString)); 28 | } 29 | 30 | public string OsPlatform { get; set; } 31 | public string OsVersion { get; set; } 32 | public string OsVersionString { get; set; } 33 | } -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29806.167 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_", "_", "{D89FAD3E-2494-4C84-8A87-1E1D1B6A1E51}" 7 | ProjectSection(SolutionItems) = preProject 8 | Directory.Build.targets = Directory.Build.targets 9 | global.json = global.json 10 | LICENSE.md = LICENSE.md 11 | README.md = README.md 12 | EndProjectSection 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{6B011824-80B7-4D62-9A51-EC319B8C1A12}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Frameworks", "Frameworks", "{C6368727-D041-4D34-A107-26884AAD495B}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{525AB2FA-0C4E-4901-BDAA-50CBC64205AA}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GoogleAnalyticsTracker.Core", "GoogleAnalyticsTracker.Core\GoogleAnalyticsTracker.Core.csproj", "{85300E06-1F96-4BE1-B267-DFB8874AD493}" 21 | EndProject 22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GoogleAnalyticsTracker.Simple", "GoogleAnalyticsTracker.Simple\GoogleAnalyticsTracker.Simple.csproj", "{B1140D42-4CA4-4B24-A234-4BC32E623CDA}" 23 | EndProject 24 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GoogleAnalyticsTracker.AspNetCore", "GoogleAnalyticsTracker.AspNetCore\GoogleAnalyticsTracker.AspNetCore.csproj", "{EB1421F8-43B5-4448-8E79-C5BAFFB2E767}" 25 | EndProject 26 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GoogleAnalyticsTracker.SampleWebApplication", "GoogleAnalyticsTracker.SampleWebApplication\GoogleAnalyticsTracker.SampleWebApplication.csproj", "{19F074E8-0E15-4F10-B873-DE136644B0BA}" 27 | EndProject 28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{C603D8E4-AC37-48A7-9BBD-AF3265FCA357}" 29 | EndProject 30 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".config", ".config", "{36F61956-6D6B-403E-ADFE-DCF7456FFAEC}" 31 | ProjectSection(SolutionItems) = preProject 32 | .config\dotnet-tools.json = .config\dotnet-tools.json 33 | EndProjectSection 34 | EndProject 35 | Global 36 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 37 | Debug|Any CPU = Debug|Any CPU 38 | Release|Any CPU = Release|Any CPU 39 | EndGlobalSection 40 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 41 | {85300E06-1F96-4BE1-B267-DFB8874AD493}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {85300E06-1F96-4BE1-B267-DFB8874AD493}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {85300E06-1F96-4BE1-B267-DFB8874AD493}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {85300E06-1F96-4BE1-B267-DFB8874AD493}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {B1140D42-4CA4-4B24-A234-4BC32E623CDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {B1140D42-4CA4-4B24-A234-4BC32E623CDA}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {B1140D42-4CA4-4B24-A234-4BC32E623CDA}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {B1140D42-4CA4-4B24-A234-4BC32E623CDA}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {EB1421F8-43B5-4448-8E79-C5BAFFB2E767}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {EB1421F8-43B5-4448-8E79-C5BAFFB2E767}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {EB1421F8-43B5-4448-8E79-C5BAFFB2E767}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {EB1421F8-43B5-4448-8E79-C5BAFFB2E767}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {19F074E8-0E15-4F10-B873-DE136644B0BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {19F074E8-0E15-4F10-B873-DE136644B0BA}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {19F074E8-0E15-4F10-B873-DE136644B0BA}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {19F074E8-0E15-4F10-B873-DE136644B0BA}.Release|Any CPU.Build.0 = Release|Any CPU 57 | {C603D8E4-AC37-48A7-9BBD-AF3265FCA357}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 58 | {C603D8E4-AC37-48A7-9BBD-AF3265FCA357}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | EndGlobalSection 60 | GlobalSection(SolutionProperties) = preSolution 61 | HideSolutionNode = FALSE 62 | EndGlobalSection 63 | GlobalSection(NestedProjects) = preSolution 64 | {85300E06-1F96-4BE1-B267-DFB8874AD493} = {6B011824-80B7-4D62-9A51-EC319B8C1A12} 65 | {B1140D42-4CA4-4B24-A234-4BC32E623CDA} = {6B011824-80B7-4D62-9A51-EC319B8C1A12} 66 | {EB1421F8-43B5-4448-8E79-C5BAFFB2E767} = {C6368727-D041-4D34-A107-26884AAD495B} 67 | {19F074E8-0E15-4F10-B873-DE136644B0BA} = {525AB2FA-0C4E-4901-BDAA-50CBC64205AA} 68 | {C603D8E4-AC37-48A7-9BBD-AF3265FCA357} = {D89FAD3E-2494-4C84-8A87-1E1D1B6A1E51} 69 | {36F61956-6D6B-403E-ADFE-DCF7456FFAEC} = {D89FAD3E-2494-4C84-8A87-1E1D1B6A1E51} 70 | EndGlobalSection 71 | GlobalSection(ExtensibilityGlobals) = postSolution 72 | SolutionGuid = {0F33A73B-8F9F-4E17-9B3A-C4E81D9D2C64} 73 | EndGlobalSection 74 | EndGlobal 75 | -------------------------------------------------------------------------------- /GoogleAnalyticsTracker.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | <data><IncludeFilters /><ExcludeFilters /></data> 4 | <data /> 5 | True 6 | True 7 | True 8 | True 9 | True 10 | True 11 | True 12 | True 13 | True 14 | True 15 | True 16 | True 17 | True 18 | True 19 | True 20 | True 21 | True -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Microsoft Public License (Ms-PL) 2 | 3 | This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software. 4 | 5 | 1. Definitions 6 | 7 | The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. 8 | 9 | A "contribution" is the original software, or any additions or changes to the software. 10 | 11 | A "contributor" is any person that distributes its contribution under this license. 12 | 13 | "Licensed patents" are a contributor's patent claims that read directly on its contribution. 14 | 15 | 2. Grant of Rights 16 | 17 | (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. 18 | 19 | (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. 20 | 21 | 3. Conditions and Limitations 22 | 23 | (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. 24 | 25 | (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. 26 | 27 | (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. 28 | 29 | (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. 30 | 31 | (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![googleanalyticstracker MyGet Build Status](https://www.myget.org/BuildSource/Badge/googleanalyticstracker?identifier=3e8e456d-0e4d-4e35-8112-1363461dfc6b)](https://www.myget.org/) 2 | 3 | # GoogleAnalyticsTracker 4 | 5 | GoogleAnalyticsTracker - A C# library for tracking Google Analytics. 6 | 7 | ## What can it be used for? 8 | 9 | GoogleAnalyticsTracker was created to have a means of tracking specific URLs directly from C#. For example, when creating an API using the ASP.NET MVC framework, GoogleAnalyticsTracker enables you to track usage of the API by calling directly into Google Analytics. 10 | 11 | Note that for GoogleAnalyticsTracker to work, you should configure Google Analytics as a website. This library will not work when the Google Analytics account is configured as an app. 12 | 13 | ## Get it on NuGet! 14 | 15 | Depending on the type of application you are using, use any of the following NuGet packages: 16 | 17 | Any application type where framework integration is not needed (Console, WPF, WinForms, ...): 18 | 19 | GoogleAnalyticsTracker.Simple 20 | 21 | ASP.NET Core: 22 | 23 | GoogleAnalyticsTracker.AspNetCore 24 | 25 | ## Example usage - GoogleAnalyticsTracker.Simple 26 | 27 | Using GoogleAnalyticsTracker is very straightforward. In your code, add the following structure wherever you want to track page views (note: when using `GoogleAnalyticsTracker.Simple`, the class to use is `SimpleTracker`): 28 | 29 | ```csharp 30 | using (var tracker = new SimpleTracker("UA-XXXXXX-XX", simpleTrackerEnvironment)) 31 | { 32 | await tracker.TrackPageViewAsync("My API - Create", "api/create"); 33 | await tracker.TrackPageViewAsync("MY API - List", "api/list"); 34 | } 35 | ``` 36 | 37 | Or without a using block: 38 | 39 | ```csharp 40 | var tracker = new SimpleTracker("UA-XXXXXX-XX", simpleTrackerEnvironment); 41 | await tracker.TrackPageViewAsync("My API - Create", "api/create"); 42 | ``` 43 | 44 | ## Example usage - GoogleAnalyticsTracker.AspNetCore 45 | 46 | Install the `GoogleAnalyticsTracker.AspNetCore` package, and register GoogleAnalyticsTracker in your startup class: 47 | 48 | 49 | ```csharp 50 | public class Startup 51 | { 52 | public void ConfigureServices(IServiceCollection services) 53 | { 54 | // ... 55 | 56 | services.AddGoogleAnalyticsTracker(options => 57 | { 58 | options.TrackerId = "UA-XXXXXX-XX"; 59 | options.ShouldTrackRequestInMiddleware = TrackRequests.Yes; 60 | }); 61 | } 62 | 63 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 64 | { 65 | app.UseGoogleAnalyticsTracker(); 66 | 67 | // ... 68 | } 69 | } 70 | ``` 71 | 72 | This will automatically track all requests made to your application. 73 | 74 | Ideally, you will want to control tracking requests. To do so: 75 | 76 | * Set the `options.ShouldTrackRequestInMiddleware = TrackRequests.No;` option to disable automatic tracking. 77 | * Add the `[TrackPageView]` or `[TrackEvent]` attributes to your actions. 78 | 79 | `[TrackPageView]` will track a page view in Google Analytics, whereas `[TrackEvent]` tracks an event. 80 | 81 | In case custom tracking is needed, inject an `AspNetCoreTracker` into your controller and use the tracking methods directly. 82 | 83 | Note that the options also provide a couple of events that can be overridden to customize GoogleAnalyticsTracker behaviour: 84 | 85 | * `CustomizeTrackerEnvironment` lets you customize the tracker environment or inject a custom one. 86 | * `CustomizeAnalyticsSession` lets you customize the analytics session or inject a custom one. 87 | 88 | ## Characteristics 89 | 90 | GoogleAnalyticsTracker does not track your users. It simply serves as an interface to Google Analytics where you should provide all tracking data that is required. 91 | Of course. GoogleAnalyticsTracker sends some data that can be inferred from usage, such as the hostname on which it is running, but not the hostname of your client. 92 | Sessions are also untracked: every event that is tracked counts as a new unique visitor to Google Analytics. 93 | 94 | * If you do need to track user sessions, implement a custom `IAnalyticsSession` and pass it to the constructor of the tracker. 95 | * If you do need to set common data for all tracking hits, subclass the `Tracker` or `TrackerBase` and override the `AmendParameters` method. 96 | 97 | ## License 98 | 99 | [MS-PL License](https://github.com/maartenba/GoogleAnalyticsTracker/blob/master/LICENSE.md) 100 | 101 | ## Who uses GoogleAnalyticsTracker? 102 | 103 | * [MyGet](https://www.myget.org/) 104 | * [JetBrains](https://www.jetbrains.com/) 105 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | :; set -eo pipefail 2 | :; SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 3 | :; ${SCRIPT_DIR}/build.sh "$@" 4 | :; exit $? 5 | 6 | @ECHO OFF 7 | powershell -ExecutionPolicy ByPass -NoProfile "%~dp0build.ps1" %* 8 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param( 3 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] 4 | [string[]]$BuildArguments 5 | ) 6 | 7 | Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)" 8 | 9 | Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 } 10 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 11 | 12 | ########################################################################### 13 | # CONFIGURATION 14 | ########################################################################### 15 | 16 | $BuildProjectFile = "$PSScriptRoot\build\_build.csproj" 17 | $TempDirectory = "$PSScriptRoot\\.nuke\temp" 18 | 19 | $DotNetGlobalFile = "$PSScriptRoot\\global.json" 20 | $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1" 21 | $DotNetChannel = "Current" 22 | 23 | $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 24 | $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 25 | $env:DOTNET_MULTILEVEL_LOOKUP = 0 26 | 27 | ########################################################################### 28 | # EXECUTION 29 | ########################################################################### 30 | 31 | function ExecSafe([scriptblock] $cmd) { 32 | & $cmd 33 | if ($LASTEXITCODE) { exit $LASTEXITCODE } 34 | } 35 | 36 | # If dotnet CLI is installed globally and it matches requested version, use for execution 37 | if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and ` 38 | $(dotnet --version) -and $LASTEXITCODE -eq 0) { 39 | $env:DOTNET_EXE = (Get-Command "dotnet").Path 40 | } 41 | else { 42 | # Download install script 43 | $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1" 44 | New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null 45 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 46 | (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile) 47 | 48 | # If global.json exists, load expected version 49 | if (Test-Path $DotNetGlobalFile) { 50 | $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json) 51 | if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) { 52 | $DotNetVersion = $DotNetGlobal.sdk.version 53 | } 54 | } 55 | 56 | # Install by channel or version 57 | $DotNetDirectory = "$TempDirectory\dotnet-win" 58 | if (!(Test-Path variable:DotNetVersion)) { 59 | ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } 60 | } else { 61 | ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } 62 | } 63 | $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe" 64 | } 65 | 66 | Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)" 67 | 68 | ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet } 69 | ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments } 70 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | bash --version 2>&1 | head -n 1 4 | 5 | set -eo pipefail 6 | SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 7 | 8 | ########################################################################### 9 | # CONFIGURATION 10 | ########################################################################### 11 | 12 | BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj" 13 | TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp" 14 | 15 | DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json" 16 | DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh" 17 | DOTNET_CHANNEL="Current" 18 | 19 | export DOTNET_CLI_TELEMETRY_OPTOUT=1 20 | export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 21 | export DOTNET_MULTILEVEL_LOOKUP=0 22 | 23 | ########################################################################### 24 | # EXECUTION 25 | ########################################################################### 26 | 27 | function FirstJsonValue { 28 | perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}" 29 | } 30 | 31 | # If dotnet CLI is installed globally and it matches requested version, use for execution 32 | if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then 33 | export DOTNET_EXE="$(command -v dotnet)" 34 | else 35 | # Download install script 36 | DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh" 37 | mkdir -p "$TEMP_DIRECTORY" 38 | curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL" 39 | chmod +x "$DOTNET_INSTALL_FILE" 40 | 41 | # If global.json exists, load expected version 42 | if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then 43 | DOTNET_VERSION=$(FirstJsonValue "version" "$(cat "$DOTNET_GLOBAL_FILE")") 44 | if [[ "$DOTNET_VERSION" == "" ]]; then 45 | unset DOTNET_VERSION 46 | fi 47 | fi 48 | 49 | # Install by channel or version 50 | DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix" 51 | if [[ -z ${DOTNET_VERSION+x} ]]; then 52 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path 53 | else 54 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path 55 | fi 56 | export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet" 57 | fi 58 | 59 | echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)" 60 | 61 | "$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet 62 | "$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@" 63 | -------------------------------------------------------------------------------- /build/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | dotnet_style_qualification_for_field = false:warning 3 | dotnet_style_qualification_for_property = false:warning 4 | dotnet_style_qualification_for_method = false:warning 5 | dotnet_style_qualification_for_event = false:warning 6 | dotnet_style_require_accessibility_modifiers = never:warning 7 | 8 | csharp_style_expression_bodied_methods = true:silent 9 | csharp_style_expression_bodied_properties = true:warning 10 | csharp_style_expression_bodied_indexers = true:warning 11 | csharp_style_expression_bodied_accessors = true:warning 12 | -------------------------------------------------------------------------------- /build/Build.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Nuke.Common; 4 | using Nuke.Common.Execution; 5 | using Nuke.Common.IO; 6 | using Nuke.Common.ProjectModel; 7 | using Nuke.Common.Tools.DotNet; 8 | using Nuke.Common.Utilities.Collections; 9 | using static Nuke.Common.IO.FileSystemTasks; 10 | using static Nuke.Common.Tools.DotNet.DotNetTasks; 11 | 12 | public static class Extensions 13 | { 14 | public static string Until(this string source, string marker) 15 | { 16 | if (string.IsNullOrEmpty(source)) return source; 17 | 18 | var length = source.IndexOf(marker, StringComparison.OrdinalIgnoreCase); 19 | 20 | if (length < 0 || length > source.Length) return source; 21 | 22 | return source.Substring(0, length); 23 | } 24 | } 25 | 26 | [CheckBuildProjectConfigurations] 27 | [UnsetVisualStudioEnvironmentVariables] 28 | // ReSharper disable once ClassNeverInstantiated.Global 29 | class Build : NukeBuild 30 | { 31 | public static int Main () => Execute(x => x.Package); 32 | 33 | [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")] 34 | readonly string Configuration = "Release"; 35 | 36 | [Solution] readonly Solution Solution; 37 | 38 | AbsolutePath SourceDirectory => RootDirectory; 39 | AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts"; 40 | 41 | static string AssemblyVersion = Environment.GetEnvironmentVariable("AssemblyVersion") ?? Environment.GetEnvironmentVariable("VersionFormat")?.Replace("{0}", "0")?.Until("-") ?? "0.0.1"; 42 | static string PackageVersion = Environment.GetEnvironmentVariable("PackageVersion") ?? AssemblyVersion + "-dev"; 43 | 44 | Target Clean => _ => _ 45 | .Before(Restore) 46 | .Executes(() => 47 | { 48 | SourceDirectory.GlobDirectories("**/bin", "**/obj").ForEach(DeleteDirectory); 49 | EnsureCleanDirectory(ArtifactsDirectory); 50 | }); 51 | 52 | Target Restore => _ => _ 53 | .Executes(() => 54 | { 55 | DotNetRestore(_ => _ 56 | .SetProjectFile(Solution)); 57 | }); 58 | 59 | Target Compile => _ => _ 60 | .DependsOn(Restore) 61 | .Executes(() => 62 | { 63 | DotNetBuild(_ => _ 64 | .SetProjectFile(Solution) 65 | .SetConfiguration(Configuration) 66 | .SetAssemblyVersion(AssemblyVersion) 67 | .SetFileVersion(AssemblyVersion) 68 | .SetInformationalVersion(PackageVersion) 69 | .EnableNoRestore()); 70 | }); 71 | 72 | Target Package => _ => _ 73 | .DependsOn(Compile) 74 | .Executes(() => 75 | { 76 | foreach (var project in Solution.AllProjects.Where(p => p.GetProperty("GeneratePackageOnBuild")).ToList()) 77 | { 78 | DotNetPack(_ => _ 79 | .SetProject(project) 80 | .SetConfiguration(Configuration) 81 | .EnableIncludeSource() 82 | .EnableIncludeSymbols() 83 | .EnableNoRestore() 84 | .EnableNoBuild() 85 | .SetVersion(PackageVersion) 86 | .SetOutputDirectory(ArtifactsDirectory)); 87 | } 88 | }); 89 | } -------------------------------------------------------------------------------- /build/_build.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | false 7 | False 8 | CS0649;CS0169 9 | .. 10 | .. 11 | 1 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /build/_build.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | DO_NOT_SHOW 3 | DO_NOT_SHOW 4 | Implicit 5 | Implicit 6 | ExpressionBody 7 | 0 8 | NEXT_LINE 9 | True 10 | False 11 | 120 12 | IF_OWNER_IS_SINGLE_LINE 13 | WRAP_IF_LONG 14 | False 15 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 16 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 17 | True 18 | True 19 | True 20 | True 21 | True 22 | True 23 | True 24 | True 25 | True 26 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "6.0", 4 | "rollForward": "latestMajor", 5 | "allowPrerelease": true 6 | } 7 | } --------------------------------------------------------------------------------