Analyse-Seite
Meldet ein Anwender ein Problem, fehlen dem Support meist genau die Angaben, die niemand auswendig
kennt: Browser und Version, Betriebssystem, Bildschirmauflösung, Zeitzone, gesetzte Cookies und
Storage-Schlüssel, aufgetretene JavaScript-Fehler. Das Paket FMH.Komponente.Analyse
liefert dafür eine fertige Seite unter /Analyse, die all das sammelt und über einen
Knopf als Text in die Zwischenablage legt.
Anders als die übrigen Bausteine dieser Dokumentation wird hier nichts in eine eigene View
eingebaut, die Seite kommt mit dem Paket mit. Zu tun ist allein die Paketreferenz auf
FMH.Komponente.Analyse aus dem FMH-Feed und, je nach Sicherheitsrichtlinie der
Anwendung, eine Anpassung der Header.
Die Seite unter /Analyse
Der Rahmen zeigt die echte Seite des Pakets, dieselbe Adresse, nur ohne Navigation und Fusszeile. Die Angaben stammen aus diesem Browser, der Speedtest laeuft beim Oeffnen automatisch.
<!-- Die Komponente kommt als NuGet-Paket. Version einsetzen oder
kuerzer: dotnet add package FMH.Komponente.Analyse -->
<ItemGroup>
<PackageReference Include="FMH.Komponente.Analyse" Version="..." />
</ItemGroup>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="Fmh_Framework_NuGet_Feed"
value="https://pkgs.dev.azure.com/fmhdev/_packaging/Fmh_Framework_NuGet_Feed/nuget/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="Fmh_Framework_NuGet_Feed">
<package pattern="FMH.*" />
</packageSource>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<Description>Browser-, Geräte- und Sitzungsinformationen für die spätere Fehleranalyse.</Description>
<PackageTags>fmh komponente analyse</PackageTags>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<!-- Explizit gesetzt: sonst leitet MSBuild beides aus dem Projektdateinamen ab, und der
Name der eingebetteten Ressourcen (speedtest.png) haengt an dessen Schreibweise. -->
<RootNamespace>FMH.Komponente.Analyse</RootNamespace>
<AssemblyName>FMH.Komponente.Analyse</AssemblyName>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\speedtest.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
<!-- Der Controller wird auf der Doku-Seite der WebUI als Code-Tab gezeigt. Eingebettet statt
kopiert, damit angezeigter und ausgefuehrter Quelltext dieselbe Datei sind. -->
<EmbeddedResource Include="Controllers\*.cs" />
<!-- Dito die Paketdefinition: Die Doku-Seite zeigt damit die tatsaechlichen Abhaengigkeiten
des Pakets und nicht eine abgeschriebene Liste, die still veraltet. Seit der Umstellung
auf das SDK-Paketieren ist die Projektdatei selbst die Paketdefinition; die frueher hier
eingebettete .nuspec gibt es nicht mehr. -->
<EmbeddedResource Include="FMH.Komponente.Analyse.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="databinding.WebSecurity.Headers.Attributes" />
<PackageReference Include="MyCSharp.HttpUserAgentParser.AspNetCore" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="serilog" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\AnalyseResources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>AnalyseResources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\AnalyseResources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>AnalyseResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>
// Die Seite nutzt Inline-JavaScript und fragt die oeffentliche IP bei api.ipify.org ab.
// Ohne AddUnsafeInlineScriptSrc() blockiert die Richtlinie das Skript; die Ausnahme fuer
// ipify bringt die Action des Pakets als [HeaderSecurityCspDirective] selbst mit.
var headerCollectionBuilder = new HeaderCollectionBuilder().AddConnectSrcLocalhost()
.AddUnsafeInlineScriptSrc();
app.UseHeaderSecurity(headerCollectionBuilder.Build());
// Mehr braucht es nicht: Controller und View des Pakets werden als ApplicationPart
// automatisch gefunden, die Adresse /Analyse entsteht aus der Standardroute.
app.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
using databinding.WebSecurity.Headers.Attributes;
using FMH.Komponente.Analyse.Models;
using Microsoft.AspNetCore.Mvc;
using MyCSharp.HttpUserAgentParser;
using Serilog;
namespace FMH.Komponente.Analyse.Controllers
{
public class AnalyseController : Controller
{
[HeaderSecurityCspDirective(CspDirective.ConnectSrc, "https://api.ipify.org")]
public IActionResult Index()
{
Log.Information("Benutzer ruft Seite Analyse auf");
var userAgent = this.Request.Headers["User-Agent"]
.ToString();
var parsed = HttpUserAgentParser.Parse(userAgent);
var model = new BrowserInfoViewModel
{
UserAgent = userAgent,
Browser = parsed.Name,
OS = parsed.Platform.HasValue ? parsed.Platform.Value.Name : "-",
BrowserVersion = parsed.Version,
Type = $"{parsed.Type}",
Device = string.IsNullOrEmpty(parsed.MobileDeviceType)
? "-"
: $"{parsed.MobileDeviceType}".Trim()
};
return this.View(model);
}
[HttpGet]
[Route("/analyse/speedtest-image")]
public IActionResult GetSpeedtestImage()
{
var assembly = typeof(AnalyseController).Assembly;
var resourceName = "FMH.Komponente.Analyse.Resources.speedtest.png";
var stream = assembly.GetManifestResourceStream(resourceName);
if (stream == null)
{
return this.NotFound();
}
return this.File(stream, "image/png");
}
}
}
Darstellung
Das Paket FMH.Komponente.Analyse bringt eine fertige Seite unter /Analyse mit. Sie richtet sich an den Anwender im Störungsfall: Er ruft sie auf, drückt «Alle Informationen kopieren» und schickt den Text an den Support. Damit liegen dort auf einen Schlag die Angaben vor, nach denen man sonst einzeln fragen muss.
Einbinden
Die Komponente wird als NuGet-Paket bezogen, nicht als Projektreferenz. Paket-Id ist FMH.Komponente.Analyse (siehe Tab «Paket (csproj)»):
dotnet add package FMH.Komponente.Analyse
oder von Hand in der .csproj:
<ItemGroup>
<PackageReference Include="FMH.Komponente.Analyse" Version="..." />
</ItemGroup>
Arbeitet das Projekt mit zentraler Paketverwaltung (ManagePackageVersionsCentrally), gehört die Version stattdessen als <PackageVersion Include="FMH.Komponente.Analyse" Version="..." /> in die Directory.Packages.props, und die PackageReference bleibt ohne Version.
Feed: FMH-Pakete liegen nicht auf nuget.org, sondern im Azure-DevOps-Feed Fmh_Framework_NuGet_Feed. Er gehört in die NuGet.config der Projektmappe, zusammen mit dem Package-Source-Mapping, das FMH.* ausschliesslich dort sucht (Tab «NuGet.config»). Ohne den Feed schlägt die Wiederherstellung mit NU1101 fehl; ohne Anmeldedaten am Feed mit 401.
Das Paket bringt mit (Abhängigkeiten laut Projektdatei, jede PackageReference darin wird beim Paketieren zu einer Abhängigkeit der Spec):
| Paket | Wofür |
|---|---|
MyCSharp.HttpUserAgentParser.AspNetCore |
Zerlegt den User-Agent in Betriebssystem, Browser, Version und Gerätetyp. |
databinding.WebSecurity.Headers.Attributes |
Liefert [HeaderSecurityCspDirective], mit dem die Seite ihre CSP-Ausnahme für api.ipify.org selbst mitbringt. |
serilog |
Protokolliert den Aufruf der Seite. |
Mehr ist nicht zu tun: Controller und View werden als ApplicationPart automatisch gefunden (Microsoft.NET.Sdk.Razor mit AddRazorSupportForMvc). Es gibt keine AddAnalyse()-Methode und keinen DI-Eintrag, nichts zu registrieren.
Voraussetzungen an die Anwendung
Route, Layout, Bootstrap, die Ausnahme in der Content Security Policy und Serilog stellt die Anwendung bereit. Die vollständige Liste, mit dem, was ohne sie fehlt, steht unter Vorbedingungen.
Was die Seite zeigt
Serverseitig ermittelt, aus dem User-Agent und dem angemeldeten Benutzer:
- Betriebssystem, Browser und Version, Typ (Browser/Robot), mobiler Gerätetyp, roher User-Agent-String
- Alle Claims des angemeldeten Benutzers als Tabelle; ohne Anmeldung steht dort «Nicht angemeldet»
Clientseitig im Browser gesammelt:
| Bereich | Angaben |
|---|---|
| Netz | Öffentliche IP-Adresse, Online-Status, gemessene Downloadgeschwindigkeit |
| Darstellung | Bildschirmgrösse, verfügbare Fläche, Fenstergrösse, DPI, Farbtiefe, Touch-Punkte |
| Umgebung | Sprache, Zeitzone, Cookies aktiviert, DoNotTrack, Referrer, aktuelle Adresse |
| Browser-Cache | Anzahl und Namen der Session-Storage-Schlüssel, Anzahl und Liste der Local-Storage-Schlüssel, alle sichtbaren Cookies der Anwendung |
| Fähigkeiten | ServiceWorker, WebGL, mediaDevices, Anzahl Kerne, Gerätespeicher |
| Fehler | JavaScript-Fehler, die auf der Seite auftreten (über window.onerror) |
Die Seitenspalte verlinkt zusätzlich auf Anleitungen zum Leeren des Browser-Caches und zum Prüfen der Browser-Version.
Geschwindigkeitsmessung
Beim Öffnen lädt die Seite ein rund 1.4 MB grosses Testbild von /analyse/speedtest-image mit cache: 'no-store', misst die Dauer und rechnet daraus Mbit/s. Das Bild liegt als eingebettete Ressource im Paket; ausgeliefert wird es nur für die Messung und danach verworfen.
Datenschutz
Storage-Schlüssel, Cookie-Namen und sämtliche Claims des angemeldeten Benutzers stehen im Klartext auf der Seite und im kopierten Text. Wo Claims fachliche Angaben enthalten, gehört das in die Überlegung, wer die Seite aufrufen und wohin der kopierte Text gehen darf.
Sprachen
Die Beschriftungen kommen aus AnalyseResources und liegen auf Deutsch, Englisch und Französisch vor, auch die Meldungen des Skripts.
Zu dieser Demo
Der Rahmen oben zeigt /Analyse?einbetten=true. Der Parameter setzt ViewData["Einbetten"], worauf das Layout dieser Anwendung Navigation und Fusszeile weglässt, sonst erschienen beide im Rahmen ein zweites Mal. Der Seiteninhalt ist unverändert; über den Link darüber lässt sich dieselbe Seite mit vollem Rahmen in einem eigenen Tab öffnen.