NOTE: Apart from
(and even then it's questionable, I'm Scottish). These are machine translated in languages I don't read. If they're terrible please contact me.
You can see how this translation was done in this article.
Sunday, 18 August 2024
//Less than a minute
In einem früheren Beitrag haben wir einen Client zum Abrufen hinzugefügt Daten zur Umami-Analyse......................................................................................................... In diesem Beitrag werden wir einen Client für das Senden von Tracking-Daten an Umami von einer C#-Anwendung hinzufügen. Umami ist ein leichtgewichtiger Analysedienst, der selbst gehostet werden kann. Es ist eine großartige Alternative zu Google Analytics und ist datenschutzorientiert. Allerdings hat es standardmäßig nur einen Node-Client für das Tracking von Daten (und selbst dann ist es nicht GREAT). Also beschloss ich, einen C#-Client zu schreiben, um Daten zu verfolgen.
Umami installieren Sie können sehen, wie ich das hier mache.
Sie können alle Quellcode für den Client sehen Hierher.
Dies benutzt Einstellungen, die ich in meinem appsettings.json
..............................................................................................................................
"Analytics":{
"UmamiPath" : "https://umamilocal.mostlylucid.net",
"WebsiteId" : "32c2aa31-b1ac-44c0-b8f3-ff1f50403bee",
"UmamiScript" : "getinfo"
},
Da die Track-API nicht authentifiziert ist, habe ich dem Client keine Authentifizierung hinzugefügt.
Um den Client einzurichten, habe ich meine übliche Erweiterungsmethode mit hinzugefügt wird von Ihrem aufgerufen Program.cs
..............................................................................................................................
services.SetupUmamiClient(config);
Dies bietet eine einfache Möglichkeit, in der UmamiClient
zu Ihrer Bewerbung.
Der Code unten zeigt die Setup-Methode an.
public static void SetupUmamiClient(this IServiceCollection services, IConfiguration config)
{
var umamiSettings= services.ConfigurePOCO<UmamiClientSettings>(config.GetSection(UmamiClientSettings.Section));
if(string.IsNullOrEmpty( umamiSettings.UmamiPath)) throw new Exception("UmamiUrl is required");
if(string.IsNullOrEmpty(umamiSettings.WebsiteId)) throw new Exception("WebsiteId is required");
services.AddTransient<HttpLogger>();
services.AddHttpClient<UmamiClient>((serviceProvider, client) =>
{
umamiSettings = serviceProvider.GetRequiredService<UmamiClientSettings>();
client.DefaultRequestHeaders.Add("User-Agent", $"Mozilla/5.0 Node/{Environment.Version}");
client.BaseAddress = new Uri(umamiSettings.UmamiPath);
}).SetHandlerLifetime(TimeSpan.FromMinutes(5)) //Set lifetime to five minutes
.AddPolicyHandler(GetRetryPolicy())
#if DEBUG
.AddLogger<HttpLogger>();
#else
;
#endif
services.AddHttpContextAccessor();
}
static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == HttpStatusCode.ServiceUnavailable)
.WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}
Wie Sie sehen können, macht dies folgendes:
Das UmamiClient
ist ziemlich einfach. Es hat eine Kernmethode Send
die die Tracking-Daten an den Umami-Server sendet.
public async Task<HttpResponseMessage> Send(UmamiPayload payload, string type = "event")
{
var jsonPayload = new { type, payload };
logger.LogInformation("Sending data to Umami {Payload}", JsonSerializer.Serialize(jsonPayload, options));
var response= await client.PostAsJsonAsync("/api/send", jsonPayload, options);
if(!response.IsSuccessStatusCode)
{
logger.LogError("Failed to send data to Umami {Response}, {Message}", response.StatusCode, response.ReasonPhrase);
}
else
{
var content = await response.Content.ReadAsStringAsync();
logger.LogInformation("Successfully sent data to Umami {Response}, {Message} {Content}", response.StatusCode, response.ReasonPhrase, content);
}
return response;
}
Wie Sie sehen werden, verwendet dies ein Objekt namens UmamiPayload
die alle möglichen Parameter für die Verfolgung von Anfragen in Umami enthält.
public class UmamiPayload
{
public string Website { get; set; }=string.Empty;
public string Hostname { get; set; }=string.Empty;
public string Language { get; set; }=string.Empty;
public string Referrer { get; set; }=string.Empty;
public string Screen { get; set; }=string.Empty;
public string Title { get; set; } =string.Empty;
public string Url { get; set; } =string.Empty;
public string Name { get; set; } =string.Empty;
public UmamiEventData? Data { get; set; }
}
public class UmamiEventData : Dictionary<string, object> { }
Das einzige erforderliche Feld ist Website
das ist die Website-ID. Der Rest ist optional (aber Url
ist wirklich nützlich!)== Einzelnachweise ==
Im Client habe ich eine Methode namens GetPayload()
die dieses Nutzlastobjekt automatisch mit Informationen aus der Anfrage bevölkert (mit dem injizierten IHttpContextAccessor
).
public class UmamiClient(HttpClient client, ILogger<UmamiClient> logger, IHttpContextAccessor accessor, UmamiClientSettings settings)...
private UmamiPayload GetPayload(string? url = null, UmamiEventData? data = null)
{
// Initialize a new UmamiPayload object
var payload = new UmamiPayload
{
Website = settings.WebsiteId,
Data = data ?? new UmamiEventData(),
Url = url ?? "" // Default URL to empty string if null
};
// Check if HttpContext is available
if (accessor.HttpContext != null)
{
var context = accessor.HttpContext;
var headers = context.Request.Headers;
// Fill payload details from HttpContext and headers
payload.Hostname = context?.Request.Host.Host ?? ""; // Default to empty string if null
payload.Language = headers?["Accept-Language"].ToString() ?? ""; // Safely retrieve Accept-Language header
payload.Referrer = headers?["Referer"].ToString() ?? ""; // Safely retrieve Referer header
payload.Screen = headers?["User-Agent"].ToString() ?? ""; // Safely retrieve User-Agent header
payload.Title = headers?["Title"].ToString() ?? ""; // Safely retrieve Title header
payload.Url = string.IsNullOrEmpty(url) ? context.Request.Path.ToString() : url; // Use the passed URL or fallback to the request path
}
return payload;
}
Dies wird dann durch weitere Utility-Methoden, die eine schönere Schnittstelle für diese Daten geben verwendet.
public async Task<HttpResponseMessage> TrackUrl(string? url="", string? eventname = "event", UmamiEventData? eventData = null)
{
var payload = GetPayload(url);
payload.Name = eventname;
return await Track(payload, eventData);
}
public async Task<HttpResponseMessage> Track(string eventObj, UmamiEventData? eventData = null)
{
var payload = new UmamiPayload
{
Website = settings.WebsiteId,
Name = eventObj,
Data = eventData ?? new UmamiEventData()
};
return await Send(payload);
}
public async Task<HttpResponseMessage> Track(UmamiPayload eventObj, UmamiEventData? eventData = null)
{
var payload = eventObj;
payload.Data = eventData ?? new UmamiEventData();
payload.Website = settings.WebsiteId;
return await Send(payload);
}
public async Task<HttpResponseMessage> Identify(UmamiEventData eventData)
{
var payload = new UmamiPayload
{
Website = settings.WebsiteId,
Data = eventData ?? new()
};
return await Send(payload, "identify");
}
Auf diese Weise können Sie Ereignisse, URLs verfolgen und Benutzer identifizieren.
In Zukunft plane ich, dies zu einem NuGet-Paket zu machen. Testen dafür habe ich einen Eintrag in der Umami.Client.csproj
Datei, die beim Bau im Debug-Modus ein neues versioniertes 'Preview'-Paket generiert.
<Target Name="NugetPackAutoVersioning" AfterTargets="Build" Condition="'$(Configuration)' == 'Debug'">
<!-- Delete the contents of the target directory -->
<RemoveDir Directories="$(SolutionDir)nuget" />
<!-- Recreate the target directory -->
<MakeDir Directories="$(SolutionDir)nuget" />
<!-- Run the dotnet pack command -->
<Exec Command="dotnet pack -p:PackageVersion=$([System.DateTime]::Now.ToString("yyyy.MM.dd.HHmm"))-preview -p:V --no-build --configuration $(Configuration) --output "$(SolutionDir)nuget"" />
<Exec Command="dotnet nuget push $(SolutionDir)nuget\*.nupkg --source Local" />
<Exec Command="del /f /s /q $(SolutionDir)nuget\*.nupkg" />
</Target>
Dies wird direkt vor Ende hinzugefügt. </Project>
tag in der .csproj
..............................................................................................................................
Es hängt von einem Nuget-Standort namens 'lokal' ab, der im Nuget.config
.............................................................................................................................. Was ich in einen lokalen Ordner auf meinem Rechner gemappt habe.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="Local" value="e:\nuget" />
<add key="Microsoft Visual Studio Offline Packages" value="C:\Program Files (x86)\Microsoft SDKs\NuGetPackages\" />
</packageSources>
</configuration>
In Zukunft plane ich, dies zu einem NuGet-Paket zu machen. Ich benutze dies jetzt im Blog, zum Beispiel, um zu verfolgen, wie lange Übersetzungen dauern
var translationTask = tasks.FirstOrDefault(t => t.TaskId == taskId);
if (translationTask == null) return TypedResults.BadRequest("Task not found");
await umamiClient.Send(new UmamiPayload(){ Name = "Get Translation"}, new UmamiEventData(){{"timetaken", translationTask.TotalMilliseconds}, {"language",translationTask.Language}});