From f8e0d222c5136dd4517b1a8a22b9a9bbaedfe97c Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sat, 15 May 2021 15:20:19 +0200 Subject: [PATCH 01/36] add init --- .gitignore | 325 ++++++++++++++++++ MyCSharp.HttpUserAgentParser.sln | 53 +++ NuGet.config | 9 + README.md | 50 ++- ...serDependencyInjectionOptionsExtensions.cs | 20 ++ .../HttpUserAgentParserAccessor.cs | 29 ++ ...harp.HttpUserAgentParser.AspNetCore.csproj | 20 ++ ...arp.HttpUserAgentParser.MemoryCache.csproj | 7 + ...erAgentParserDependencyInjectionOptions.cs | 14 + .../ServiceCollectionExtensions.cs | 42 +++ .../HttpUserAgentInformation.cs | 26 ++ .../HttpUserAgentInformationExtensions.cs | 10 + .../HttpUserAgentParser.cs | 107 ++++++ .../HttpUserAgentPlatformInformation.cs | 16 + .../HttpUserAgentPlatformType.cs | 16 + .../HttpUserAgentStatics.cs | 207 +++++++++++ .../HttpUserAgentType.cs | 9 + .../MyCSharp.HttpUserAgentParser.csproj | 13 + .../CachedHttpUserAgentParserProvider.cs | 12 + .../DefaultHttpUserAgentParserProvider.cs | 8 + .../Providers/IHttpUserAgentParserProvider.cs | 7 + ...serAgentParser.AspNetCore.UnitTests.csproj | 23 ++ .../HttpUserAgentParserTests.cs | 152 ++++++++ ...Sharp.HttpUserAgentParser.UnitTests.csproj | 26 ++ version.json | 19 + 25 files changed, 1219 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 MyCSharp.HttpUserAgentParser.sln create mode 100644 NuGet.config create mode 100644 src/MyCSharp.HttpUserAgentParser.AspNetCore/DependencyInjection/HttpUserAgentParserDependencyInjectionOptionsExtensions.cs create mode 100644 src/MyCSharp.HttpUserAgentParser.AspNetCore/HttpUserAgentParserAccessor.cs create mode 100644 src/MyCSharp.HttpUserAgentParser.AspNetCore/MyCSharp.HttpUserAgentParser.AspNetCore.csproj create mode 100644 src/MyCSharp.HttpUserAgentParser.MemoryCache/MyCSharp.HttpUserAgentParser.MemoryCache.csproj create mode 100644 src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserDependencyInjectionOptions.cs create mode 100644 src/MyCSharp.HttpUserAgentParser/DependencyInjection/ServiceCollectionExtensions.cs create mode 100644 src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs create mode 100644 src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformationExtensions.cs create mode 100644 src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs create mode 100644 src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformInformation.cs create mode 100644 src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformType.cs create mode 100644 src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs create mode 100644 src/MyCSharp.HttpUserAgentParser/HttpUserAgentType.cs create mode 100644 src/MyCSharp.HttpUserAgentParser/MyCSharp.HttpUserAgentParser.csproj create mode 100644 src/MyCSharp.HttpUserAgentParser/Providers/CachedHttpUserAgentParserProvider.cs create mode 100644 src/MyCSharp.HttpUserAgentParser/Providers/DefaultHttpUserAgentParserProvider.cs create mode 100644 src/MyCSharp.HttpUserAgentParser/Providers/IHttpUserAgentParserProvider.cs create mode 100644 tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests.csproj create mode 100644 tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs create mode 100644 tests/MyCSharp.HttpUserAgentParser.UnitTests/MyCSharp.HttpUserAgentParser.UnitTests.csproj create mode 100644 version.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..70c5e46 --- /dev/null +++ b/.gitignore @@ -0,0 +1,325 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +## Custom +appsettings.ABT-PC.json + + + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +##**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog diff --git a/MyCSharp.HttpUserAgentParser.sln b/MyCSharp.HttpUserAgentParser.sln new file mode 100644 index 0000000..f6f825f --- /dev/null +++ b/MyCSharp.HttpUserAgentParser.sln @@ -0,0 +1,53 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31306.274 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{008A2BAB-78B4-42EB-A5D4-DE434438CEF0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyCSharp.HttpUserAgentParser.AspNetCore", "src\MyCSharp.HttpUserAgentParser.AspNetCore\MyCSharp.HttpUserAgentParser.AspNetCore.csproj", "{45927CF7-1BF4-479B-BBAA-8AD9CA901AE4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyCSharp.HttpUserAgentParser", "src\MyCSharp.HttpUserAgentParser\MyCSharp.HttpUserAgentParser.csproj", "{3357BEC0-8216-409E-A539-F9A71DBACB81}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyCSharp.HttpUserAgentParser.UnitTests", "tests\MyCSharp.HttpUserAgentParser.UnitTests\MyCSharp.HttpUserAgentParser.UnitTests.csproj", "{F16697F7-74B4-441D-A0C0-1A0572AC3AB0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests", "tests\MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests\MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests.csproj", "{75960783-8BF9-479C-9ECF-E9653B74C9A2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F54C9296-4EF7-40F0-9F20-F23A2270ABC9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {45927CF7-1BF4-479B-BBAA-8AD9CA901AE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45927CF7-1BF4-479B-BBAA-8AD9CA901AE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45927CF7-1BF4-479B-BBAA-8AD9CA901AE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45927CF7-1BF4-479B-BBAA-8AD9CA901AE4}.Release|Any CPU.Build.0 = Release|Any CPU + {3357BEC0-8216-409E-A539-F9A71DBACB81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3357BEC0-8216-409E-A539-F9A71DBACB81}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3357BEC0-8216-409E-A539-F9A71DBACB81}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3357BEC0-8216-409E-A539-F9A71DBACB81}.Release|Any CPU.Build.0 = Release|Any CPU + {F16697F7-74B4-441D-A0C0-1A0572AC3AB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F16697F7-74B4-441D-A0C0-1A0572AC3AB0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F16697F7-74B4-441D-A0C0-1A0572AC3AB0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F16697F7-74B4-441D-A0C0-1A0572AC3AB0}.Release|Any CPU.Build.0 = Release|Any CPU + {75960783-8BF9-479C-9ECF-E9653B74C9A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75960783-8BF9-479C-9ECF-E9653B74C9A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75960783-8BF9-479C-9ECF-E9653B74C9A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75960783-8BF9-479C-9ECF-E9653B74C9A2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {45927CF7-1BF4-479B-BBAA-8AD9CA901AE4} = {008A2BAB-78B4-42EB-A5D4-DE434438CEF0} + {3357BEC0-8216-409E-A539-F9A71DBACB81} = {008A2BAB-78B4-42EB-A5D4-DE434438CEF0} + {F16697F7-74B4-441D-A0C0-1A0572AC3AB0} = {F54C9296-4EF7-40F0-9F20-F23A2270ABC9} + {75960783-8BF9-479C-9ECF-E9653B74C9A2} = {F54C9296-4EF7-40F0-9F20-F23A2270ABC9} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E8B0C994-0BF2-4692-9E22-E48B265B2804} + EndGlobalSection +EndGlobal diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 0000000..5368ac4 --- /dev/null +++ b/NuGet.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index adf48f3..8bb39d9 100644 --- a/README.md +++ b/README.md @@ -1 +1,49 @@ -# MyCSharp.HttpUserAgentParser \ No newline at end of file +# MyCSharp.HttpUserAgentParser + +## Usage + +text here + +### Parse + +text here + +### Dependency Injection Configuration + +text here + +### Caching + +text here + +### ASP.NET Core + + +## Disclaimer + +This library is inspired by [UserAgentService by DannyBoyNg](https://github.com/DannyBoyNg/UserAgentService) and contains optimizations for our requirements on myCSharp.de. +We decided to fork the project, because we want a general restructuring with corresponding breaking changes. + +## License + +MIT License + +Copyright (c) 2021 MyCSharp + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/MyCSharp.HttpUserAgentParser.AspNetCore/DependencyInjection/HttpUserAgentParserDependencyInjectionOptionsExtensions.cs b/src/MyCSharp.HttpUserAgentParser.AspNetCore/DependencyInjection/HttpUserAgentParserDependencyInjectionOptionsExtensions.cs new file mode 100644 index 0000000..ceda188 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser.AspNetCore/DependencyInjection/HttpUserAgentParserDependencyInjectionOptionsExtensions.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; +using MyCSharp.HttpUserAgentParser.DependencyInjection; +using MyCSharp.HttpUserAgentParser.Providers; + +namespace MyCSharp.HttpUserAgentParser.AspNetCore.DependencyInjection +{ + public static class HttpUserAgentParserDependencyInjectionOptionsExtensions + { + /// + /// Registers as . + /// Requires a registered + /// + public static HttpUserAgentParserDependencyInjectionOptions AddHttpUserAgentParserAccessor( + this HttpUserAgentParserDependencyInjectionOptions options) + { + options.Services.AddSingleton(); + return options; + } + } +} \ No newline at end of file diff --git a/src/MyCSharp.HttpUserAgentParser.AspNetCore/HttpUserAgentParserAccessor.cs b/src/MyCSharp.HttpUserAgentParser.AspNetCore/HttpUserAgentParserAccessor.cs new file mode 100644 index 0000000..dde668a --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser.AspNetCore/HttpUserAgentParserAccessor.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Http; +using MyCSharp.HttpUserAgentParser.Providers; + +namespace MyCSharp.HttpUserAgentParser.AspNetCore +{ + public interface IHttpUserAgentParserAccessor + { + string HttpContextUserAgent { get; } + HttpUserAgentInformation ParseFromHttpContext(); + } + + public class HttpUserAgentParserAccessor : IHttpUserAgentParserAccessor + { + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IHttpUserAgentParserProvider _httpUserAgentParser; + + public HttpUserAgentParserAccessor(IHttpContextAccessor httpContextAccessor, IHttpUserAgentParserProvider httpUserAgentParser) + { + _httpContextAccessor = httpContextAccessor; + _httpUserAgentParser = httpUserAgentParser; + } + + public string HttpContextUserAgent => + _httpContextAccessor?.HttpContext?.Request?.Headers["User-Agent"].ToString()!; + + public HttpUserAgentInformation ParseFromHttpContext() + => _httpUserAgentParser.Parse(HttpContextUserAgent); + } +} diff --git a/src/MyCSharp.HttpUserAgentParser.AspNetCore/MyCSharp.HttpUserAgentParser.AspNetCore.csproj b/src/MyCSharp.HttpUserAgentParser.AspNetCore/MyCSharp.HttpUserAgentParser.AspNetCore.csproj new file mode 100644 index 0000000..b72c8f2 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser.AspNetCore/MyCSharp.HttpUserAgentParser.AspNetCore.csproj @@ -0,0 +1,20 @@ + + + + net5.0 + 9.0 + enable + + + + + + + + + + + + + + diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/MyCSharp.HttpUserAgentParser.MemoryCache.csproj b/src/MyCSharp.HttpUserAgentParser.MemoryCache/MyCSharp.HttpUserAgentParser.MemoryCache.csproj new file mode 100644 index 0000000..f208d30 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/MyCSharp.HttpUserAgentParser.MemoryCache.csproj @@ -0,0 +1,7 @@ + + + + net5.0 + + + diff --git a/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserDependencyInjectionOptions.cs b/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserDependencyInjectionOptions.cs new file mode 100644 index 0000000..322b9f3 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserDependencyInjectionOptions.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace MyCSharp.HttpUserAgentParser.DependencyInjection +{ + public class HttpUserAgentParserDependencyInjectionOptions + { + public IServiceCollection Services { get; } + + public HttpUserAgentParserDependencyInjectionOptions(IServiceCollection services) + { + Services = services; + } + } +} \ No newline at end of file diff --git a/src/MyCSharp.HttpUserAgentParser/DependencyInjection/ServiceCollectionExtensions.cs b/src/MyCSharp.HttpUserAgentParser/DependencyInjection/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..8f48057 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser/DependencyInjection/ServiceCollectionExtensions.cs @@ -0,0 +1,42 @@ +using Microsoft.Extensions.DependencyInjection; +using MyCSharp.HttpUserAgentParser.Providers; + +namespace MyCSharp.HttpUserAgentParser.DependencyInjection +{ + public static class HttpUserAgentParserServiceCollectionExtensions + { + /// + /// Registers as singleton to + /// + public static HttpUserAgentParserDependencyInjectionOptions AddHttpUserAgentParser( + this IServiceCollection services) + { + return AddHttpUserAgentParser(services); + } + + /// + /// Registers as singleton to + /// + public static HttpUserAgentParserDependencyInjectionOptions AddCachedHttpUserAgentParser( + this IServiceCollection services) + { + return AddHttpUserAgentParser(services); + } + + /// + /// Registers as singleton to + /// + public static HttpUserAgentParserDependencyInjectionOptions AddHttpUserAgentParser( + this IServiceCollection services) where TProvider : class, IHttpUserAgentParserProvider + { + // create options + HttpUserAgentParserDependencyInjectionOptions options = new(services); + + // add provider + services.AddSingleton(); + + return options; + } + + } +} diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs new file mode 100644 index 0000000..15bca37 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs @@ -0,0 +1,26 @@ +namespace MyCSharp.HttpUserAgentParser +{ + public readonly struct HttpUserAgentInformation + { + public string UserAgent { get; } + public HttpUserAgentType Type { get; } + + public HttpUserAgentPlatformInformation? Platform { get; } + public string? Name { get; } + public string? Version { get; } + public string? MobileDeviceType { get; } + + + public HttpUserAgentInformation(string userAgent, HttpUserAgentType type, HttpUserAgentPlatformInformation? platform, string? name, string? version, string? mobileDeviceType) + { + UserAgent = userAgent; + Type = type; + MobileDeviceType = mobileDeviceType; + Platform = platform; + Name = name; + Version = version; + } + + public static HttpUserAgentInformation Parse(string userAgent) => HttpUserAgentParser.Parse(userAgent); + } +} diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformationExtensions.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformationExtensions.cs new file mode 100644 index 0000000..5108b28 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformationExtensions.cs @@ -0,0 +1,10 @@ +namespace MyCSharp.HttpUserAgentParser +{ + public static class HttpUserAgentInformationExtensions + { + public static bool IsType(this HttpUserAgentInformation userAgent, HttpUserAgentType type) => userAgent.Type == type; + public static bool IsRobot(this HttpUserAgentInformation userAgent) => IsType(userAgent, HttpUserAgentType.Robot); + public static bool IsBrowser(this HttpUserAgentInformation userAgent) => IsType(userAgent, HttpUserAgentType.Browser); + public static bool IsMobile(this HttpUserAgentInformation userAgent) => userAgent.MobileDeviceType is not null; + } +} \ No newline at end of file diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs new file mode 100644 index 0000000..da0b183 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; + +namespace MyCSharp.HttpUserAgentParser +{ + public static class HttpUserAgentParser + { + public static HttpUserAgentInformation Parse(string userAgent) + { + // prepare + userAgent = Cleanup(userAgent); + + // analyze + if (TryGetRobot(userAgent, out string? robotName)) + { + return new HttpUserAgentInformation(userAgent, HttpUserAgentType.Robot, null, robotName, null, null); + } + + HttpUserAgentPlatformInformation? platform = GetPlatform(userAgent); + string? mobileDeviceType = GetMobileDevice(userAgent); + + if (TryGetBrowser(userAgent, out (string Name, string? Version)? browser)) + { + return new HttpUserAgentInformation(userAgent, HttpUserAgentType.Browser, platform, browser?.Name, browser?.Version, mobileDeviceType); + } + + return new HttpUserAgentInformation(userAgent, HttpUserAgentType.Unknown, platform, null, null, mobileDeviceType); + } + + public static string Cleanup(string userAgent) => userAgent.Trim(); + + public static HttpUserAgentPlatformInformation? GetPlatform(string userAgent) + { + foreach (var item in HttpUserAgentStatics.Platforms) + { + if (Regex.IsMatch(userAgent, $"{Regex.Escape(item.Id)}", RegexOptions.IgnoreCase)) + { + return item; + } + } + + return null; + } + public static bool TryGetPlatform(string userAgent, [NotNullWhen(true)] out HttpUserAgentPlatformInformation? platform) + { + platform = GetPlatform(userAgent); + return platform is not null; + } + + public static (string Name, string? Version)? GetBrowser(string userAgent) + { + foreach (var item in HttpUserAgentStatics.Browsers) + { + Match match = Regex.Match(userAgent, $@"{item.Key}.*?([0-9\.]+)", RegexOptions.IgnoreCase); + if (match.Success) + { + return (item.Value, match.Groups[1].Value); + } + } + + return null; + } + public static bool TryGetBrowser(string userAgent, [NotNullWhen(true)] out (string Name, string? Version)? browser) + { + browser = GetBrowser(userAgent); + return browser is not null; + } + + public static string? GetRobot(string userAgent) + { + foreach (KeyValuePair item in HttpUserAgentStatics.Robots) + { + if (Regex.IsMatch(userAgent, $"{Regex.Escape(item.Key)}", RegexOptions.IgnoreCase)) + { + return item.Value; + } + } + + return null; + } + public static bool TryGetRobot(string userAgent, [NotNullWhen(true)] out string? robotName) + { + robotName = GetRobot(userAgent); + return robotName is not null; + } + + public static string? GetMobileDevice(string userAgent) + { + foreach (KeyValuePair item in HttpUserAgentStatics.Mobiles) + { + if (userAgent.IndexOf(item.Key, StringComparison.OrdinalIgnoreCase) != -1) + { + return item.Value; + } + } + + return null; + } + public static bool TryGetMobileDevice(string userAgent, [NotNullWhen(true)] out string? device) + { + device = GetMobileDevice(userAgent); + return device is not null; + } + } +} \ No newline at end of file diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformInformation.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformInformation.cs new file mode 100644 index 0000000..a065fa6 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformInformation.cs @@ -0,0 +1,16 @@ +namespace MyCSharp.HttpUserAgentParser +{ + public readonly struct HttpUserAgentPlatformInformation + { + public string Id { get; } + public string Name { get; } + public HttpUserAgentPlatformType PlatformType { get; } + + public HttpUserAgentPlatformInformation(string id, string name, HttpUserAgentPlatformType platformType) + { + Id = id; + Name = name; + PlatformType = platformType; + } + } +} \ No newline at end of file diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformType.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformType.cs new file mode 100644 index 0000000..5c6bda8 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformType.cs @@ -0,0 +1,16 @@ +namespace MyCSharp.HttpUserAgentParser +{ + public enum HttpUserAgentPlatformType + { + Unknown, + Generic, + Windows, + Linux, + Unix, + IOS, + MacOS, + BlackBerry, + Android, + Symbian + } +} \ No newline at end of file diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs new file mode 100644 index 0000000..6d33676 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs @@ -0,0 +1,207 @@ +using System.Collections.Generic; + +namespace MyCSharp.HttpUserAgentParser +{ + public static class HttpUserAgentStatics + { + public static HashSet Platforms = new() + { + new("windows nt 10.0", "Windows 10", HttpUserAgentPlatformType.Windows), + new("windows nt 6.3", "Windows 8.1", HttpUserAgentPlatformType.Windows), + new("windows nt 6.2", "Windows 8", HttpUserAgentPlatformType.Windows), + new("windows nt 6.1", "Windows 7", HttpUserAgentPlatformType.Windows), + new("windows nt 6.0", "Windows Vista", HttpUserAgentPlatformType.Windows), + new("windows nt 5.2", "Windows 2003", HttpUserAgentPlatformType.Windows), + new("windows nt 5.1", "Windows XP", HttpUserAgentPlatformType.Windows), + new("windows nt 5.0", "Windows 2000", HttpUserAgentPlatformType.Windows), + new("windows nt 4.0", "Windows NT 4.0", HttpUserAgentPlatformType.Windows), + new("winnt4.0", "Windows NT 4.0", HttpUserAgentPlatformType.Windows), + new("winnt 4.0", "Windows NT", HttpUserAgentPlatformType.Windows), + new("winnt", "Windows NT", HttpUserAgentPlatformType.Windows), + new("windows 98", "Windows 98", HttpUserAgentPlatformType.Windows), + new("win98", "Windows 98", HttpUserAgentPlatformType.Windows), + new("windows 95", "Windows 95", HttpUserAgentPlatformType.Windows), + new("win95", "Windows 95", HttpUserAgentPlatformType.Windows), + new("windows phone", "Windows Phone", HttpUserAgentPlatformType.Windows), + new("windows", "Unknown Windows OS", HttpUserAgentPlatformType.Windows), + new("android", "Android", HttpUserAgentPlatformType.Android), + new("blackberry", "BlackBerry", HttpUserAgentPlatformType.BlackBerry), + new("iphone", "iOS", HttpUserAgentPlatformType.IOS), + new("ipad", "iOS", HttpUserAgentPlatformType.IOS), + new("ipod", "iOS", HttpUserAgentPlatformType.IOS), + new("os x", "Mac OS X", HttpUserAgentPlatformType.MacOS), + new("ppc mac", "Power PC Mac", HttpUserAgentPlatformType.MacOS), + new("freebsd", "FreeBSD", HttpUserAgentPlatformType.Linux), + new("ppc", "Macintosh", HttpUserAgentPlatformType.Linux), + new("linux", "Linux", HttpUserAgentPlatformType.Linux), + new("debian", "Debian", HttpUserAgentPlatformType.Linux), + new("sunos", "Sun Solaris", HttpUserAgentPlatformType.Generic), + new("beos", "BeOS", HttpUserAgentPlatformType.Generic), + new("apachebench", "ApacheBench", HttpUserAgentPlatformType.Generic), + new("aix", "AIX", HttpUserAgentPlatformType.Generic), + new("irix", "Irix", HttpUserAgentPlatformType.Generic), + new("osf", "DEC OSF", HttpUserAgentPlatformType.Generic), + new("hp-ux", "HP-UX", HttpUserAgentPlatformType.Windows), + new("netbsd", "NetBSD", HttpUserAgentPlatformType.Generic), + new("bsdi", "BSDi", HttpUserAgentPlatformType.Generic), + new("openbsd", "OpenBSD", HttpUserAgentPlatformType.Unix), + new("gnu", "GNU/Linux", HttpUserAgentPlatformType.Linux), + new("unix", "Unknown Unix OS", HttpUserAgentPlatformType.Unix), + new("symbian", "Symbian OS", HttpUserAgentPlatformType.Symbian), + }; + + internal static Dictionary Browsers = new() + { + { "OPR", "Opera" }, + { "Flock", "Flock" }, + { "Edge", "Edge" }, + { "EdgA", "Edge" }, + { "Edg", "Edge" }, + { "Vivaldi", "Vivaldi" }, + { "Brave Chrome", "Brave" }, + { "Chrome", "Chrome" }, + { "CriOS", "Chrome" }, + { "Opera.*?Version", "Opera" }, + { "Opera", "Opera" }, + { "MSIE", "Internet Explorer" }, + { "Internet Explorer", "Internet Explorer" }, + { "Trident.* rv", "Internet Explorer" }, + { "Shiira", "Shiira" }, + { "Firefox", "Firefox" }, + { "FxiOS", "Firefox" }, + { "Chimera", "Chimera" }, + { "Phoenix", "Phoenix" }, + { "Firebird", "Firebird" }, + { "Camino", "Camino" }, + { "Netscape", "Netscape" }, + { "OmniWeb", "OmniWeb" }, + { "Safari", "Safari" }, + { "Mozilla", "Mozilla" }, + { "Konqueror", "Konqueror" }, + { "icab", "iCab" }, + { "Lynx", "Lynx" }, + { "Links", "Links" }, + { "hotjava", "HotJava" }, + { "amaya", "Amaya" }, + { "IBrowse", "IBrowse" }, + { "Maxthon", "Maxthon" }, + { "ipod touch", "Apple iPod" }, + { "Ubuntu", "Ubuntu Web Browser" }, + }; + + internal static Dictionary Mobiles = new() + { + // Legacy + { "mobileexplorer", "Mobile Explorer" }, + { "palmsource", "Palm" }, + { "palmscape", "Palmscape" }, + // Phones and Manufacturers + { "motorola", "Motorola" }, + { "nokia", "Nokia" }, + { "palm", "Palm" }, + { "ipad", "Apple iPad" }, + { "ipod", "Apple iPod" }, + { "iphone", "Apple iPhone" }, + { "sony", "Sony Ericsson" }, + { "ericsson", "Sony Ericsson" }, + { "blackberry", "BlackBerry" }, + { "cocoon", "O2 Cocoon" }, + { "blazer", "Treo" }, + { "lg", "LG" }, + { "amoi", "Amoi" }, + { "xda", "XDA" }, + { "mda", "MDA" }, + { "vario", "Vario" }, + { "htc", "HTC" }, + { "samsung", "Samsung" }, + { "sharp", "Sharp" }, + { "sie-", "Siemens" }, + { "alcatel", "Alcatel" }, + { "benq", "BenQ" }, + { "ipaq", "HP iPaq" }, + { "mot-", "Motorola" }, + { "playstation portable", "PlayStation Portable" }, + { "playstation 3", "PlayStation 3" }, + { "playstation vita", "PlayStation Vita" }, + { "hiptop", "Danger Hiptop" }, + { "nec-", "NEC" }, + { "panasonic", "Panasonic" }, + { "philips", "Philips" }, + { "sagem", "Sagem" }, + { "sanyo", "Sanyo" }, + { "spv", "SPV" }, + { "zte", "ZTE" }, + { "sendo", "Sendo" }, + { "nintendo dsi", "Nintendo DSi" }, + { "nintendo ds", "Nintendo DS" }, + { "nintendo 3ds", "Nintendo 3DS" }, + { "wii", "Nintendo Wii" }, + { "open web", "Open Web" }, + { "openweb", "OpenWeb" }, + // Operating Systems + { "android", "Android" }, + { "symbian", "Symbian" }, + { "SymbianOS", "SymbianOS" }, + { "elaine", "Palm" }, + { "series60", "Symbian S60" }, + { "windows ce", "Windows CE" }, + // Browsers + { "obigo", "Obigo" }, + { "netfront", "Netfront Browser" }, + { "openwave", "Openwave Browser" }, + { "mobilexplorer", "Mobile Explorer" }, + { "operamini", "Opera Mini" }, + { "opera mini", "Opera Mini" }, + { "opera mobi", "Opera Mobile" }, + { "fennec", "Firefox Mobile" }, + // Other + { "digital paths", "Digital Paths" }, + { "avantgo", "AvantGo" }, + { "xiino", "Xiino" }, + { "novarra", "Novarra Transcoder" }, + { "vodafone", "Vodafone" }, + { "docomo", "NTT DoCoMo" }, + { "o2", "O2" }, + // Fallback + { "mobile", "Generic Mobile" }, + { "wireless", "Generic Mobile" }, + { "j2me", "Generic Mobile" }, + { "midp", "Generic Mobile" }, + { "cldc", "Generic Mobile" }, + { "up.link", "Generic Mobile" }, + { "up.browser", "Generic Mobile" }, + { "smartphone", "Generic Mobile" }, + { "cellphone", "Generic Mobile" }, + }; + + internal static Dictionary Robots = new() + { + { "googlebot", "Googlebot" }, + { "msnbot", "MSNBot" }, + { "baiduspider", "Baiduspider" }, + { "bingbot", "Bing" }, + { "slurp", "Inktomi Slurp" }, + { "yahoo", "Yahoo" }, + { "ask jeeves", "Ask Jeeves" }, + { "fastcrawler", "FastCrawler" }, + { "infoseek", "InfoSeek Robot 1.0" }, + { "lycos", "Lycos" }, + { "yandex", "YandexBot" }, + { "mediapartners-google", "MediaPartners Google" }, + { "apis-google", "APIs-Google" }, + { "CRAZYWEBCRAWLER", "Crazy Webcrawler" }, + { "adsbot-google", "AdsBot Google" }, + { "feedfetcher-google", "Feedfetcher Google" }, + { "google-read-aloud", "Google-Read-Aloud" }, + { "curious george", "Curious George" }, + { "ia_archiver", "Alexa Crawler" }, + { "MJ12bot", "Majestic-12" }, + { "Uptimebot", "Uptimebot" }, + }; + + internal static Dictionary Tools = new() + { + { "curl", "curl" } + }; + } +} \ No newline at end of file diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentType.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentType.cs new file mode 100644 index 0000000..bf74cc1 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentType.cs @@ -0,0 +1,9 @@ +namespace MyCSharp.HttpUserAgentParser +{ + public enum HttpUserAgentType + { + Unknown, + Browser, + Robot, + } +} \ No newline at end of file diff --git a/src/MyCSharp.HttpUserAgentParser/MyCSharp.HttpUserAgentParser.csproj b/src/MyCSharp.HttpUserAgentParser/MyCSharp.HttpUserAgentParser.csproj new file mode 100644 index 0000000..122e5f1 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser/MyCSharp.HttpUserAgentParser.csproj @@ -0,0 +1,13 @@ + + + + net5.0 + 9.0 + enable + + + + + + + diff --git a/src/MyCSharp.HttpUserAgentParser/Providers/CachedHttpUserAgentParserProvider.cs b/src/MyCSharp.HttpUserAgentParser/Providers/CachedHttpUserAgentParserProvider.cs new file mode 100644 index 0000000..e4632e5 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser/Providers/CachedHttpUserAgentParserProvider.cs @@ -0,0 +1,12 @@ +using System.Collections.Concurrent; + +namespace MyCSharp.HttpUserAgentParser.Providers +{ + public class CachedHttpUserAgentParserProvider : IHttpUserAgentParserProvider + { + private readonly ConcurrentDictionary _cache = new(); + + public HttpUserAgentInformation Parse(string userAgent) + => _cache.GetOrAdd(userAgent, HttpUserAgentInformation.Parse(userAgent)); + } +} \ No newline at end of file diff --git a/src/MyCSharp.HttpUserAgentParser/Providers/DefaultHttpUserAgentParserProvider.cs b/src/MyCSharp.HttpUserAgentParser/Providers/DefaultHttpUserAgentParserProvider.cs new file mode 100644 index 0000000..c682294 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser/Providers/DefaultHttpUserAgentParserProvider.cs @@ -0,0 +1,8 @@ +namespace MyCSharp.HttpUserAgentParser.Providers +{ + public class DefaultHttpUserAgentParserProvider : IHttpUserAgentParserProvider + { + public HttpUserAgentInformation Parse(string userAgent) + => HttpUserAgentParser.Parse(userAgent); + } +} \ No newline at end of file diff --git a/src/MyCSharp.HttpUserAgentParser/Providers/IHttpUserAgentParserProvider.cs b/src/MyCSharp.HttpUserAgentParser/Providers/IHttpUserAgentParserProvider.cs new file mode 100644 index 0000000..44f1c67 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser/Providers/IHttpUserAgentParserProvider.cs @@ -0,0 +1,7 @@ +namespace MyCSharp.HttpUserAgentParser.Providers +{ + public interface IHttpUserAgentParserProvider + { + HttpUserAgentInformation Parse(string userAgent); + } +} \ No newline at end of file diff --git a/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests.csproj b/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests.csproj new file mode 100644 index 0000000..ef46261 --- /dev/null +++ b/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests.csproj @@ -0,0 +1,23 @@ + + + + Exe + net5.0 + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs new file mode 100644 index 0000000..46edcd5 --- /dev/null +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs @@ -0,0 +1,152 @@ +using FluentAssertions; +using Xunit; + +namespace MyCSharp.HttpUserAgentParser.UnitTests +{ + public class HttpUserAgentParserTests + { + [Theory] + // IE + [InlineData("Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; WOW64; Trident/4.0;)", "Internet Explorer", "7.0", "Windows Vista", HttpUserAgentPlatformType.Windows, null)] + [InlineData("Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)", "Internet Explorer", "8.0", "Windows XP", HttpUserAgentPlatformType.Windows, null)] + [InlineData("Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)", "Internet Explorer", "8.0", "Windows 7", HttpUserAgentPlatformType.Windows, null)] + [InlineData("Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.0)", "Internet Explorer", "9.0", "Windows Vista", HttpUserAgentPlatformType.Windows, null)] + [InlineData("Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1)", "Internet Explorer", "9.0", "Windows 7", HttpUserAgentPlatformType.Windows, null)] + [InlineData("Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)", "Internet Explorer", "10.0", "Windows 7", HttpUserAgentPlatformType.Windows, null)] + [InlineData("Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2)", "Internet Explorer", "10.0", "Windows 8", HttpUserAgentPlatformType.Windows, null)] + [InlineData("Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko", "Internet Explorer", "11.0", "Windows 7", HttpUserAgentPlatformType.Windows, null)] + [InlineData("Mozilla/5.0 (Windows NT 6.2; Trident/7.0; rv:11.0) like Gecko", "Internet Explorer", "11.0", "Windows 8", HttpUserAgentPlatformType.Windows, null)] + [InlineData("Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko", "Internet Explorer", "11.0", "Windows 8.1", HttpUserAgentPlatformType.Windows, null)] + [InlineData("Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko", "Internet Explorer", "11.0", "Windows 10", HttpUserAgentPlatformType.Windows, null)] + // Chrome + [InlineData("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", "Chrome", "90.0.4430.212", "Windows 10", HttpUserAgentPlatformType.Windows, null)] + [InlineData("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", "Chrome", "90.0.4430.212", "Windows 10", HttpUserAgentPlatformType.Windows, null)] + [InlineData("Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", "Chrome", "90.0.4430.212", "Windows 10", HttpUserAgentPlatformType.Windows, null)] + [InlineData("Mozilla/5.0 (Macintosh; Intel Mac OS X 11_3_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", "Chrome", "90.0.4430.212", "Mac OS X", HttpUserAgentPlatformType.MacOS, null)] + [InlineData("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", "Chrome", "90.0.4430.212", "Linux", HttpUserAgentPlatformType.Linux, null)] + [InlineData("Mozilla/5.0 (iPhone; CPU iPhone OS 14_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/90.0.4430.78 Mobile/15E148 Safari/604.1", "Chrome", "90.0.4430.78", "iOS", HttpUserAgentPlatformType.IOS, "Apple iPhone")] + [InlineData("Mozilla/5.0 (iPad; CPU OS 14_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/90.0.4430.78 Mobile/15E148 Safari/604.1", "Chrome", "90.0.4430.78", "iOS", HttpUserAgentPlatformType.IOS, "Apple iPad")] + [InlineData("Mozilla/5.0 (iPod; CPU iPhone OS 14_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/90.0.4430.78 Mobile/15E148 Safari/604.1", "Chrome", "90.0.4430.78", "iOS", HttpUserAgentPlatformType.IOS, "Apple iPod")] + [InlineData("Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.210 Mobile Safari/537.36", "Chrome", "90.0.4430.210", "Android", HttpUserAgentPlatformType.Android, "Android")] + [InlineData("Mozilla/5.0 (Linux; Android 10; SM-A205U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.210 Mobile Safari/537.36", "Chrome", "90.0.4430.210", "Android", HttpUserAgentPlatformType.Android, "Android")] + [InlineData("Mozilla/5.0 (Linux; Android 10; LM-Q720) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.210 Mobile Safari/537.36", "Chrome", "90.0.4430.210", "Android", HttpUserAgentPlatformType.Android, "Android")] + // Safari + [InlineData("Mozilla/5.0 (Windows; U; Windows NT 10.0; en-US) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/11.0 Safari/605.1.15", "Safari", "605.1.15", "Windows 10", HttpUserAgentPlatformType.Windows, null)] + [InlineData("Mozilla/5.0 (Macintosh; Intel Mac OS X 11_3_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Safari/605.1.15", "Safari", "605.1.15", "Mac OS X", HttpUserAgentPlatformType.MacOS, null)] + [InlineData("Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1", "Safari", "604.1", "iOS", HttpUserAgentPlatformType.IOS, "Apple iPhone")] + [InlineData("Mozilla/5.0 (iPod touch; CPU iPhone 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1", "Safari", "604.1", "iOS", HttpUserAgentPlatformType.IOS, "Apple iPod")] + // Edge + [InlineData("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.51", "Edge", "90.0.818.51", "Windows 10", HttpUserAgentPlatformType.Windows, null)] + [InlineData("Mozilla/5.0 (Macintosh; Intel Mac OS X 11_3_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.51", "Edge", "90.0.818.51", "Mac OS X", HttpUserAgentPlatformType.MacOS, null)] + [InlineData("Mozilla/5.0 (Linux; Android 10; HD1913) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.210 Mobile Safari/537.36 EdgA/46.3.4.5155", "Edge", "46.3.4.5155", "Android", HttpUserAgentPlatformType.Android, "Android")] + [InlineData("Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 EdgiOS/46.3.13 Mobile/15E148 Safari/605.1.15", "Edge", "46.3.13", "iOS", HttpUserAgentPlatformType.IOS, "Apple iPhone")] + [InlineData("Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edge/44.18363.8131", "Edge", "44.18363.8131", "Windows 10", HttpUserAgentPlatformType.Windows, null)] + // Firefox + [InlineData("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0", "Firefox", "88.0", "Windows 10", HttpUserAgentPlatformType.Windows, null)] + [InlineData("Mozilla/5.0 (Macintosh; Intel Mac OS X 11.3; rv:88.0) Gecko/20100101 Firefox/88.0", "Firefox", "88.0", "Mac OS X", HttpUserAgentPlatformType.MacOS, null)] + [InlineData("Mozilla/5.0 (X11; Linux i686; rv:88.0) Gecko/20100101 Firefox/88.0", "Firefox", "88.0", "Linux", HttpUserAgentPlatformType.Linux, null)] + [InlineData("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0", "Firefox", "88.0", "Linux", HttpUserAgentPlatformType.Linux, null)] + [InlineData("Mozilla/5.0 (iPhone; CPU iPhone OS 11_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/33.0 Mobile/15E148 Safari/605.1.15", "Firefox", "33.0", "iOS", HttpUserAgentPlatformType.IOS, "Apple iPhone")] + [InlineData("Mozilla/5.0 (Android 11; Mobile; rv:68.0) Gecko/68.0 Firefox/88.0", "Firefox", "88.0", "Android", HttpUserAgentPlatformType.Android, "Android")] + // Opera + [InlineData("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 OPR/76.0.4017.107", "Opera", "76.0.4017.107", "Windows 10", HttpUserAgentPlatformType.Windows, null)] + [InlineData("Mozilla/5.0 (Macintosh; Intel Mac OS X 11_3_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 OPR/76.0.4017.107", "Opera", "76.0.4017.107", "Mac OS X", HttpUserAgentPlatformType.MacOS, null)] + [InlineData("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 OPR/76.0.4017.107", "Opera", "76.0.4017.107", "Linux", HttpUserAgentPlatformType.Linux, null)] + [InlineData("Mozilla/5.0 (Linux; Android 10; VOG-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.210 Mobile Safari/537.36 OPR/63.0.3216.58473", "Opera", "63.0.3216.58473", "Android", HttpUserAgentPlatformType.Android, "Android")] + public void BrowserTests(string ua, string name, string version, string platformName, HttpUserAgentPlatformType platformType, string mobileDeviceType) + { + HttpUserAgentInformation uaInfo = HttpUserAgentInformation.Parse(ua); + + uaInfo.Name.Should().Be(name); + uaInfo.Version.Should().Be(version); + uaInfo.UserAgent.Should().Be(ua); + + uaInfo.Type.Should().Be(HttpUserAgentType.Browser); + + HttpUserAgentPlatformInformation platform = uaInfo.Platform.GetValueOrDefault(); + platform.PlatformType.Should().Be(platformType); + platform.Name.Should().Be(platformName); + + uaInfo.MobileDeviceType.Should().Be(mobileDeviceType); + + uaInfo.IsBrowser().Should().Be(true); + uaInfo.IsMobile().Should().Be(mobileDeviceType is not null); + uaInfo.IsRobot().Should().Be(false); + } + + [Theory] + // Google https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers + [InlineData("APIs-Google (+https://developers.google.com/webmasters/APIs-Google.html)", "APIs Google")] + [InlineData("Mediapartners-Google", "Mediapartners Google")] + [InlineData("Mozilla/5.0 (Linux; Android 5.0; SM-G920A) AppleWebKit (KHTML, like Gecko) Chrome Mobile Safari (compatible; AdsBot-Google-Mobile; +http://www.google.com/mobile/adsbot.html)", "AdsBot Google Mobile")] + [InlineData("Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML,like Gecko) Version/9.0 Mobile/13B143 Safari/601.1 (compatible; AdsBot-Google-Mobile; +http://www.google.com/mobile/adsbot.html)", "AdsBot Google Mobile")] + [InlineData("AdsBot-Google (+http://www.google.com/adsbot.html)", "AdsBot Google")] + [InlineData("Googlebot-Image/1.0", "Googlebot")] + [InlineData("Googlebot-News", "Googlebot")] + [InlineData("Googlebot-Video/1.0", "Googlebot")] + [InlineData("Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)", "Googlebot")] + [InlineData("Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Googlebot/2.1; +http://www.google.com/bot.html) Chrome/1.2.3 Safari/537.36", "Googlebot")] + [InlineData("Googlebot/2.1 (+http://www.google.com/bot.html)", "Googlebot")] + [InlineData("Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/1.2.3 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)", "Googlebot")] + [InlineData("Mediapartners-Google/2.1; +http://www.google.com/bot.html)", "MMediapartners-Google")] + [InlineData("FeedFetcher-Google; (+http://www.google.com/feedfetcher.html)", "FeedFetcher-Google")] + [InlineData("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36 (compatible; Google-Read-Aloud; +https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers)", "Google-Read-Aloud")] + [InlineData("Mozilla/5.0 (Linux; Android 7.0; SM-G930V Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.125 Mobile Safari/537.36 (compatible; Google-Read-Aloud; +https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers)", "Google-Read-Aloud")] + [InlineData("Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012; DuplexWeb-Google/1.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Mobile Safari/537.36", "DuplexWeb-Google")] + [InlineData("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36 Google Favicon", "Google Favicon")] + [InlineData("Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko; googleweblight) Chrome/38.0.1025.166 Mobile Safari/535.19", "googleweblight")] + [InlineData("Mozilla/5.0 (X11; Linux x86_64; Storebot-Google/1.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36", "Storebot-Google")] + [InlineData("Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012; Storebot-Google/1.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36", "Storebot-Google")] + // Bing + [InlineData("Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) ", "BingBot")] + [InlineData("Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) ", "BingBot")] + [InlineData("Mozilla/5.0 (Windows Phone 8.1; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 530) like Gecko (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) ", "BingBot")] + [InlineData("Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) Chrome/1.2.3.4 Safari/537.36 Edg/1.2.3.4", "BingBot")] + [InlineData("Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/1.2.3.4  Mobile Safari/537.36 Edg/1.2.3.4 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) ", "BingBot")] + [InlineData("Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)", "Baidu")] + [InlineData("Mozilla/5.0 (compatible; MJ12bot/v1.4.5; http://www.majestic12.co.uk/bot.php?+)", "Majestic")] + [InlineData("Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)", "Yahoo Slurp")] + [InlineData("Mozilla/5.0 (compatible; MegaIndex.ru/2.0; +http://megaindex.com/crawler)", "MegaIndex")] + [InlineData("Mozilla/5.0 (compatible; AhrefsBot/5.2; +http://ahrefs.com/robot/)", "Ahrefs")] + [InlineData("Mozilla/5.0 (compatible; SemrushBot/7~bl; +http://www.semrush.com/bot.html)", "SEMRush")] + [InlineData("Mozilla/5.0 (compatible; DotBot/1.1; http://www.opensiteexplorer.org/dotbot, help@moz.com)", "OpenSite")] + [InlineData("Mozilla/5.0 (X11; U; Linux Core i7-4980HQ; de; rv:32.0; compatible; JobboerseBot; http://www.jobboerse.com/bot.htm) Gecko/20100101 Firefox/38.0", "Jobboerse")] + [InlineData("Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)", "Majestic")] + [InlineData("Mozilla/5.0 (compatible; SemrushBot/2~bl; +http://www.semrush.com/bot.html)", "SEMRush")] + [InlineData("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36 Google (+https://developers.google.com/+/web/snippet/)", "Google+")] + [InlineData("Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)", "Yandex")] + [InlineData("Mozilla/5.0 (compatible; YandexImages/3.0; +http://yandex.com/bots)", "Yandex")] + [InlineData("Mozilla/5.0 (compatible; Yahoo! Slurp/3.0; http://help.yahoo.com/help/us/ysearch/slurp)", "Yahoo Slurp")] + [InlineData("msnbot/1.0 (+http://search.msn.com/msnbot.htm)", "MSNBot")] + [InlineData("msnbot/2.0b (+http://search.msn.com/msnbot.htm)", "MSNBot")] + [InlineData("Mozilla/5.0 (compatible; AhrefsBot/5.0; +http://ahrefs.com/robot/)", "Ahrefs")] + [InlineData("Mozilla/5.0 (compatible; seoscanners.net/1; +spider@seoscanners.net)", "SEO Scanners")] + [InlineData("Mozilla/5.0 (compatible; SEOkicks-Robot; +http://www.seokicks.de/robot.html)", "SEOkicks")] + [InlineData("facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)", "Facebook")] + [InlineData("Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)", "BingBot")] + [InlineData("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534+ (KHTML, like Gecko) BingPreview/1.0b", "Bing Preview")] + [InlineData("CheckMarkNetwork/1.0 (+http://www.checkmarknetwork.com/spider.html)", "CheckMark")] + [InlineData("Mozilla/5.0 (compatible; BLEXBot/1.0; +http://webmeup-crawler.com/)", "BLEXBot")] + [InlineData("Mozilla/5.0 (compatible; Linux x86_64; Mail.RU_Bot/Fast/2.0; +http://go.mail.ru/help/robots)", "Mail.ru")] + [InlineData("Mozilla/5.0 (compatible; adscanner/)", "AdScanner")] + [InlineData("Mozilla/5.0 (compatible; SISTRIX Crawler; http://crawler.sistrix.net/)", "Sistrix")] + [InlineData("Mozilla/5.0 (Linux; Android 7.0;) AppleWebKit/537.36 (KHTML, like Gecko) Mobile Safari/537.36 (compatible; PetalBot;+https://aspiegel.com/petalbot)", "PetalBot")] + public void BotTests(string ua, string name) + { + HttpUserAgentInformation uaInfo = HttpUserAgentInformation.Parse(ua); + + uaInfo.Name.Should().Be(name); + uaInfo.Version.Should().Be(null); + uaInfo.UserAgent.Should().Be(ua); + + uaInfo.Type.Should().Be(HttpUserAgentType.Robot); + + uaInfo.Platform.Should().Be(null); + uaInfo.MobileDeviceType.Should().Be(null); + + uaInfo.IsBrowser().Should().Be(false); + uaInfo.IsMobile().Should().Be(false); + uaInfo.IsRobot().Should().Be(true); + } + } +} diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/MyCSharp.HttpUserAgentParser.UnitTests.csproj b/tests/MyCSharp.HttpUserAgentParser.UnitTests/MyCSharp.HttpUserAgentParser.UnitTests.csproj new file mode 100644 index 0000000..553c457 --- /dev/null +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/MyCSharp.HttpUserAgentParser.UnitTests.csproj @@ -0,0 +1,26 @@ + + + + Exe + net5.0 + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/version.json b/version.json new file mode 100644 index 0000000..7ebe4ab --- /dev/null +++ b/version.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "0.1", + "nugetPackageVersion": { + "semVer": 1 // optional. Set to either 1 or 2 to control how the NuGet package version string is generated. Default is 1. + }, + "publicReleaseRefSpec": [ + "^refs/heads/master$", // we release out of master + ], + "cloudBuild": { + "buildNumber": { + "enabled": true + } + }, + "release": { + "versionIncrement" : "build", + "firstUnstableTag": "preview" + } + } \ No newline at end of file From de35be39effab54d13f43ba524498e45dee42211 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sat, 15 May 2021 17:58:22 +0200 Subject: [PATCH 02/36] add bot tests --- .../HttpUserAgentParser.cs | 4 +-- .../HttpUserAgentPlatformType.cs | 2 +- .../HttpUserAgentStatics.cs | 35 +++++++++++++++---- .../HttpUserAgentParserTests.cs | 24 ++++++------- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs index da0b183..1573d58 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs @@ -72,7 +72,7 @@ public static bool TryGetBrowser(string userAgent, [NotNullWhen(true)] out (stri { foreach (KeyValuePair item in HttpUserAgentStatics.Robots) { - if (Regex.IsMatch(userAgent, $"{Regex.Escape(item.Key)}", RegexOptions.IgnoreCase)) + if (userAgent.Contains(item.Key, StringComparison.OrdinalIgnoreCase)) { return item.Value; } @@ -90,7 +90,7 @@ public static bool TryGetRobot(string userAgent, [NotNullWhen(true)] out string? { foreach (KeyValuePair item in HttpUserAgentStatics.Mobiles) { - if (userAgent.IndexOf(item.Key, StringComparison.OrdinalIgnoreCase) != -1) + if (userAgent.Contains(item.Key, StringComparison.OrdinalIgnoreCase)) { return item.Value; } diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformType.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformType.cs index 5c6bda8..3bdd447 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformType.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformType.cs @@ -2,7 +2,7 @@ { public enum HttpUserAgentPlatformType { - Unknown, + Unknown = 0, Generic, Windows, Linux, diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs index 6d33676..abdd81b 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs @@ -177,26 +177,47 @@ public static class HttpUserAgentStatics internal static Dictionary Robots = new() { { "googlebot", "Googlebot" }, + { "googleweblight","Google Web Light" }, + { "PetalBot", "PetalBot" }, + { "DuplexWeb-Google", "DuplexWeb-Google" }, + { "Storebot-Google", "Storebot-Google" }, { "msnbot", "MSNBot" }, { "baiduspider", "Baiduspider" }, - { "bingbot", "Bing" }, - { "slurp", "Inktomi Slurp" }, + { "Google Favicon", "Google Favicon" }, + { "Jobboerse", "Jobboerse" }, + { "bingbot", "BingBot" }, + { "BingPreview", "Bing Preview" }, + { "slurp", "Slurp" }, { "yahoo", "Yahoo" }, { "ask jeeves", "Ask Jeeves" }, { "fastcrawler", "FastCrawler" }, { "infoseek", "InfoSeek Robot 1.0" }, { "lycos", "Lycos" }, - { "yandex", "YandexBot" }, - { "mediapartners-google", "MediaPartners Google" }, - { "apis-google", "APIs-Google" }, + { "YandexBot", "YandexBot" }, + { "YandexImages", "YandexImages" }, + { "mediapartners-google", "Mediapartners Google" }, + { "apis-google", "APIs Google" }, { "CRAZYWEBCRAWLER", "Crazy Webcrawler" }, + { "AdsBot-Google-Mobile", "AdsBot Google Mobile" }, { "adsbot-google", "AdsBot Google" }, - { "feedfetcher-google", "Feedfetcher Google" }, + { "feedfetcher-google", "FeedFetcher-Google" }, { "google-read-aloud", "Google-Read-Aloud" }, { "curious george", "Curious George" }, { "ia_archiver", "Alexa Crawler" }, - { "MJ12bot", "Majestic-12" }, + { "MJ12bot", "Majestic" }, { "Uptimebot", "Uptimebot" }, + { "CheckMarkNetwork", "CheckMark" }, + { "facebookexternalhit", "Facebook" }, + { "adscanner", "AdScanner" }, + { "AhrefsBot", "Ahrefs" }, + { "BLEXBot", "BLEXBot" }, + { "DotBot", "OpenSite" }, + { "Mail.RU_Bot", "Mail.ru" }, + { "MegaIndex", "MegaIndex" }, + { "SemrushBot", "SEMRush" }, + { "SEOkicks", "SEOkicks" }, + { "seoscanners.net", "SEO Scanners" }, + { "Sistrix", "Sistrix" } }; internal static Dictionary Tools = new() diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs index 46edcd5..7671d13 100644 --- a/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs @@ -88,24 +88,24 @@ public void BrowserTests(string ua, string name, string version, string platform [InlineData("Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Googlebot/2.1; +http://www.google.com/bot.html) Chrome/1.2.3 Safari/537.36", "Googlebot")] [InlineData("Googlebot/2.1 (+http://www.google.com/bot.html)", "Googlebot")] [InlineData("Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/1.2.3 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)", "Googlebot")] - [InlineData("Mediapartners-Google/2.1; +http://www.google.com/bot.html)", "MMediapartners-Google")] + [InlineData("Mediapartners-Google/2.1; +http://www.google.com/bot.html)", "Mediapartners Google")] [InlineData("FeedFetcher-Google; (+http://www.google.com/feedfetcher.html)", "FeedFetcher-Google")] [InlineData("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36 (compatible; Google-Read-Aloud; +https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers)", "Google-Read-Aloud")] [InlineData("Mozilla/5.0 (Linux; Android 7.0; SM-G930V Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.125 Mobile Safari/537.36 (compatible; Google-Read-Aloud; +https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers)", "Google-Read-Aloud")] [InlineData("Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012; DuplexWeb-Google/1.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Mobile Safari/537.36", "DuplexWeb-Google")] [InlineData("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36 Google Favicon", "Google Favicon")] - [InlineData("Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko; googleweblight) Chrome/38.0.1025.166 Mobile Safari/535.19", "googleweblight")] + [InlineData("Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko; googleweblight) Chrome/38.0.1025.166 Mobile Safari/535.19", "Google Web Light")] [InlineData("Mozilla/5.0 (X11; Linux x86_64; Storebot-Google/1.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36", "Storebot-Google")] [InlineData("Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012; Storebot-Google/1.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36", "Storebot-Google")] // Bing - [InlineData("Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) ", "BingBot")] - [InlineData("Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) ", "BingBot")] - [InlineData("Mozilla/5.0 (Windows Phone 8.1; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 530) like Gecko (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) ", "BingBot")] + [InlineData("Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)", "BingBot")] + [InlineData("Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)", "BingBot")] + [InlineData("Mozilla/5.0 (Windows Phone 8.1; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 530) like Gecko (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)", "BingBot")] [InlineData("Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) Chrome/1.2.3.4 Safari/537.36 Edg/1.2.3.4", "BingBot")] - [InlineData("Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/1.2.3.4  Mobile Safari/537.36 Edg/1.2.3.4 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) ", "BingBot")] - [InlineData("Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)", "Baidu")] + [InlineData("Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/1.2.3.4  Mobile Safari/537.36 Edg/1.2.3.4 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)", "BingBot")] + [InlineData("Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)", "Baiduspider")] [InlineData("Mozilla/5.0 (compatible; MJ12bot/v1.4.5; http://www.majestic12.co.uk/bot.php?+)", "Majestic")] - [InlineData("Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)", "Yahoo Slurp")] + [InlineData("Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)", "Slurp")] [InlineData("Mozilla/5.0 (compatible; MegaIndex.ru/2.0; +http://megaindex.com/crawler)", "MegaIndex")] [InlineData("Mozilla/5.0 (compatible; AhrefsBot/5.2; +http://ahrefs.com/robot/)", "Ahrefs")] [InlineData("Mozilla/5.0 (compatible; SemrushBot/7~bl; +http://www.semrush.com/bot.html)", "SEMRush")] @@ -113,17 +113,15 @@ public void BrowserTests(string ua, string name, string version, string platform [InlineData("Mozilla/5.0 (X11; U; Linux Core i7-4980HQ; de; rv:32.0; compatible; JobboerseBot; http://www.jobboerse.com/bot.htm) Gecko/20100101 Firefox/38.0", "Jobboerse")] [InlineData("Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)", "Majestic")] [InlineData("Mozilla/5.0 (compatible; SemrushBot/2~bl; +http://www.semrush.com/bot.html)", "SEMRush")] - [InlineData("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36 Google (+https://developers.google.com/+/web/snippet/)", "Google+")] - [InlineData("Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)", "Yandex")] - [InlineData("Mozilla/5.0 (compatible; YandexImages/3.0; +http://yandex.com/bots)", "Yandex")] - [InlineData("Mozilla/5.0 (compatible; Yahoo! Slurp/3.0; http://help.yahoo.com/help/us/ysearch/slurp)", "Yahoo Slurp")] + [InlineData("Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)", "YandexBot")] + [InlineData("Mozilla/5.0 (compatible; YandexImages/3.0; +http://yandex.com/bots)", "YandexImages")] + [InlineData("Mozilla/5.0 (compatible; Yahoo! Slurp/3.0; http://help.yahoo.com/help/us/ysearch/slurp)", "Slurp")] [InlineData("msnbot/1.0 (+http://search.msn.com/msnbot.htm)", "MSNBot")] [InlineData("msnbot/2.0b (+http://search.msn.com/msnbot.htm)", "MSNBot")] [InlineData("Mozilla/5.0 (compatible; AhrefsBot/5.0; +http://ahrefs.com/robot/)", "Ahrefs")] [InlineData("Mozilla/5.0 (compatible; seoscanners.net/1; +spider@seoscanners.net)", "SEO Scanners")] [InlineData("Mozilla/5.0 (compatible; SEOkicks-Robot; +http://www.seokicks.de/robot.html)", "SEOkicks")] [InlineData("facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)", "Facebook")] - [InlineData("Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)", "BingBot")] [InlineData("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534+ (KHTML, like Gecko) BingPreview/1.0b", "Bing Preview")] [InlineData("CheckMarkNetwork/1.0 (+http://www.checkmarknetwork.com/spider.html)", "CheckMark")] [InlineData("Mozilla/5.0 (compatible; BLEXBot/1.0; +http://webmeup-crawler.com/)", "BLEXBot")] From f5fd2adcc30ae701572ff874971459bf0d3821b2 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sat, 15 May 2021 18:07:15 +0200 Subject: [PATCH 03/36] add regex map --- .../HttpUserAgentParser.cs | 2 +- .../HttpUserAgentStatics.cs | 77 ++++++++++--------- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs index 1573d58..7d2b662 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs @@ -53,7 +53,7 @@ public static (string Name, string? Version)? GetBrowser(string userAgent) { foreach (var item in HttpUserAgentStatics.Browsers) { - Match match = Regex.Match(userAgent, $@"{item.Key}.*?([0-9\.]+)", RegexOptions.IgnoreCase); + Match match = item.Key.Match(userAgent); if (match.Success) { return (item.Value, match.Groups[1].Value); diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs index abdd81b..9207e4c 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Text.RegularExpressions; namespace MyCSharp.HttpUserAgentParser { @@ -50,43 +51,45 @@ public static class HttpUserAgentStatics new("symbian", "Symbian OS", HttpUserAgentPlatformType.Symbian), }; - internal static Dictionary Browsers = new() + private static RegexOptions DefaultBrowserRegexFlags = RegexOptions.IgnoreCase | RegexOptions.Compiled; + private static Regex CreateDefaultRegex(string key) => new($@"{key}.*?([0-9\.]+)", DefaultBrowserRegexFlags); + internal static Dictionary Browsers = new() { - { "OPR", "Opera" }, - { "Flock", "Flock" }, - { "Edge", "Edge" }, - { "EdgA", "Edge" }, - { "Edg", "Edge" }, - { "Vivaldi", "Vivaldi" }, - { "Brave Chrome", "Brave" }, - { "Chrome", "Chrome" }, - { "CriOS", "Chrome" }, - { "Opera.*?Version", "Opera" }, - { "Opera", "Opera" }, - { "MSIE", "Internet Explorer" }, - { "Internet Explorer", "Internet Explorer" }, - { "Trident.* rv", "Internet Explorer" }, - { "Shiira", "Shiira" }, - { "Firefox", "Firefox" }, - { "FxiOS", "Firefox" }, - { "Chimera", "Chimera" }, - { "Phoenix", "Phoenix" }, - { "Firebird", "Firebird" }, - { "Camino", "Camino" }, - { "Netscape", "Netscape" }, - { "OmniWeb", "OmniWeb" }, - { "Safari", "Safari" }, - { "Mozilla", "Mozilla" }, - { "Konqueror", "Konqueror" }, - { "icab", "iCab" }, - { "Lynx", "Lynx" }, - { "Links", "Links" }, - { "hotjava", "HotJava" }, - { "amaya", "Amaya" }, - { "IBrowse", "IBrowse" }, - { "Maxthon", "Maxthon" }, - { "ipod touch", "Apple iPod" }, - { "Ubuntu", "Ubuntu Web Browser" }, + { CreateDefaultRegex("OPR"), "Opera" }, + { CreateDefaultRegex("Flock"), "Flock" }, + { CreateDefaultRegex("Edge"), "Edge" }, + { CreateDefaultRegex("EdgA"), "Edge" }, + { CreateDefaultRegex("Edg"), "Edge" }, + { CreateDefaultRegex("Vivaldi"), "Vivaldi" }, + { CreateDefaultRegex("Brave Chrome"), "Brave" }, + { CreateDefaultRegex("Chrome"), "Chrome" }, + { CreateDefaultRegex("CriOS"), "Chrome" }, + { CreateDefaultRegex("Opera.*?Version"), "Opera" }, + { CreateDefaultRegex("Opera"), "Opera" }, + { CreateDefaultRegex("MSIE"), "Internet Explorer" }, + { CreateDefaultRegex("Internet Explorer"), "Internet Explorer" }, + { CreateDefaultRegex("Trident.* rv"), "Internet Explorer" }, + { CreateDefaultRegex("Shiira"), "Shiira" }, + { CreateDefaultRegex("Firefox"), "Firefox" }, + { CreateDefaultRegex("FxiOS"), "Firefox" }, + { CreateDefaultRegex("Chimera"), "Chimera" }, + { CreateDefaultRegex("Phoenix"), "Phoenix" }, + { CreateDefaultRegex("Firebird"), "Firebird" }, + { CreateDefaultRegex("Camino"), "Camino" }, + { CreateDefaultRegex("Netscape"), "Netscape" }, + { CreateDefaultRegex("OmniWeb"), "OmniWeb" }, + { CreateDefaultRegex("Safari"), "Safari" }, + { CreateDefaultRegex("Mozilla"), "Mozilla" }, + { CreateDefaultRegex("Konqueror"), "Konqueror" }, + { CreateDefaultRegex("icab"), "iCab" }, + { CreateDefaultRegex("Lynx"), "Lynx" }, + { CreateDefaultRegex("Links"), "Links" }, + { CreateDefaultRegex("hotjava"), "HotJava" }, + { CreateDefaultRegex("amaya"), "Amaya" }, + { CreateDefaultRegex("IBrowse"), "IBrowse" }, + { CreateDefaultRegex("Maxthon"), "Maxthon" }, + { CreateDefaultRegex("ipod touch"), "Apple iPod" }, + { CreateDefaultRegex("Ubuntu"), "Ubuntu Web Browser" }, }; internal static Dictionary Mobiles = new() @@ -177,7 +180,7 @@ public static class HttpUserAgentStatics internal static Dictionary Robots = new() { { "googlebot", "Googlebot" }, - { "googleweblight","Google Web Light" }, + { "googleweblight", "Google Web Light" }, { "PetalBot", "PetalBot" }, { "DuplexWeb-Google", "DuplexWeb-Google" }, { "Storebot-Google", "Storebot-Google" }, From 0f313b9eee6005bc3d9117f56291272c21426c81 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 15:13:30 +0200 Subject: [PATCH 04/36] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- .../ServiceCollectionExtensions.cs | 1 - .../HttpUserAgentInformation.cs | 2 +- .../HttpUserAgentInformationExtensions.cs | 10 +++++----- .../HttpUserAgentParser.cs | 6 +++--- .../HttpUserAgentPlatformType.cs | 4 ++-- .../HttpUserAgentStatics.cs | 12 ++++++------ .../HttpUserAgentType.cs | 4 ++-- 7 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/MyCSharp.HttpUserAgentParser/DependencyInjection/ServiceCollectionExtensions.cs b/src/MyCSharp.HttpUserAgentParser/DependencyInjection/ServiceCollectionExtensions.cs index 8f48057..8b7fa0f 100644 --- a/src/MyCSharp.HttpUserAgentParser/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/MyCSharp.HttpUserAgentParser/DependencyInjection/ServiceCollectionExtensions.cs @@ -37,6 +37,5 @@ public static HttpUserAgentParserDependencyInjectionOptions AddHttpUserAgentPars return options; } - } } diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs index 15bca37..f09c39e 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs @@ -11,7 +11,7 @@ public readonly struct HttpUserAgentInformation public string? MobileDeviceType { get; } - public HttpUserAgentInformation(string userAgent, HttpUserAgentType type, HttpUserAgentPlatformInformation? platform, string? name, string? version, string? mobileDeviceType) + public HttpUserAgentInformation(string userAgent, HttpUserAgentType type, in HttpUserAgentPlatformInformation? platform, string? name, string? version, string? mobileDeviceType) { UserAgent = userAgent; Type = type; diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformationExtensions.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformationExtensions.cs index 5108b28..cb2b4d2 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformationExtensions.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformationExtensions.cs @@ -2,9 +2,9 @@ { public static class HttpUserAgentInformationExtensions { - public static bool IsType(this HttpUserAgentInformation userAgent, HttpUserAgentType type) => userAgent.Type == type; - public static bool IsRobot(this HttpUserAgentInformation userAgent) => IsType(userAgent, HttpUserAgentType.Robot); - public static bool IsBrowser(this HttpUserAgentInformation userAgent) => IsType(userAgent, HttpUserAgentType.Browser); - public static bool IsMobile(this HttpUserAgentInformation userAgent) => userAgent.MobileDeviceType is not null; + public static bool IsType(this in HttpUserAgentInformation userAgent, HttpUserAgentType type) => userAgent.Type == type; + public static bool IsRobot(this in HttpUserAgentInformation userAgent) => IsType(userAgent, HttpUserAgentType.Robot); + public static bool IsBrowser(this in HttpUserAgentInformation userAgent) => IsType(userAgent, HttpUserAgentType.Browser); + public static bool IsMobile(this in HttpUserAgentInformation userAgent) => userAgent.MobileDeviceType is not null; } -} \ No newline at end of file +} diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs index 7d2b662..6f83fe3 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs @@ -33,7 +33,7 @@ public static HttpUserAgentInformation Parse(string userAgent) public static HttpUserAgentPlatformInformation? GetPlatform(string userAgent) { - foreach (var item in HttpUserAgentStatics.Platforms) + foreach (HttpUserAgentPlatformInformation item in HttpUserAgentStatics.Platforms) { if (Regex.IsMatch(userAgent, $"{Regex.Escape(item.Id)}", RegexOptions.IgnoreCase)) { @@ -51,7 +51,7 @@ public static bool TryGetPlatform(string userAgent, [NotNullWhen(true)] out Http public static (string Name, string? Version)? GetBrowser(string userAgent) { - foreach (var item in HttpUserAgentStatics.Browsers) + foreach (KeyValuePair item in HttpUserAgentStatics.Browsers) { Match match = item.Key.Match(userAgent); if (match.Success) @@ -104,4 +104,4 @@ public static bool TryGetMobileDevice(string userAgent, [NotNullWhen(true)] out return device is not null; } } -} \ No newline at end of file +} diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformType.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformType.cs index 3bdd447..b270917 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformType.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformType.cs @@ -1,6 +1,6 @@ namespace MyCSharp.HttpUserAgentParser { - public enum HttpUserAgentPlatformType + public enum HttpUserAgentPlatformType : byte { Unknown = 0, Generic, @@ -13,4 +13,4 @@ public enum HttpUserAgentPlatformType Android, Symbian } -} \ No newline at end of file +} diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs index 9207e4c..8fc0983 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs @@ -5,7 +5,7 @@ namespace MyCSharp.HttpUserAgentParser { public static class HttpUserAgentStatics { - public static HashSet Platforms = new() + public static readonly HashSet Platforms = new() { new("windows nt 10.0", "Windows 10", HttpUserAgentPlatformType.Windows), new("windows nt 6.3", "Windows 8.1", HttpUserAgentPlatformType.Windows), @@ -51,7 +51,7 @@ public static class HttpUserAgentStatics new("symbian", "Symbian OS", HttpUserAgentPlatformType.Symbian), }; - private static RegexOptions DefaultBrowserRegexFlags = RegexOptions.IgnoreCase | RegexOptions.Compiled; + private const RegexOptions DefaultBrowserRegexFlags = RegexOptions.IgnoreCase | RegexOptions.Compiled; private static Regex CreateDefaultRegex(string key) => new($@"{key}.*?([0-9\.]+)", DefaultBrowserRegexFlags); internal static Dictionary Browsers = new() { @@ -92,7 +92,7 @@ public static class HttpUserAgentStatics { CreateDefaultRegex("Ubuntu"), "Ubuntu Web Browser" }, }; - internal static Dictionary Mobiles = new() + internal static readonly Dictionary Mobiles = new() { // Legacy { "mobileexplorer", "Mobile Explorer" }, @@ -177,7 +177,7 @@ public static class HttpUserAgentStatics { "cellphone", "Generic Mobile" }, }; - internal static Dictionary Robots = new() + internal static readonly Dictionary Robots = new() { { "googlebot", "Googlebot" }, { "googleweblight", "Google Web Light" }, @@ -223,9 +223,9 @@ public static class HttpUserAgentStatics { "Sistrix", "Sistrix" } }; - internal static Dictionary Tools = new() + internal static readonly Dictionary Tools = new() { { "curl", "curl" } }; } -} \ No newline at end of file +} diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentType.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentType.cs index bf74cc1..1b9f286 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentType.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentType.cs @@ -1,9 +1,9 @@ namespace MyCSharp.HttpUserAgentParser { - public enum HttpUserAgentType + public enum HttpUserAgentType : byte { Unknown, Browser, Robot, } -} \ No newline at end of file +} From 98cdb6458ea81fbc898f74279ec1673964fd8ed9 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 15:55:25 +0200 Subject: [PATCH 05/36] add memory cache implementation --- MyCSharp.HttpUserAgentParser.sln | 7 ++++ ...rMemoryCacheServiceCollectionExtensions.cs | 26 +++++++++++++ ...HttpUserAgentParserMemoryCachedProvider.cs | 30 +++++++++++++++ ...rAgentParserMemoryCachedProviderOptions.cs | 38 +++++++++++++++++++ ...arp.HttpUserAgentParser.MemoryCache.csproj | 10 +++++ ...AgentParserServiceCollectionExtensions.cs} | 0 .../DefaultHttpUserAgentParserProvider.cs | 2 +- 7 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs create mode 100644 src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs create mode 100644 src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs rename src/MyCSharp.HttpUserAgentParser/DependencyInjection/{ServiceCollectionExtensions.cs => HttpUserAgentParserServiceCollectionExtensions.cs} (100%) diff --git a/MyCSharp.HttpUserAgentParser.sln b/MyCSharp.HttpUserAgentParser.sln index f6f825f..dedeea8 100644 --- a/MyCSharp.HttpUserAgentParser.sln +++ b/MyCSharp.HttpUserAgentParser.sln @@ -15,6 +15,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyCSharp.HttpUserAgentParse EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F54C9296-4EF7-40F0-9F20-F23A2270ABC9}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyCSharp.HttpUserAgentParser.MemoryCache", "src\MyCSharp.HttpUserAgentParser.MemoryCache\MyCSharp.HttpUserAgentParser.MemoryCache.csproj", "{3C8CCD44-F47C-4624-8997-54C42F02E376}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -37,6 +39,10 @@ Global {75960783-8BF9-479C-9ECF-E9653B74C9A2}.Debug|Any CPU.Build.0 = Debug|Any CPU {75960783-8BF9-479C-9ECF-E9653B74C9A2}.Release|Any CPU.ActiveCfg = Release|Any CPU {75960783-8BF9-479C-9ECF-E9653B74C9A2}.Release|Any CPU.Build.0 = Release|Any CPU + {3C8CCD44-F47C-4624-8997-54C42F02E376}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C8CCD44-F47C-4624-8997-54C42F02E376}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C8CCD44-F47C-4624-8997-54C42F02E376}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C8CCD44-F47C-4624-8997-54C42F02E376}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -46,6 +52,7 @@ Global {3357BEC0-8216-409E-A539-F9A71DBACB81} = {008A2BAB-78B4-42EB-A5D4-DE434438CEF0} {F16697F7-74B4-441D-A0C0-1A0572AC3AB0} = {F54C9296-4EF7-40F0-9F20-F23A2270ABC9} {75960783-8BF9-479C-9ECF-E9653B74C9A2} = {F54C9296-4EF7-40F0-9F20-F23A2270ABC9} + {3C8CCD44-F47C-4624-8997-54C42F02E376} = {008A2BAB-78B4-42EB-A5D4-DE434438CEF0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E8B0C994-0BF2-4692-9E22-E48B265B2804} diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs b/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs new file mode 100644 index 0000000..a859c71 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using MyCSharp.HttpUserAgentParser.DependencyInjection; +using MyCSharp.HttpUserAgentParser.Providers; + +namespace MyCSharp.HttpUserAgentParser.MemoryCache.DependencyInjection +{ + public static class HttpUserAgentParserMemoryCacheServiceCollectionExtensions + { + /// + /// Registers as singleton to + /// + public static HttpUserAgentParserDependencyInjectionOptions AddMemoryCachedHttpUserAgentParser( + this IServiceCollection services, Action? options = null) + { + HttpUserAgentParserMemoryCachedProviderOptions providerOptions = new(); + options?.Invoke(providerOptions); + + // register options + services.AddSingleton(providerOptions); + + // register cache provider + return services.AddHttpUserAgentParser(); + } + } +} diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs new file mode 100644 index 0000000..218dc54 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs @@ -0,0 +1,30 @@ +using Microsoft.Extensions.Caching.Memory; +using MyCSharp.HttpUserAgentParser.Providers; + +namespace MyCSharp.HttpUserAgentParser.MemoryCache +{ + public class HttpUserAgentParserMemoryCachedProvider : IHttpUserAgentParserProvider + { + private readonly IMemoryCache _memoryCache; + private readonly HttpUserAgentParserMemoryCachedProviderOptions _options; + + public HttpUserAgentParserMemoryCachedProvider(IMemoryCache memoryCache, HttpUserAgentParserMemoryCachedProviderOptions options) + { + _memoryCache = memoryCache; + _options = options; + } + + private static string Cleanup(string userAgent) + => userAgent.Trim(); + + + public HttpUserAgentInformation Parse(string userAgent) + { + return _memoryCache.GetOrCreate(Cleanup(userAgent), entry => + { + entry.SlidingExpiration = _options.CacheEntryOptions.SlidingExpiration; + return HttpUserAgentInformation.Parse(userAgent); + }); + } + } +} \ No newline at end of file diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs new file mode 100644 index 0000000..4297e84 --- /dev/null +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs @@ -0,0 +1,38 @@ +using System; +using Microsoft.Extensions.Caching.Memory; + +namespace MyCSharp.HttpUserAgentParser.MemoryCache +{ + public class HttpUserAgentParserMemoryCachedProviderOptions + { + /// + /// Default of is 256. + /// Default of is 1 day + /// + public MemoryCacheOptions CacheOptions { get; } + public MemoryCacheEntryOptions CacheEntryOptions { get; } + + public HttpUserAgentParserMemoryCachedProviderOptions() + : this(null, null) { } + + public HttpUserAgentParserMemoryCachedProviderOptions(MemoryCacheOptions cacheOptions) + : this(cacheOptions, null) { } + + + public HttpUserAgentParserMemoryCachedProviderOptions(MemoryCacheEntryOptions cacheEntryOptions) + : this(null, cacheEntryOptions) { } + + public HttpUserAgentParserMemoryCachedProviderOptions(MemoryCacheOptions? cacheOptions = null, MemoryCacheEntryOptions? cacheEntryOptions = null) + { + CacheEntryOptions = cacheEntryOptions ?? new MemoryCacheEntryOptions + { + // defaults + SlidingExpiration = TimeSpan.FromDays(1) + }; + CacheOptions = cacheOptions ?? new MemoryCacheOptions + { + SizeLimit = 256 + }; + } + } +} \ No newline at end of file diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/MyCSharp.HttpUserAgentParser.MemoryCache.csproj b/src/MyCSharp.HttpUserAgentParser.MemoryCache/MyCSharp.HttpUserAgentParser.MemoryCache.csproj index f208d30..f4c82d2 100644 --- a/src/MyCSharp.HttpUserAgentParser.MemoryCache/MyCSharp.HttpUserAgentParser.MemoryCache.csproj +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/MyCSharp.HttpUserAgentParser.MemoryCache.csproj @@ -2,6 +2,16 @@ net5.0 + enable + + + + + + + + + diff --git a/src/MyCSharp.HttpUserAgentParser/DependencyInjection/ServiceCollectionExtensions.cs b/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserServiceCollectionExtensions.cs similarity index 100% rename from src/MyCSharp.HttpUserAgentParser/DependencyInjection/ServiceCollectionExtensions.cs rename to src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserServiceCollectionExtensions.cs diff --git a/src/MyCSharp.HttpUserAgentParser/Providers/DefaultHttpUserAgentParserProvider.cs b/src/MyCSharp.HttpUserAgentParser/Providers/DefaultHttpUserAgentParserProvider.cs index c682294..8c82214 100644 --- a/src/MyCSharp.HttpUserAgentParser/Providers/DefaultHttpUserAgentParserProvider.cs +++ b/src/MyCSharp.HttpUserAgentParser/Providers/DefaultHttpUserAgentParserProvider.cs @@ -3,6 +3,6 @@ public class DefaultHttpUserAgentParserProvider : IHttpUserAgentParserProvider { public HttpUserAgentInformation Parse(string userAgent) - => HttpUserAgentParser.Parse(userAgent); + => HttpUserAgentInformation.Parse(userAgent); } } \ No newline at end of file From 7e597e1acc24dc1f1223e30e451f26d0f0b68886 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 16:01:05 +0200 Subject: [PATCH 06/36] add regex --- .../HttpUserAgentParser.cs | 21 ++- .../HttpUserAgentPlatformInformation.cs | 10 +- .../HttpUserAgentStatics.cs | 159 +++++++++--------- 3 files changed, 97 insertions(+), 93 deletions(-) diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs index 6f83fe3..0cad8a8 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; @@ -35,7 +34,7 @@ public static HttpUserAgentInformation Parse(string userAgent) { foreach (HttpUserAgentPlatformInformation item in HttpUserAgentStatics.Platforms) { - if (Regex.IsMatch(userAgent, $"{Regex.Escape(item.Id)}", RegexOptions.IgnoreCase)) + if (item.Regex.IsMatch(userAgent)) { return item; } @@ -51,12 +50,12 @@ public static bool TryGetPlatform(string userAgent, [NotNullWhen(true)] out Http public static (string Name, string? Version)? GetBrowser(string userAgent) { - foreach (KeyValuePair item in HttpUserAgentStatics.Browsers) + foreach ((Regex key, string? value) in HttpUserAgentStatics.Browsers) { - Match match = item.Key.Match(userAgent); + Match match = key.Match(userAgent); if (match.Success) { - return (item.Value, match.Groups[1].Value); + return (value, match.Groups[1].Value); } } @@ -70,11 +69,11 @@ public static bool TryGetBrowser(string userAgent, [NotNullWhen(true)] out (stri public static string? GetRobot(string userAgent) { - foreach (KeyValuePair item in HttpUserAgentStatics.Robots) + foreach ((string key, string value) in HttpUserAgentStatics.Robots) { - if (userAgent.Contains(item.Key, StringComparison.OrdinalIgnoreCase)) + if (userAgent.Contains(key, StringComparison.OrdinalIgnoreCase)) { - return item.Value; + return value; } } @@ -88,11 +87,11 @@ public static bool TryGetRobot(string userAgent, [NotNullWhen(true)] out string? public static string? GetMobileDevice(string userAgent) { - foreach (KeyValuePair item in HttpUserAgentStatics.Mobiles) + foreach ((string key, string value) in HttpUserAgentStatics.Mobiles) { - if (userAgent.Contains(item.Key, StringComparison.OrdinalIgnoreCase)) + if (userAgent.Contains(key, StringComparison.OrdinalIgnoreCase)) { - return item.Value; + return value; } } diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformInformation.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformInformation.cs index a065fa6..1f1bb03 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformInformation.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformInformation.cs @@ -1,14 +1,16 @@ -namespace MyCSharp.HttpUserAgentParser +using System.Text.RegularExpressions; + +namespace MyCSharp.HttpUserAgentParser { public readonly struct HttpUserAgentPlatformInformation { - public string Id { get; } + public Regex Regex { get; } public string Name { get; } public HttpUserAgentPlatformType PlatformType { get; } - public HttpUserAgentPlatformInformation(string id, string name, HttpUserAgentPlatformType platformType) + public HttpUserAgentPlatformInformation(Regex regex, string name, HttpUserAgentPlatformType platformType) { - Id = id; + Regex = regex; Name = name; PlatformType = platformType; } diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs index 8fc0983..d283126 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs @@ -5,91 +5,94 @@ namespace MyCSharp.HttpUserAgentParser { public static class HttpUserAgentStatics { + + private const RegexOptions DefaultPlatformsRegexFlags = RegexOptions.IgnoreCase | RegexOptions.Compiled; + private static Regex CreateDefaultPlatformRegex(string key) => new(Regex.Escape($"{key}"), DefaultPlatformsRegexFlags); public static readonly HashSet Platforms = new() { - new("windows nt 10.0", "Windows 10", HttpUserAgentPlatformType.Windows), - new("windows nt 6.3", "Windows 8.1", HttpUserAgentPlatformType.Windows), - new("windows nt 6.2", "Windows 8", HttpUserAgentPlatformType.Windows), - new("windows nt 6.1", "Windows 7", HttpUserAgentPlatformType.Windows), - new("windows nt 6.0", "Windows Vista", HttpUserAgentPlatformType.Windows), - new("windows nt 5.2", "Windows 2003", HttpUserAgentPlatformType.Windows), - new("windows nt 5.1", "Windows XP", HttpUserAgentPlatformType.Windows), - new("windows nt 5.0", "Windows 2000", HttpUserAgentPlatformType.Windows), - new("windows nt 4.0", "Windows NT 4.0", HttpUserAgentPlatformType.Windows), - new("winnt4.0", "Windows NT 4.0", HttpUserAgentPlatformType.Windows), - new("winnt 4.0", "Windows NT", HttpUserAgentPlatformType.Windows), - new("winnt", "Windows NT", HttpUserAgentPlatformType.Windows), - new("windows 98", "Windows 98", HttpUserAgentPlatformType.Windows), - new("win98", "Windows 98", HttpUserAgentPlatformType.Windows), - new("windows 95", "Windows 95", HttpUserAgentPlatformType.Windows), - new("win95", "Windows 95", HttpUserAgentPlatformType.Windows), - new("windows phone", "Windows Phone", HttpUserAgentPlatformType.Windows), - new("windows", "Unknown Windows OS", HttpUserAgentPlatformType.Windows), - new("android", "Android", HttpUserAgentPlatformType.Android), - new("blackberry", "BlackBerry", HttpUserAgentPlatformType.BlackBerry), - new("iphone", "iOS", HttpUserAgentPlatformType.IOS), - new("ipad", "iOS", HttpUserAgentPlatformType.IOS), - new("ipod", "iOS", HttpUserAgentPlatformType.IOS), - new("os x", "Mac OS X", HttpUserAgentPlatformType.MacOS), - new("ppc mac", "Power PC Mac", HttpUserAgentPlatformType.MacOS), - new("freebsd", "FreeBSD", HttpUserAgentPlatformType.Linux), - new("ppc", "Macintosh", HttpUserAgentPlatformType.Linux), - new("linux", "Linux", HttpUserAgentPlatformType.Linux), - new("debian", "Debian", HttpUserAgentPlatformType.Linux), - new("sunos", "Sun Solaris", HttpUserAgentPlatformType.Generic), - new("beos", "BeOS", HttpUserAgentPlatformType.Generic), - new("apachebench", "ApacheBench", HttpUserAgentPlatformType.Generic), - new("aix", "AIX", HttpUserAgentPlatformType.Generic), - new("irix", "Irix", HttpUserAgentPlatformType.Generic), - new("osf", "DEC OSF", HttpUserAgentPlatformType.Generic), - new("hp-ux", "HP-UX", HttpUserAgentPlatformType.Windows), - new("netbsd", "NetBSD", HttpUserAgentPlatformType.Generic), - new("bsdi", "BSDi", HttpUserAgentPlatformType.Generic), - new("openbsd", "OpenBSD", HttpUserAgentPlatformType.Unix), - new("gnu", "GNU/Linux", HttpUserAgentPlatformType.Linux), - new("unix", "Unknown Unix OS", HttpUserAgentPlatformType.Unix), - new("symbian", "Symbian OS", HttpUserAgentPlatformType.Symbian), + new(CreateDefaultPlatformRegex("windows nt 10.0"), "Windows 10", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("windows nt 6.3"), "Windows 8.1", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("windows nt 6.2"), "Windows 8", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("windows nt 6.1"), "Windows 7", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("windows nt 6.0"), "Windows Vista", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("windows nt 5.2"), "Windows 2003", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("windows nt 5.1"), "Windows XP", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("windows nt 5.0"), "Windows 2000", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("windows nt 4.0"), "Windows NT 4.0", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("winnt4.0"), "Windows NT 4.0", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("winnt 4.0"), "Windows NT", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("winnt"), "Windows NT", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("windows 98"), "Windows 98", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("win98"), "Windows 98", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("windows 95"), "Windows 95", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("win95"), "Windows 95", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("windows phone"), "Windows Phone", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("windows"), "Unknown Windows OS", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("android"), "Android", HttpUserAgentPlatformType.Android), + new(CreateDefaultPlatformRegex("blackberry"), "BlackBerry", HttpUserAgentPlatformType.BlackBerry), + new(CreateDefaultPlatformRegex("iphone"), "iOS", HttpUserAgentPlatformType.IOS), + new(CreateDefaultPlatformRegex("ipad"), "iOS", HttpUserAgentPlatformType.IOS), + new(CreateDefaultPlatformRegex("ipod"), "iOS", HttpUserAgentPlatformType.IOS), + new(CreateDefaultPlatformRegex("os x"), "Mac OS X", HttpUserAgentPlatformType.MacOS), + new(CreateDefaultPlatformRegex("ppc mac"), "Power PC Mac", HttpUserAgentPlatformType.MacOS), + new(CreateDefaultPlatformRegex("freebsd"), "FreeBSD", HttpUserAgentPlatformType.Linux), + new(CreateDefaultPlatformRegex("ppc"), "Macintosh", HttpUserAgentPlatformType.Linux), + new(CreateDefaultPlatformRegex("linux"), "Linux", HttpUserAgentPlatformType.Linux), + new(CreateDefaultPlatformRegex("debian"), "Debian", HttpUserAgentPlatformType.Linux), + new(CreateDefaultPlatformRegex("sunos"), "Sun Solaris", HttpUserAgentPlatformType.Generic), + new(CreateDefaultPlatformRegex("beos"), "BeOS", HttpUserAgentPlatformType.Generic), + new(CreateDefaultPlatformRegex("apachebench"), "ApacheBench", HttpUserAgentPlatformType.Generic), + new(CreateDefaultPlatformRegex("aix"), "AIX", HttpUserAgentPlatformType.Generic), + new(CreateDefaultPlatformRegex("irix"), "Irix", HttpUserAgentPlatformType.Generic), + new(CreateDefaultPlatformRegex("osf"), "DEC OSF", HttpUserAgentPlatformType.Generic), + new(CreateDefaultPlatformRegex("hp-ux"), "HP-UX", HttpUserAgentPlatformType.Windows), + new(CreateDefaultPlatformRegex("netbsd"), "NetBSD", HttpUserAgentPlatformType.Generic), + new(CreateDefaultPlatformRegex("bsdi"), "BSDi", HttpUserAgentPlatformType.Generic), + new(CreateDefaultPlatformRegex("openbsd"), "OpenBSD", HttpUserAgentPlatformType.Unix), + new(CreateDefaultPlatformRegex("gnu"), "GNU/Linux", HttpUserAgentPlatformType.Linux), + new(CreateDefaultPlatformRegex("unix"), "Unknown Unix OS", HttpUserAgentPlatformType.Unix), + new(CreateDefaultPlatformRegex("symbian"), "Symbian OS", HttpUserAgentPlatformType.Symbian), }; private const RegexOptions DefaultBrowserRegexFlags = RegexOptions.IgnoreCase | RegexOptions.Compiled; - private static Regex CreateDefaultRegex(string key) => new($@"{key}.*?([0-9\.]+)", DefaultBrowserRegexFlags); + private static Regex CreateDefaultBrowserRegex(string key) => new($@"{key}.*?([0-9\.]+)", DefaultBrowserRegexFlags); internal static Dictionary Browsers = new() { - { CreateDefaultRegex("OPR"), "Opera" }, - { CreateDefaultRegex("Flock"), "Flock" }, - { CreateDefaultRegex("Edge"), "Edge" }, - { CreateDefaultRegex("EdgA"), "Edge" }, - { CreateDefaultRegex("Edg"), "Edge" }, - { CreateDefaultRegex("Vivaldi"), "Vivaldi" }, - { CreateDefaultRegex("Brave Chrome"), "Brave" }, - { CreateDefaultRegex("Chrome"), "Chrome" }, - { CreateDefaultRegex("CriOS"), "Chrome" }, - { CreateDefaultRegex("Opera.*?Version"), "Opera" }, - { CreateDefaultRegex("Opera"), "Opera" }, - { CreateDefaultRegex("MSIE"), "Internet Explorer" }, - { CreateDefaultRegex("Internet Explorer"), "Internet Explorer" }, - { CreateDefaultRegex("Trident.* rv"), "Internet Explorer" }, - { CreateDefaultRegex("Shiira"), "Shiira" }, - { CreateDefaultRegex("Firefox"), "Firefox" }, - { CreateDefaultRegex("FxiOS"), "Firefox" }, - { CreateDefaultRegex("Chimera"), "Chimera" }, - { CreateDefaultRegex("Phoenix"), "Phoenix" }, - { CreateDefaultRegex("Firebird"), "Firebird" }, - { CreateDefaultRegex("Camino"), "Camino" }, - { CreateDefaultRegex("Netscape"), "Netscape" }, - { CreateDefaultRegex("OmniWeb"), "OmniWeb" }, - { CreateDefaultRegex("Safari"), "Safari" }, - { CreateDefaultRegex("Mozilla"), "Mozilla" }, - { CreateDefaultRegex("Konqueror"), "Konqueror" }, - { CreateDefaultRegex("icab"), "iCab" }, - { CreateDefaultRegex("Lynx"), "Lynx" }, - { CreateDefaultRegex("Links"), "Links" }, - { CreateDefaultRegex("hotjava"), "HotJava" }, - { CreateDefaultRegex("amaya"), "Amaya" }, - { CreateDefaultRegex("IBrowse"), "IBrowse" }, - { CreateDefaultRegex("Maxthon"), "Maxthon" }, - { CreateDefaultRegex("ipod touch"), "Apple iPod" }, - { CreateDefaultRegex("Ubuntu"), "Ubuntu Web Browser" }, + { CreateDefaultBrowserRegex("OPR"), "Opera" }, + { CreateDefaultBrowserRegex("Flock"), "Flock" }, + { CreateDefaultBrowserRegex("Edge"), "Edge" }, + { CreateDefaultBrowserRegex("EdgA"), "Edge" }, + { CreateDefaultBrowserRegex("Edg"), "Edge" }, + { CreateDefaultBrowserRegex("Vivaldi"), "Vivaldi" }, + { CreateDefaultBrowserRegex("Brave Chrome"), "Brave" }, + { CreateDefaultBrowserRegex("Chrome"), "Chrome" }, + { CreateDefaultBrowserRegex("CriOS"), "Chrome" }, + { CreateDefaultBrowserRegex("Opera.*?Version"), "Opera" }, + { CreateDefaultBrowserRegex("Opera"), "Opera" }, + { CreateDefaultBrowserRegex("MSIE"), "Internet Explorer" }, + { CreateDefaultBrowserRegex("Internet Explorer"), "Internet Explorer" }, + { CreateDefaultBrowserRegex("Trident.* rv"), "Internet Explorer" }, + { CreateDefaultBrowserRegex("Shiira"), "Shiira" }, + { CreateDefaultBrowserRegex("Firefox"), "Firefox" }, + { CreateDefaultBrowserRegex("FxiOS"), "Firefox" }, + { CreateDefaultBrowserRegex("Chimera"), "Chimera" }, + { CreateDefaultBrowserRegex("Phoenix"), "Phoenix" }, + { CreateDefaultBrowserRegex("Firebird"), "Firebird" }, + { CreateDefaultBrowserRegex("Camino"), "Camino" }, + { CreateDefaultBrowserRegex("Netscape"), "Netscape" }, + { CreateDefaultBrowserRegex("OmniWeb"), "OmniWeb" }, + { CreateDefaultBrowserRegex("Safari"), "Safari" }, + { CreateDefaultBrowserRegex("Mozilla"), "Mozilla" }, + { CreateDefaultBrowserRegex("Konqueror"), "Konqueror" }, + { CreateDefaultBrowserRegex("icab"), "iCab" }, + { CreateDefaultBrowserRegex("Lynx"), "Lynx" }, + { CreateDefaultBrowserRegex("Links"), "Links" }, + { CreateDefaultBrowserRegex("hotjava"), "HotJava" }, + { CreateDefaultBrowserRegex("amaya"), "Amaya" }, + { CreateDefaultBrowserRegex("IBrowse"), "IBrowse" }, + { CreateDefaultBrowserRegex("Maxthon"), "Maxthon" }, + { CreateDefaultBrowserRegex("ipod touch"), "Apple iPod" }, + { CreateDefaultBrowserRegex("Ubuntu"), "Ubuntu Web Browser" }, }; internal static readonly Dictionary Mobiles = new() From 398e9229b05fb2a4abb760ddb590933e755e89d5 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 16:06:56 +0200 Subject: [PATCH 07/36] remove custom --- .gitignore | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.gitignore b/.gitignore index 70c5e46..96b845c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,11 +3,6 @@ ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore -## Custom -appsettings.ABT-PC.json - - - # User-specific files *.suo *.user From e2612ce658b2a611d5f073ef08de8a777a246ba7 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 16:20:18 +0200 Subject: [PATCH 08/36] add factories --- .../HttpUserAgentInformation.cs | 20 +++++++++++++++---- .../HttpUserAgentParser.cs | 6 +++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs index f09c39e..b3e88e0 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs @@ -10,17 +10,29 @@ public readonly struct HttpUserAgentInformation public string? Version { get; } public string? MobileDeviceType { get; } - - public HttpUserAgentInformation(string userAgent, HttpUserAgentType type, in HttpUserAgentPlatformInformation? platform, string? name, string? version, string? mobileDeviceType) + private HttpUserAgentInformation(string userAgent, HttpUserAgentPlatformInformation? platform, HttpUserAgentType type, string? name, string? version, string? deviceName) { UserAgent = userAgent; Type = type; - MobileDeviceType = mobileDeviceType; - Platform = platform; Name = name; + Platform = platform; Version = version; + MobileDeviceType = deviceName; } + // parse + public static HttpUserAgentInformation Parse(string userAgent) => HttpUserAgentParser.Parse(userAgent); + + // create factories + + public static HttpUserAgentInformation CreateForRobot(string userAgent, string robotName) + => new(userAgent, null, HttpUserAgentType.Robot, robotName, null, null); + + public static HttpUserAgentInformation CreateForBrowser(string userAgent, HttpUserAgentPlatformInformation? platform, string? browserName, string? browserVersion, string? deviceName) + => new(userAgent, platform, HttpUserAgentType.Browser, browserName, browserVersion, deviceName); + + public static HttpUserAgentInformation CreateForUnknown(string userAgent, HttpUserAgentPlatformInformation? platform, string? deviceName) + => new(userAgent, platform, HttpUserAgentType.Unknown, null, null, deviceName); } } diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs index 0cad8a8..7d639bd 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs @@ -14,7 +14,7 @@ public static HttpUserAgentInformation Parse(string userAgent) // analyze if (TryGetRobot(userAgent, out string? robotName)) { - return new HttpUserAgentInformation(userAgent, HttpUserAgentType.Robot, null, robotName, null, null); + return HttpUserAgentInformation.CreateForRobot(userAgent, robotName); } HttpUserAgentPlatformInformation? platform = GetPlatform(userAgent); @@ -22,10 +22,10 @@ public static HttpUserAgentInformation Parse(string userAgent) if (TryGetBrowser(userAgent, out (string Name, string? Version)? browser)) { - return new HttpUserAgentInformation(userAgent, HttpUserAgentType.Browser, platform, browser?.Name, browser?.Version, mobileDeviceType); + return HttpUserAgentInformation.CreateForBrowser(userAgent, platform, browser?.Name, browser?.Version, mobileDeviceType); } - return new HttpUserAgentInformation(userAgent, HttpUserAgentType.Unknown, platform, null, null, mobileDeviceType); + return HttpUserAgentInformation.CreateForUnknown(userAgent, platform, mobileDeviceType); } public static string Cleanup(string userAgent) => userAgent.Trim(); From 530468f04f82ce97cd2541186fe3aa41a45aee35 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 17:25:32 +0200 Subject: [PATCH 09/36] remove redirect --- .../Providers/CachedHttpUserAgentParserProvider.cs | 2 +- .../Providers/DefaultHttpUserAgentParserProvider.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MyCSharp.HttpUserAgentParser/Providers/CachedHttpUserAgentParserProvider.cs b/src/MyCSharp.HttpUserAgentParser/Providers/CachedHttpUserAgentParserProvider.cs index e4632e5..51ee278 100644 --- a/src/MyCSharp.HttpUserAgentParser/Providers/CachedHttpUserAgentParserProvider.cs +++ b/src/MyCSharp.HttpUserAgentParser/Providers/CachedHttpUserAgentParserProvider.cs @@ -7,6 +7,6 @@ public class CachedHttpUserAgentParserProvider : IHttpUserAgentParserProvider private readonly ConcurrentDictionary _cache = new(); public HttpUserAgentInformation Parse(string userAgent) - => _cache.GetOrAdd(userAgent, HttpUserAgentInformation.Parse(userAgent)); + => _cache.GetOrAdd(userAgent, HttpUserAgentParser.Parse(userAgent)); } } \ No newline at end of file diff --git a/src/MyCSharp.HttpUserAgentParser/Providers/DefaultHttpUserAgentParserProvider.cs b/src/MyCSharp.HttpUserAgentParser/Providers/DefaultHttpUserAgentParserProvider.cs index 8c82214..c682294 100644 --- a/src/MyCSharp.HttpUserAgentParser/Providers/DefaultHttpUserAgentParserProvider.cs +++ b/src/MyCSharp.HttpUserAgentParser/Providers/DefaultHttpUserAgentParserProvider.cs @@ -3,6 +3,6 @@ public class DefaultHttpUserAgentParserProvider : IHttpUserAgentParserProvider { public HttpUserAgentInformation Parse(string userAgent) - => HttpUserAgentInformation.Parse(userAgent); + => HttpUserAgentParser.Parse(userAgent); } } \ No newline at end of file From 7c5146a9d2e715f6076dab1a77c4e81543952d6b Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 17:26:18 +0200 Subject: [PATCH 10/36] fix naming --- ...erAgentParserMemoryCacheServiceCollectionExtensions.cs | 2 +- .../HttpUserAgentParserServiceCollectionExtensions.cs | 8 ++++---- ...erProvider.cs => HttpUserAgentParserCachedProvider.cs} | 2 +- ...rProvider.cs => HttpUserAgentParserDefaultProvider.cs} | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) rename src/MyCSharp.HttpUserAgentParser/Providers/{CachedHttpUserAgentParserProvider.cs => HttpUserAgentParserCachedProvider.cs} (85%) rename src/MyCSharp.HttpUserAgentParser/Providers/{DefaultHttpUserAgentParserProvider.cs => HttpUserAgentParserDefaultProvider.cs} (76%) diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs b/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs index a859c71..683ab7a 100644 --- a/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs @@ -8,7 +8,7 @@ namespace MyCSharp.HttpUserAgentParser.MemoryCache.DependencyInjection public static class HttpUserAgentParserMemoryCacheServiceCollectionExtensions { /// - /// Registers as singleton to + /// Registers as singleton to /// public static HttpUserAgentParserDependencyInjectionOptions AddMemoryCachedHttpUserAgentParser( this IServiceCollection services, Action? options = null) diff --git a/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserServiceCollectionExtensions.cs b/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserServiceCollectionExtensions.cs index 8b7fa0f..b98fdfe 100644 --- a/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserServiceCollectionExtensions.cs +++ b/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserServiceCollectionExtensions.cs @@ -6,21 +6,21 @@ namespace MyCSharp.HttpUserAgentParser.DependencyInjection public static class HttpUserAgentParserServiceCollectionExtensions { /// - /// Registers as singleton to + /// Registers as singleton to /// public static HttpUserAgentParserDependencyInjectionOptions AddHttpUserAgentParser( this IServiceCollection services) { - return AddHttpUserAgentParser(services); + return AddHttpUserAgentParser(services); } /// - /// Registers as singleton to + /// Registers as singleton to /// public static HttpUserAgentParserDependencyInjectionOptions AddCachedHttpUserAgentParser( this IServiceCollection services) { - return AddHttpUserAgentParser(services); + return AddHttpUserAgentParser(services); } /// diff --git a/src/MyCSharp.HttpUserAgentParser/Providers/CachedHttpUserAgentParserProvider.cs b/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs similarity index 85% rename from src/MyCSharp.HttpUserAgentParser/Providers/CachedHttpUserAgentParserProvider.cs rename to src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs index 51ee278..12317ef 100644 --- a/src/MyCSharp.HttpUserAgentParser/Providers/CachedHttpUserAgentParserProvider.cs +++ b/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs @@ -2,7 +2,7 @@ namespace MyCSharp.HttpUserAgentParser.Providers { - public class CachedHttpUserAgentParserProvider : IHttpUserAgentParserProvider + public class HttpUserAgentParserCachedProvider : IHttpUserAgentParserProvider { private readonly ConcurrentDictionary _cache = new(); diff --git a/src/MyCSharp.HttpUserAgentParser/Providers/DefaultHttpUserAgentParserProvider.cs b/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserDefaultProvider.cs similarity index 76% rename from src/MyCSharp.HttpUserAgentParser/Providers/DefaultHttpUserAgentParserProvider.cs rename to src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserDefaultProvider.cs index c682294..c1c9d43 100644 --- a/src/MyCSharp.HttpUserAgentParser/Providers/DefaultHttpUserAgentParserProvider.cs +++ b/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserDefaultProvider.cs @@ -1,6 +1,6 @@ namespace MyCSharp.HttpUserAgentParser.Providers { - public class DefaultHttpUserAgentParserProvider : IHttpUserAgentParserProvider + public class HttpUserAgentParserDefaultProvider : IHttpUserAgentParserProvider { public HttpUserAgentInformation Parse(string userAgent) => HttpUserAgentParser.Parse(userAgent); From 1079944772e41aebda6ccc9ad112bd16efe94fe7 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 17:28:27 +0200 Subject: [PATCH 11/36] add cache feedback --- ...HttpUserAgentParserMemoryCachedProvider.cs | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs index 218dc54..71e9078 100644 --- a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Caching.Memory; +using System; +using Microsoft.Extensions.Caching.Memory; using MyCSharp.HttpUserAgentParser.Providers; namespace MyCSharp.HttpUserAgentParser.MemoryCache @@ -14,17 +15,38 @@ public HttpUserAgentParserMemoryCachedProvider(IMemoryCache memoryCache, HttpUse _options = options; } - private static string Cleanup(string userAgent) - => userAgent.Trim(); - - public HttpUserAgentInformation Parse(string userAgent) { - return _memoryCache.GetOrCreate(Cleanup(userAgent), entry => + CacheKey key = GetKey(userAgent); + + return _memoryCache.GetOrCreate(key, static entry => { - entry.SlidingExpiration = _options.CacheEntryOptions.SlidingExpiration; - return HttpUserAgentInformation.Parse(userAgent); + CacheKey key = (entry.Key as CacheKey)!; + entry.SlidingExpiration = key.Options.CacheEntryOptions.SlidingExpiration; + entry.SetSize(1); + + return HttpUserAgentParser.Parse(key.UserAgent); }); } + + [ThreadStatic] + private static CacheKey? t_key; + + private CacheKey GetKey(string userAgent) + { + CacheKey key = t_key ??= new(); + + key.UserAgent = userAgent; + key.Options = _options; + + return key; + } + + private class CacheKey + { + public string UserAgent { get; set; } = null!; + + public HttpUserAgentParserMemoryCachedProviderOptions Options { get; set; } = null!; + } } } \ No newline at end of file From e9016f878f1a801af4a105bca1cf9bc9339fec78 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 17:41:09 +0200 Subject: [PATCH 12/36] add feedback --- .../Providers/HttpUserAgentParserCachedProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs b/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs index 12317ef..6ac2349 100644 --- a/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs +++ b/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs @@ -7,6 +7,6 @@ public class HttpUserAgentParserCachedProvider : IHttpUserAgentParserProvider private readonly ConcurrentDictionary _cache = new(); public HttpUserAgentInformation Parse(string userAgent) - => _cache.GetOrAdd(userAgent, HttpUserAgentParser.Parse(userAgent)); + => _cache.GetOrAdd(userAgent, HttpUserAgentParser.Parse); } } \ No newline at end of file From 98d68831f143fc35bddc4b6de1f8cce3b27c7a03 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 17:45:35 +0200 Subject: [PATCH 13/36] remove doubled ctor --- .../HttpUserAgentParserMemoryCachedProviderOptions.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs index 4297e84..4f6a329 100644 --- a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs @@ -12,13 +12,9 @@ public class HttpUserAgentParserMemoryCachedProviderOptions public MemoryCacheOptions CacheOptions { get; } public MemoryCacheEntryOptions CacheEntryOptions { get; } - public HttpUserAgentParserMemoryCachedProviderOptions() - : this(null, null) { } - public HttpUserAgentParserMemoryCachedProviderOptions(MemoryCacheOptions cacheOptions) : this(cacheOptions, null) { } - public HttpUserAgentParserMemoryCachedProviderOptions(MemoryCacheEntryOptions cacheEntryOptions) : this(null, cacheEntryOptions) { } From 50ec6862906c00ae965a88e8e97ab0b4f2d0bd53 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 17:46:37 +0200 Subject: [PATCH 14/36] add feedback --- .../Providers/HttpUserAgentParserCachedProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs b/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs index 6ac2349..954e5b4 100644 --- a/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs +++ b/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs @@ -7,6 +7,6 @@ public class HttpUserAgentParserCachedProvider : IHttpUserAgentParserProvider private readonly ConcurrentDictionary _cache = new(); public HttpUserAgentInformation Parse(string userAgent) - => _cache.GetOrAdd(userAgent, HttpUserAgentParser.Parse); + => _cache.GetOrAdd(userAgent, static ua => HttpUserAgentParser.Parse(ua)); } } \ No newline at end of file From 76cb8bd57c8bca3c39d141b9cb9c2fa01bfa2186 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 18:25:24 +0200 Subject: [PATCH 15/36] add docs --- README.md | 92 +++++++++++++++++-- .../HttpUserAgentParserAccessor.cs | 4 +- ...rMemoryCacheServiceCollectionExtensions.cs | 2 +- 3 files changed, 88 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 8bb39d9..9323bf9 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,107 @@ # MyCSharp.HttpUserAgentParser -## Usage +Parsing HTTP User Agents with .NET -text here +## NuGet -### Parse +| NuGet | +|-| +| [![MyCSharp.HttpUserAgentParser](https://img.shields.io/nuget/v/MyCSharp.HttpUserAgentParser.svg?logo=nuget&label=MyCSharp.HttpUserAgentParser)](https://www.nuget.org/packages/MyCSharp.HttpUserAgentParser) | +| [![MyCSharp.HttpUserAgentParser](https://img.shields.io/nuget/v/MyCSharp.HttpUserAgentParser.MemoryCache.svg?logo=nuget&label=MyCSharp.HttpUserAgentParser.MemoryCache)](https://www.nuget.org/packages/MyCSharp.HttpUserAgentParser.MemoryCache)| `dotnet add package MyCSharp.HttpUserAgentParser.MemoryCach.MemoryCache` | +| [![MyCSharp.HttpUserAgentParser.AspNetCore](https://img.shields.io/nuget/v/MyCSharp.HttpUserAgentParser.AspNetCore.svg?logo=nuget&label=MyCSharp.HttpUserAgentParser.AspNetCore)](https://www.nuget.org/packages/MyCSharp.HttpUserAgentParser.AspNetCore) | `dotnet add package MyCSharp.HttpUserAgentParser.AspNetCore` | -text here -### Dependency Injection Configuration +## Usage -text here +```csharp +string userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"; +HttpUserAgentInformation info = HttpUserAgentParser.Parse(userAgent); // alias HttpUserAgentInformation.Parse() +``` +returns +```csharp +UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36" +Type = HttpUserAgentType.Browser +Platform = { + Name = "Windows 10", + PlatformType = HttpUserAgentPlatformType.Windows +} +Name = "Chrome" +Version = "90.0.4430.212" +MobileDeviceType = null +``` ### Caching -text here +If no cache is required but dependency injection is still desired, the default cache provider can simply be used. This registers the dummy class `HttpUserAgentParserDefaultProvider`, which does not cache at all. + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + services.AddHttpUserAgentParser(); // uses HttpUserAgentParserDefaultProvider and does not cache +} +``` + +Likewise, an In Process Cache mechanism is provided, based on a `ConcurrentDictionary`. + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + services.AddHttpUserAgentCachedParser(); // uses `HttpUserAgentParserCachedProvider` + // or + // services.AddHttpUserAgentParser(); +} +``` + + This is especially recommended for tests. For web applications, the `IMemoryCache` implementation should be used, which offers a timed expiration of the entries. + +The package [MyCSharp.HttpUserAgentParser.MemoryCache](https://www.nuget.org/packages/MyCSharp.HttpUserAgentParser.MemoryCache) is required to use the IMemoryCache. This enables the registration of the `IMemoryCache` implementation: + + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + services.AddHttpUserAgentMemoryCachedParser(); + + // or use options + + services.AddHttpUserAgentMemoryCachedParser(options => + { + options.CacheEntryOptions.SlidingExpiration = TimeSpan.FromMinutes(60); // default is 1 day + + options.CacheOptions.SizeLimit = 1024; // default is 256 + }); +} +``` ### ASP.NET Core +For ASP.NET Core applications, an accessor pattern (`IHttpUserAgentParserAccessor`) implementation can be registered additionally that independently retrieves the user agent based on the `HttpContextAccessor`. This requires the package [MyCSharp.HttpUserAgentParser.AspNetCore](https://www.nuget.org/packages/MyCSharp.HttpUserAgentParser.AspNetCore) + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + services.AddHttpUserAgentParserAccessor(); // registers IHttpUserAgentParserAccessor +} +``` + +Now you can use + +```csharp +public void MyMethod(IHttpUserAgentParserAccessor parserAccessor) +{ + HttpUserAgentInformation info = parserAccessor.Get(); +} +``` ## Disclaimer This library is inspired by [UserAgentService by DannyBoyNg](https://github.com/DannyBoyNg/UserAgentService) and contains optimizations for our requirements on myCSharp.de. We decided to fork the project, because we want a general restructuring with corresponding breaking changes. +## Maintained + +by [@gfoidl](https://github.com/gfoidl) and [@BenjaminAbt](https://github.com/BenjaminAbt) + ## License MIT License diff --git a/src/MyCSharp.HttpUserAgentParser.AspNetCore/HttpUserAgentParserAccessor.cs b/src/MyCSharp.HttpUserAgentParser.AspNetCore/HttpUserAgentParserAccessor.cs index dde668a..60c9e09 100644 --- a/src/MyCSharp.HttpUserAgentParser.AspNetCore/HttpUserAgentParserAccessor.cs +++ b/src/MyCSharp.HttpUserAgentParser.AspNetCore/HttpUserAgentParserAccessor.cs @@ -6,7 +6,7 @@ namespace MyCSharp.HttpUserAgentParser.AspNetCore public interface IHttpUserAgentParserAccessor { string HttpContextUserAgent { get; } - HttpUserAgentInformation ParseFromHttpContext(); + HttpUserAgentInformation Get(); } public class HttpUserAgentParserAccessor : IHttpUserAgentParserAccessor @@ -23,7 +23,7 @@ public HttpUserAgentParserAccessor(IHttpContextAccessor httpContextAccessor, IHt public string HttpContextUserAgent => _httpContextAccessor?.HttpContext?.Request?.Headers["User-Agent"].ToString()!; - public HttpUserAgentInformation ParseFromHttpContext() + public HttpUserAgentInformation Get() => _httpUserAgentParser.Parse(HttpContextUserAgent); } } diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs b/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs index 683ab7a..fddefa6 100644 --- a/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs @@ -10,7 +10,7 @@ public static class HttpUserAgentParserMemoryCacheServiceCollectionExtensions /// /// Registers as singleton to /// - public static HttpUserAgentParserDependencyInjectionOptions AddMemoryCachedHttpUserAgentParser( + public static HttpUserAgentParserDependencyInjectionOptions AddHttpUserAgentMemoryCachedParser( this IServiceCollection services, Action? options = null) { HttpUserAgentParserMemoryCachedProviderOptions providerOptions = new(); From 6506c431023e5a6004d8f9b7cd6ddd7ed3d38b1a Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 18:28:11 +0200 Subject: [PATCH 16/36] add feedback --- .../MyCSharp.HttpUserAgentParser.AspNetCore.csproj | 7 ++----- .../MyCSharp.HttpUserAgentParser.MemoryCache.csproj | 3 ++- src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs | 4 ++++ .../MyCSharp.HttpUserAgentParser.csproj | 4 ++-- ...yCSharp.HttpUserAgentParser.AspNetCore.UnitTests.csproj | 3 +-- .../MyCSharp.HttpUserAgentParser.UnitTests.csproj | 2 +- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/MyCSharp.HttpUserAgentParser.AspNetCore/MyCSharp.HttpUserAgentParser.AspNetCore.csproj b/src/MyCSharp.HttpUserAgentParser.AspNetCore/MyCSharp.HttpUserAgentParser.AspNetCore.csproj index b72c8f2..4278fcb 100644 --- a/src/MyCSharp.HttpUserAgentParser.AspNetCore/MyCSharp.HttpUserAgentParser.AspNetCore.csproj +++ b/src/MyCSharp.HttpUserAgentParser.AspNetCore/MyCSharp.HttpUserAgentParser.AspNetCore.csproj @@ -2,19 +2,16 @@ net5.0 - 9.0 + preview enable - - - - + \ No newline at end of file diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/MyCSharp.HttpUserAgentParser.MemoryCache.csproj b/src/MyCSharp.HttpUserAgentParser.MemoryCache/MyCSharp.HttpUserAgentParser.MemoryCache.csproj index f4c82d2..8e8862d 100644 --- a/src/MyCSharp.HttpUserAgentParser.MemoryCache/MyCSharp.HttpUserAgentParser.MemoryCache.csproj +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/MyCSharp.HttpUserAgentParser.MemoryCache.csproj @@ -2,6 +2,7 @@ net5.0 + preview enable @@ -14,4 +15,4 @@ - + \ No newline at end of file diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs index 7d639bd..34e97d5 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs @@ -42,6 +42,7 @@ public static HttpUserAgentInformation Parse(string userAgent) return null; } + public static bool TryGetPlatform(string userAgent, [NotNullWhen(true)] out HttpUserAgentPlatformInformation? platform) { platform = GetPlatform(userAgent); @@ -61,6 +62,7 @@ public static (string Name, string? Version)? GetBrowser(string userAgent) return null; } + public static bool TryGetBrowser(string userAgent, [NotNullWhen(true)] out (string Name, string? Version)? browser) { browser = GetBrowser(userAgent); @@ -79,6 +81,7 @@ public static bool TryGetBrowser(string userAgent, [NotNullWhen(true)] out (stri return null; } + public static bool TryGetRobot(string userAgent, [NotNullWhen(true)] out string? robotName) { robotName = GetRobot(userAgent); @@ -97,6 +100,7 @@ public static bool TryGetRobot(string userAgent, [NotNullWhen(true)] out string? return null; } + public static bool TryGetMobileDevice(string userAgent, [NotNullWhen(true)] out string? device) { device = GetMobileDevice(userAgent); diff --git a/src/MyCSharp.HttpUserAgentParser/MyCSharp.HttpUserAgentParser.csproj b/src/MyCSharp.HttpUserAgentParser/MyCSharp.HttpUserAgentParser.csproj index 122e5f1..87da525 100644 --- a/src/MyCSharp.HttpUserAgentParser/MyCSharp.HttpUserAgentParser.csproj +++ b/src/MyCSharp.HttpUserAgentParser/MyCSharp.HttpUserAgentParser.csproj @@ -2,7 +2,7 @@ net5.0 - 9.0 + preview enable @@ -10,4 +10,4 @@ - + \ No newline at end of file diff --git a/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests.csproj b/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests.csproj index ef46261..e9394f0 100644 --- a/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests.csproj +++ b/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests.csproj @@ -19,5 +19,4 @@ - - + \ No newline at end of file diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/MyCSharp.HttpUserAgentParser.UnitTests.csproj b/tests/MyCSharp.HttpUserAgentParser.UnitTests/MyCSharp.HttpUserAgentParser.UnitTests.csproj index 553c457..449b240 100644 --- a/tests/MyCSharp.HttpUserAgentParser.UnitTests/MyCSharp.HttpUserAgentParser.UnitTests.csproj +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/MyCSharp.HttpUserAgentParser.UnitTests.csproj @@ -23,4 +23,4 @@ - + \ No newline at end of file From bfd72a43a37f23e7be2d779d51c8cf609c0926c4 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 18:30:36 +0200 Subject: [PATCH 17/36] add code cleanup --- ...tpUserAgentParserMemoryCacheServiceCollectionExtensions.cs | 4 ++-- .../HttpUserAgentParserMemoryCachedProvider.cs | 4 ++-- .../HttpUserAgentParserMemoryCachedProviderOptions.cs | 4 ++-- .../HttpUserAgentParserServiceCollectionExtensions.cs | 2 +- src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs b/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs index fddefa6..eaa4c83 100644 --- a/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs @@ -1,7 +1,7 @@ -using System; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using MyCSharp.HttpUserAgentParser.DependencyInjection; using MyCSharp.HttpUserAgentParser.Providers; +using System; namespace MyCSharp.HttpUserAgentParser.MemoryCache.DependencyInjection { diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs index 71e9078..844179e 100644 --- a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs @@ -1,6 +1,6 @@ -using System; -using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Memory; using MyCSharp.HttpUserAgentParser.Providers; +using System; namespace MyCSharp.HttpUserAgentParser.MemoryCache { diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs index 4f6a329..5c809c4 100644 --- a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs @@ -1,5 +1,5 @@ -using System; -using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Memory; +using System; namespace MyCSharp.HttpUserAgentParser.MemoryCache { diff --git a/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserServiceCollectionExtensions.cs b/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserServiceCollectionExtensions.cs index b98fdfe..e72059a 100644 --- a/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserServiceCollectionExtensions.cs +++ b/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserServiceCollectionExtensions.cs @@ -17,7 +17,7 @@ public static HttpUserAgentParserDependencyInjectionOptions AddHttpUserAgentPars /// /// Registers as singleton to /// - public static HttpUserAgentParserDependencyInjectionOptions AddCachedHttpUserAgentParser( + public static HttpUserAgentParserDependencyInjectionOptions AddHttpUserAgentCachedParser( this IServiceCollection services) { return AddHttpUserAgentParser(services); diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs index 34e97d5..9984440 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs @@ -100,7 +100,7 @@ public static bool TryGetRobot(string userAgent, [NotNullWhen(true)] out string? return null; } - + public static bool TryGetMobileDevice(string userAgent, [NotNullWhen(true)] out string? device) { device = GetMobileDevice(userAgent); From 8da5e864aeb6088822ad9331e531593ce0ac3799 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 18:37:38 +0200 Subject: [PATCH 18/36] add docs --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9323bf9..e7379c2 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,17 @@ Version = "90.0.4430.212" MobileDeviceType = null ``` -### Caching +### Dependency Injection and Caching + +For dependency injection mechanisms, the `IHttpUserAgentParserProvider` interface exists, for which built-in or custom caching mechanisms can be used. The use is always: + +```csharp +private IHttpUserAgentParserProvider _parser; +public void MyMethod(string userAgent) +{ + HttpUserAgentInformation info = _parser.Parse(userAgent); +} +``` If no cache is required but dependency injection is still desired, the default cache provider can simply be used. This registers the dummy class `HttpUserAgentParserDefaultProvider`, which does not cache at all. From b46a8c802bd54f45b67320629242acf52a63cde7 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 18:40:06 +0200 Subject: [PATCH 19/36] add docs --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e7379c2..f934982 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ public void MyMethod(string userAgent) } ``` -If no cache is required but dependency injection is still desired, the default cache provider can simply be used. This registers the dummy class `HttpUserAgentParserDefaultProvider`, which does not cache at all. +If no cache is required but dependency injection is still desired, the default cache provider can simply be used. This registers the `HttpUserAgentParserDefaultProvider`, which does not cache at all. ```csharp public void ConfigureServices(IServiceCollection services) @@ -105,12 +105,12 @@ public void MyMethod(IHttpUserAgentParserAccessor parserAccessor) ## Disclaimer -This library is inspired by [UserAgentService by DannyBoyNg](https://github.com/DannyBoyNg/UserAgentService) and contains optimizations for our requirements on myCSharp.de. +This library is inspired by [UserAgentService by DannyBoyNg](https://github.com/DannyBoyNg/UserAgentService) and contains optimizations for our requirements on [myCSharp.de](https://mycsharp.de). We decided to fork the project, because we want a general restructuring with corresponding breaking changes. ## Maintained -by [@gfoidl](https://github.com/gfoidl) and [@BenjaminAbt](https://github.com/BenjaminAbt) +by [@BenjaminAbt](https://github.com/BenjaminAbt) and [@gfoidl](https://github.com/gfoidl) ## License From fd79a9f699a452a7516c5833ad404cac717b2100 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 18:42:03 +0200 Subject: [PATCH 20/36] add feedback --- .../HttpUserAgentStatics.cs | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs index d283126..35beeca 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs @@ -180,50 +180,50 @@ public static class HttpUserAgentStatics { "cellphone", "Generic Mobile" }, }; - internal static readonly Dictionary Robots = new() + internal static readonly (string Key, string Value)[] Robots = { - { "googlebot", "Googlebot" }, - { "googleweblight", "Google Web Light" }, - { "PetalBot", "PetalBot" }, - { "DuplexWeb-Google", "DuplexWeb-Google" }, - { "Storebot-Google", "Storebot-Google" }, - { "msnbot", "MSNBot" }, - { "baiduspider", "Baiduspider" }, - { "Google Favicon", "Google Favicon" }, - { "Jobboerse", "Jobboerse" }, - { "bingbot", "BingBot" }, - { "BingPreview", "Bing Preview" }, - { "slurp", "Slurp" }, - { "yahoo", "Yahoo" }, - { "ask jeeves", "Ask Jeeves" }, - { "fastcrawler", "FastCrawler" }, - { "infoseek", "InfoSeek Robot 1.0" }, - { "lycos", "Lycos" }, - { "YandexBot", "YandexBot" }, - { "YandexImages", "YandexImages" }, - { "mediapartners-google", "Mediapartners Google" }, - { "apis-google", "APIs Google" }, - { "CRAZYWEBCRAWLER", "Crazy Webcrawler" }, - { "AdsBot-Google-Mobile", "AdsBot Google Mobile" }, - { "adsbot-google", "AdsBot Google" }, - { "feedfetcher-google", "FeedFetcher-Google" }, - { "google-read-aloud", "Google-Read-Aloud" }, - { "curious george", "Curious George" }, - { "ia_archiver", "Alexa Crawler" }, - { "MJ12bot", "Majestic" }, - { "Uptimebot", "Uptimebot" }, - { "CheckMarkNetwork", "CheckMark" }, - { "facebookexternalhit", "Facebook" }, - { "adscanner", "AdScanner" }, - { "AhrefsBot", "Ahrefs" }, - { "BLEXBot", "BLEXBot" }, - { "DotBot", "OpenSite" }, - { "Mail.RU_Bot", "Mail.ru" }, - { "MegaIndex", "MegaIndex" }, - { "SemrushBot", "SEMRush" }, - { "SEOkicks", "SEOkicks" }, - { "seoscanners.net", "SEO Scanners" }, - { "Sistrix", "Sistrix" } + ( "googlebot", "Googlebot" ), + ( "googleweblight", "Google Web Light" ), + ( "PetalBot", "PetalBot"), + ( "DuplexWeb-Google", "DuplexWeb-Google"), + ( "Storebot-Google", "Storebot-Google"), + ( "msnbot", "MSNBot"), + ( "baiduspider", "Baiduspider"), + ( "Google Favicon", "Google Favicon"), + ( "Jobboerse", "Jobboerse"), + ( "bingbot", "BingBot"), + ( "BingPreview", "Bing Preview"), + ( "slurp", "Slurp"), + ( "yahoo", "Yahoo"), + ( "ask jeeves", "Ask Jeeves"), + ( "fastcrawler", "FastCrawler"), + ( "infoseek", "InfoSeek Robot 1.0"), + ( "lycos", "Lycos"), + ( "YandexBot", "YandexBot"), + ( "YandexImages", "YandexImages"), + ( "mediapartners-google", "Mediapartners Google"), + ( "apis-google", "APIs Google"), + ( "CRAZYWEBCRAWLER", "Crazy Webcrawler"), + ( "AdsBot-Google-Mobile", "AdsBot Google Mobile"), + ( "adsbot-google", "AdsBot Google"), + ( "feedfetcher-google", "FeedFetcher-Google"), + ( "google-read-aloud", "Google-Read-Aloud"), + ( "curious george", "Curious George"), + ( "ia_archiver", "Alexa Crawler"), + ( "MJ12bot", "Majestic"), + ( "Uptimebot", "Uptimebot"), + ( "CheckMarkNetwork", "CheckMark"), + ( "facebookexternalhit", "Facebook"), + ( "adscanner", "AdScanner"), + ( "AhrefsBot", "Ahrefs"), + ( "BLEXBot", "BLEXBot"), + ( "DotBot", "OpenSite"), + ( "Mail.RU_Bot", "Mail.ru"), + ( "MegaIndex", "MegaIndex"), + ( "SemrushBot", "SEMRush"), + ( "SEOkicks", "SEOkicks"), + ( "seoscanners.net", "SEO Scanners"), + ( "Sistrix", "Sistrix" ) }; internal static readonly Dictionary Tools = new() From 32f99f45465b6f080fef33c5c93c89a7bd6e3279 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 18:53:51 +0200 Subject: [PATCH 21/36] add docs --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f934982..eb8dc7f 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,8 @@ public void ConfigureServices(IServiceCollection services) { options.CacheEntryOptions.SlidingExpiration = TimeSpan.FromMinutes(60); // default is 1 day + // limit the total entries in the MemoryCache + // each unique user agent string counts as one entry options.CacheOptions.SizeLimit = 1024; // default is 256 }); } From d5c5760fc6fd260db5f1fe5a792b2fa67b8b4398 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 18:59:18 +0200 Subject: [PATCH 22/36] add auto cleanup editconfig --- .editorconfig | 240 ++++++++++++++++++ ...serDependencyInjectionOptionsExtensions.cs | 4 +- .../HttpUserAgentParserAccessor.cs | 6 +- ...rMemoryCacheServiceCollectionExtensions.cs | 6 +- ...HttpUserAgentParserMemoryCachedProvider.cs | 6 +- ...rAgentParserMemoryCachedProviderOptions.cs | 4 +- ...erAgentParserDependencyInjectionOptions.cs | 4 +- ...rAgentParserServiceCollectionExtensions.cs | 4 +- .../HttpUserAgentInformation.cs | 4 +- .../HttpUserAgentInformationExtensions.cs | 4 +- .../HttpUserAgentParser.cs | 4 +- .../HttpUserAgentPlatformInformation.cs | 4 +- .../HttpUserAgentPlatformType.cs | 4 +- .../HttpUserAgentStatics.cs | 12 +- .../HttpUserAgentType.cs | 4 +- .../HttpUserAgentParserCachedProvider.cs | 4 +- .../HttpUserAgentParserDefaultProvider.cs | 4 +- .../Providers/IHttpUserAgentParserProvider.cs | 4 +- .../HttpUserAgentParserTests.cs | 4 +- 19 files changed, 301 insertions(+), 25 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..132451c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,240 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# all files +[*] + +charset = utf-8 + +# Indentation and spacing +indent_style = space +indent_size = 4 +tab_width = 4 + +# New line preferences +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +# C# files +[*.cs] + +file_header_template = Copyright © myCSharp 2020-2021, all rights reserved + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = true + +# this. and Me. preferences +dotnet_style_qualification_for_event = true:suggestion +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_method = true:suggestion +dotnet_style_qualification_for_property = true:suggestion + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent + +# Expression-level preferences +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_throw_expression = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion + +# Field preferences +dotnet_style_readonly_field = true:suggestion + +# Parameter preferences +dotnet_code_quality_unused_parameters = all:suggestion + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false:silent +csharp_style_var_for_built_in_types = false:silent +csharp_style_var_when_type_is_apparent = true:silent + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = true:silent +csharp_style_expression_bodied_methods = true:silent +csharp_style_expression_bodied_operators = true:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_switch_expression = true:suggestion + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_prefer_static_local_function = true:suggestion +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async + +# Code-block preferences +csharp_prefer_braces = false:silent +csharp_prefer_simple_using_statement = true:suggestion + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:silent + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.constant_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_should_be_pascal_case.symbols = constant +dotnet_naming_rule.constant_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.private_or_internal_static_field_should_be_static_field.severity = suggestion +dotnet_naming_rule.private_or_internal_static_field_should_be_static_field.symbols = private_or_internal_static_field +dotnet_naming_rule.private_or_internal_static_field_should_be_static_field.style = static_field + +dotnet_naming_rule.private_or_internal_field_should_be_instance_field.severity = suggestion +dotnet_naming_rule.private_or_internal_field_should_be_instance_field.symbols = private_or_internal_field +dotnet_naming_rule.private_or_internal_field_should_be_instance_field.style = instance_field + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field +dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected +dotnet_naming_symbols.private_or_internal_field.required_modifiers = + +dotnet_naming_symbols.private_or_internal_static_field.applicable_kinds = field +dotnet_naming_symbols.private_or_internal_static_field.applicable_accessibilities = internal, private, private_protected +dotnet_naming_symbols.private_or_internal_static_field.required_modifiers = static + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +dotnet_naming_symbols.constant.applicable_kinds = field +dotnet_naming_symbols.constant.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.constant.required_modifiers = const + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.static_field.required_prefix = s_ +dotnet_naming_style.static_field.required_suffix = +dotnet_naming_style.static_field.word_separator = +dotnet_naming_style.static_field.capitalization = camel_case + +dotnet_naming_style.instance_field.required_prefix = _ +dotnet_naming_style.instance_field.required_suffix = +dotnet_naming_style.instance_field.word_separator = +dotnet_naming_style.instance_field.capitalization = camel_case + +# RCS1096: Convert 'HasFlag' call to bitwise operation (or vice versa). +dotnet_diagnostic.RCS1096.severity = none diff --git a/src/MyCSharp.HttpUserAgentParser.AspNetCore/DependencyInjection/HttpUserAgentParserDependencyInjectionOptionsExtensions.cs b/src/MyCSharp.HttpUserAgentParser.AspNetCore/DependencyInjection/HttpUserAgentParserDependencyInjectionOptionsExtensions.cs index ceda188..7676eb8 100644 --- a/src/MyCSharp.HttpUserAgentParser.AspNetCore/DependencyInjection/HttpUserAgentParserDependencyInjectionOptionsExtensions.cs +++ b/src/MyCSharp.HttpUserAgentParser.AspNetCore/DependencyInjection/HttpUserAgentParserDependencyInjectionOptionsExtensions.cs @@ -1,4 +1,6 @@ -using Microsoft.Extensions.DependencyInjection; +// Copyright © myCSharp 2020-2021, all rights reserved + +using Microsoft.Extensions.DependencyInjection; using MyCSharp.HttpUserAgentParser.DependencyInjection; using MyCSharp.HttpUserAgentParser.Providers; diff --git a/src/MyCSharp.HttpUserAgentParser.AspNetCore/HttpUserAgentParserAccessor.cs b/src/MyCSharp.HttpUserAgentParser.AspNetCore/HttpUserAgentParserAccessor.cs index 60c9e09..d0f2a57 100644 --- a/src/MyCSharp.HttpUserAgentParser.AspNetCore/HttpUserAgentParserAccessor.cs +++ b/src/MyCSharp.HttpUserAgentParser.AspNetCore/HttpUserAgentParserAccessor.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Http; +// Copyright © myCSharp 2020-2021, all rights reserved + +using Microsoft.AspNetCore.Http; using MyCSharp.HttpUserAgentParser.Providers; namespace MyCSharp.HttpUserAgentParser.AspNetCore @@ -24,6 +26,6 @@ public HttpUserAgentParserAccessor(IHttpContextAccessor httpContextAccessor, IHt _httpContextAccessor?.HttpContext?.Request?.Headers["User-Agent"].ToString()!; public HttpUserAgentInformation Get() - => _httpUserAgentParser.Parse(HttpContextUserAgent); + => _httpUserAgentParser.Parse(this.HttpContextUserAgent); } } diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs b/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs index eaa4c83..21b7890 100644 --- a/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensions.cs @@ -1,7 +1,9 @@ -using Microsoft.Extensions.DependencyInjection; +// Copyright © myCSharp 2020-2021, all rights reserved + +using System; +using Microsoft.Extensions.DependencyInjection; using MyCSharp.HttpUserAgentParser.DependencyInjection; using MyCSharp.HttpUserAgentParser.Providers; -using System; namespace MyCSharp.HttpUserAgentParser.MemoryCache.DependencyInjection { diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs index 844179e..46877be 100644 --- a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs @@ -1,6 +1,8 @@ -using Microsoft.Extensions.Caching.Memory; -using MyCSharp.HttpUserAgentParser.Providers; +// Copyright © myCSharp 2020-2021, all rights reserved + using System; +using Microsoft.Extensions.Caching.Memory; +using MyCSharp.HttpUserAgentParser.Providers; namespace MyCSharp.HttpUserAgentParser.MemoryCache { diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs index 5c809c4..2be1c40 100644 --- a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs @@ -1,5 +1,7 @@ -using Microsoft.Extensions.Caching.Memory; +// Copyright © myCSharp 2020-2021, all rights reserved + using System; +using Microsoft.Extensions.Caching.Memory; namespace MyCSharp.HttpUserAgentParser.MemoryCache { diff --git a/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserDependencyInjectionOptions.cs b/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserDependencyInjectionOptions.cs index 322b9f3..c2d6715 100644 --- a/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserDependencyInjectionOptions.cs +++ b/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserDependencyInjectionOptions.cs @@ -1,4 +1,6 @@ -using Microsoft.Extensions.DependencyInjection; +// Copyright © myCSharp 2020-2021, all rights reserved + +using Microsoft.Extensions.DependencyInjection; namespace MyCSharp.HttpUserAgentParser.DependencyInjection { diff --git a/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserServiceCollectionExtensions.cs b/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserServiceCollectionExtensions.cs index e72059a..6406501 100644 --- a/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserServiceCollectionExtensions.cs +++ b/src/MyCSharp.HttpUserAgentParser/DependencyInjection/HttpUserAgentParserServiceCollectionExtensions.cs @@ -1,4 +1,6 @@ -using Microsoft.Extensions.DependencyInjection; +// Copyright © myCSharp 2020-2021, all rights reserved + +using Microsoft.Extensions.DependencyInjection; using MyCSharp.HttpUserAgentParser.Providers; namespace MyCSharp.HttpUserAgentParser.DependencyInjection diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs index b3e88e0..fe8533b 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs @@ -1,4 +1,6 @@ -namespace MyCSharp.HttpUserAgentParser +// Copyright © myCSharp 2020-2021, all rights reserved + +namespace MyCSharp.HttpUserAgentParser { public readonly struct HttpUserAgentInformation { diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformationExtensions.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformationExtensions.cs index cb2b4d2..4e3f083 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformationExtensions.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformationExtensions.cs @@ -1,4 +1,6 @@ -namespace MyCSharp.HttpUserAgentParser +// Copyright © myCSharp 2020-2021, all rights reserved + +namespace MyCSharp.HttpUserAgentParser { public static class HttpUserAgentInformationExtensions { diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs index 9984440..dfb33aa 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentParser.cs @@ -1,4 +1,6 @@ -using System; +// Copyright © myCSharp 2020-2021, all rights reserved + +using System; using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformInformation.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformInformation.cs index 1f1bb03..85be234 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformInformation.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformInformation.cs @@ -1,4 +1,6 @@ -using System.Text.RegularExpressions; +// Copyright © myCSharp 2020-2021, all rights reserved + +using System.Text.RegularExpressions; namespace MyCSharp.HttpUserAgentParser { diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformType.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformType.cs index b270917..b491651 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformType.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentPlatformType.cs @@ -1,4 +1,6 @@ -namespace MyCSharp.HttpUserAgentParser +// Copyright © myCSharp 2020-2021, all rights reserved + +namespace MyCSharp.HttpUserAgentParser { public enum HttpUserAgentPlatformType : byte { diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs index 35beeca..cda789c 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentStatics.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +// Copyright © myCSharp 2020-2021, all rights reserved + +using System.Collections.Generic; using System.Text.RegularExpressions; namespace MyCSharp.HttpUserAgentParser @@ -56,7 +58,7 @@ public static class HttpUserAgentStatics private const RegexOptions DefaultBrowserRegexFlags = RegexOptions.IgnoreCase | RegexOptions.Compiled; private static Regex CreateDefaultBrowserRegex(string key) => new($@"{key}.*?([0-9\.]+)", DefaultBrowserRegexFlags); - internal static Dictionary Browsers = new() + public static Dictionary Browsers = new() { { CreateDefaultBrowserRegex("OPR"), "Opera" }, { CreateDefaultBrowserRegex("Flock"), "Flock" }, @@ -95,7 +97,7 @@ public static class HttpUserAgentStatics { CreateDefaultBrowserRegex("Ubuntu"), "Ubuntu Web Browser" }, }; - internal static readonly Dictionary Mobiles = new() + public static readonly Dictionary Mobiles = new() { // Legacy { "mobileexplorer", "Mobile Explorer" }, @@ -180,7 +182,7 @@ public static class HttpUserAgentStatics { "cellphone", "Generic Mobile" }, }; - internal static readonly (string Key, string Value)[] Robots = + public static readonly (string Key, string Value)[] Robots = { ( "googlebot", "Googlebot" ), ( "googleweblight", "Google Web Light" ), @@ -226,7 +228,7 @@ internal static readonly (string Key, string Value)[] Robots = ( "Sistrix", "Sistrix" ) }; - internal static readonly Dictionary Tools = new() + public static readonly Dictionary Tools = new() { { "curl", "curl" } }; diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentType.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentType.cs index 1b9f286..e75c7a4 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentType.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentType.cs @@ -1,4 +1,6 @@ -namespace MyCSharp.HttpUserAgentParser +// Copyright © myCSharp 2020-2021, all rights reserved + +namespace MyCSharp.HttpUserAgentParser { public enum HttpUserAgentType : byte { diff --git a/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs b/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs index 954e5b4..2da21c9 100644 --- a/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs +++ b/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs @@ -1,4 +1,6 @@ -using System.Collections.Concurrent; +// Copyright © myCSharp 2020-2021, all rights reserved + +using System.Collections.Concurrent; namespace MyCSharp.HttpUserAgentParser.Providers { diff --git a/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserDefaultProvider.cs b/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserDefaultProvider.cs index c1c9d43..9cdb2db 100644 --- a/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserDefaultProvider.cs +++ b/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserDefaultProvider.cs @@ -1,4 +1,6 @@ -namespace MyCSharp.HttpUserAgentParser.Providers +// Copyright © myCSharp 2020-2021, all rights reserved + +namespace MyCSharp.HttpUserAgentParser.Providers { public class HttpUserAgentParserDefaultProvider : IHttpUserAgentParserProvider { diff --git a/src/MyCSharp.HttpUserAgentParser/Providers/IHttpUserAgentParserProvider.cs b/src/MyCSharp.HttpUserAgentParser/Providers/IHttpUserAgentParserProvider.cs index 44f1c67..f0eb9a9 100644 --- a/src/MyCSharp.HttpUserAgentParser/Providers/IHttpUserAgentParserProvider.cs +++ b/src/MyCSharp.HttpUserAgentParser/Providers/IHttpUserAgentParserProvider.cs @@ -1,4 +1,6 @@ -namespace MyCSharp.HttpUserAgentParser.Providers +// Copyright © myCSharp 2020-2021, all rights reserved + +namespace MyCSharp.HttpUserAgentParser.Providers { public interface IHttpUserAgentParserProvider { diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs index 7671d13..517cd5d 100644 --- a/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs @@ -1,4 +1,6 @@ -using FluentAssertions; +// Copyright © myCSharp 2020-2021, all rights reserved + +using FluentAssertions; using Xunit; namespace MyCSharp.HttpUserAgentParser.UnitTests From 7401c0e2b2e044ae8cb9865acc92fd465b0b0c54 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 19:01:38 +0200 Subject: [PATCH 23/36] add feedback --- .../HttpUserAgentParserAccessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MyCSharp.HttpUserAgentParser.AspNetCore/HttpUserAgentParserAccessor.cs b/src/MyCSharp.HttpUserAgentParser.AspNetCore/HttpUserAgentParserAccessor.cs index d0f2a57..261a924 100644 --- a/src/MyCSharp.HttpUserAgentParser.AspNetCore/HttpUserAgentParserAccessor.cs +++ b/src/MyCSharp.HttpUserAgentParser.AspNetCore/HttpUserAgentParserAccessor.cs @@ -23,7 +23,7 @@ public HttpUserAgentParserAccessor(IHttpContextAccessor httpContextAccessor, IHt } public string HttpContextUserAgent => - _httpContextAccessor?.HttpContext?.Request?.Headers["User-Agent"].ToString()!; + _httpContextAccessor.HttpContext?.Request?.Headers["User-Agent"].ToString()!; public HttpUserAgentInformation Get() => _httpUserAgentParser.Parse(this.HttpContextUserAgent); From ed4f6beb2005c3c14ccd94c2b6f0c53c015ee2ac Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 19:38:47 +0200 Subject: [PATCH 24/36] add tests --- .../HttpUserAgentParserCachedProvider.cs | 7 ++- ...erAgentParserDependencyInjectionOptions.cs | 23 ++++++++ ...tParserServiceCollectionExtensionsTests.cs | 57 ++++++++++++++++++ ...Sharp.HttpUserAgentParser.UnitTests.csproj | 2 + .../HttpUserAgentParserCachedProviderTests.cs | 58 +++++++++++++++++++ 5 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 tests/MyCSharp.HttpUserAgentParser.UnitTests/DependencyInjection/HttpUserAgentParserDependencyInjectionOptions.cs create mode 100644 tests/MyCSharp.HttpUserAgentParser.UnitTests/DependencyInjection/HttpUserAgentParserServiceCollectionExtensionsTests.cs create mode 100644 tests/MyCSharp.HttpUserAgentParser.UnitTests/Providers/HttpUserAgentParserCachedProviderTests.cs diff --git a/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs b/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs index 2da21c9..eaa86cd 100644 --- a/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs +++ b/src/MyCSharp.HttpUserAgentParser/Providers/HttpUserAgentParserCachedProvider.cs @@ -1,4 +1,4 @@ -// Copyright © myCSharp 2020-2021, all rights reserved +// Copyright © myCSharp 2020-2021, all rights reserved using System.Collections.Concurrent; @@ -10,5 +10,8 @@ public class HttpUserAgentParserCachedProvider : IHttpUserAgentParserProvider public HttpUserAgentInformation Parse(string userAgent) => _cache.GetOrAdd(userAgent, static ua => HttpUserAgentParser.Parse(ua)); + + public int CacheEntryCount => _cache.Count; + public bool HasCacheEntry(string userAgent) => _cache.ContainsKey(userAgent); } -} \ No newline at end of file +} diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/DependencyInjection/HttpUserAgentParserDependencyInjectionOptions.cs b/tests/MyCSharp.HttpUserAgentParser.UnitTests/DependencyInjection/HttpUserAgentParserDependencyInjectionOptions.cs new file mode 100644 index 0000000..81437a2 --- /dev/null +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/DependencyInjection/HttpUserAgentParserDependencyInjectionOptions.cs @@ -0,0 +1,23 @@ +// Copyright © myCSharp 2020-2021, all rights reserved + +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using MyCSharp.HttpUserAgentParser.DependencyInjection; +using Xunit; + +namespace MyCSharp.HttpUserAgentParser.UnitTests.DependencyInjection +{ + public class UserAgentParserDependencyInjectionOptionsTests + { + [Fact] + public void Ctor_Should_Set_Property() + { + Mock scMock = new(); + + HttpUserAgentParserDependencyInjectionOptions options = new(scMock.Object); + + options.Services.Should().BeEquivalentTo(scMock.Object); + } + } +} diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/DependencyInjection/HttpUserAgentParserServiceCollectionExtensionsTests.cs b/tests/MyCSharp.HttpUserAgentParser.UnitTests/DependencyInjection/HttpUserAgentParserServiceCollectionExtensionsTests.cs new file mode 100644 index 0000000..02e1d2b --- /dev/null +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/DependencyInjection/HttpUserAgentParserServiceCollectionExtensionsTests.cs @@ -0,0 +1,57 @@ +// Copyright © myCSharp 2020-2021, all rights reserved + +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using MyCSharp.HttpUserAgentParser.DependencyInjection; +using MyCSharp.HttpUserAgentParser.Providers; +using Xunit; + +namespace MyCSharp.HttpUserAgentParser.UnitTests.DependencyInjection +{ + public class HttpUserAgentParserServiceCollectionExtensionsTests + { + public class TestHttpUserAgentParserProvider : IHttpUserAgentParserProvider + { + public HttpUserAgentInformation Parse(string userAgent) => throw new System.NotImplementedException(); + } + + [Fact] + public void AddHttpUserAgentParser() + { + ServiceCollection services = new(); + + services.AddHttpUserAgentParser(); + + services.Count.Should().Be(1); + services[0].ServiceType.Should().Be(); + services[0].ImplementationType.Should().Be(); + services[0].Lifetime.Should().Be(ServiceLifetime.Singleton); + } + + [Fact] + public void AddHttpUserAgentCachedParser() + { + ServiceCollection services = new(); + + services.AddHttpUserAgentCachedParser(); + + services.Count.Should().Be(1); + services[0].ServiceType.Should().Be(); + services[0].ImplementationType.Should().Be(); + services[0].Lifetime.Should().Be(ServiceLifetime.Singleton); + } + + [Fact] + public void AddHttpUserAgentParser_With_Generic() + { + ServiceCollection services = new(); + + services.AddHttpUserAgentParser(); + + services.Count.Should().Be(1); + services[0].ServiceType.Should().Be(); + services[0].ImplementationType.Should().Be(); + services[0].Lifetime.Should().Be(ServiceLifetime.Singleton); + } + } +} diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/MyCSharp.HttpUserAgentParser.UnitTests.csproj b/tests/MyCSharp.HttpUserAgentParser.UnitTests/MyCSharp.HttpUserAgentParser.UnitTests.csproj index 449b240..4753354 100644 --- a/tests/MyCSharp.HttpUserAgentParser.UnitTests/MyCSharp.HttpUserAgentParser.UnitTests.csproj +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/MyCSharp.HttpUserAgentParser.UnitTests.csproj @@ -7,7 +7,9 @@ + + all diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/Providers/HttpUserAgentParserCachedProviderTests.cs b/tests/MyCSharp.HttpUserAgentParser.UnitTests/Providers/HttpUserAgentParserCachedProviderTests.cs new file mode 100644 index 0000000..4add80b --- /dev/null +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/Providers/HttpUserAgentParserCachedProviderTests.cs @@ -0,0 +1,58 @@ +// Copyright © myCSharp 2020-2021, all rights reserved + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FluentAssertions; +using MyCSharp.HttpUserAgentParser.Providers; +using Xunit; + +namespace MyCSharp.HttpUserAgentParser.UnitTests.Providers +{ + public class HttpUserAgentParserCachedProviderTests + { + [Fact] + public void Parse() + { + HttpUserAgentParserCachedProvider provider = new HttpUserAgentParserCachedProvider(); + + provider.CacheEntryCount.Should().Be(0); + + // create first + string userAgentOne = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62"; + + HttpUserAgentInformation infoOne = provider.Parse(userAgentOne); + + infoOne.Name.Should().Be("Edge"); + infoOne.Version.Should().Be("90.0.818.62"); + + provider.CacheEntryCount.Should().Be(1); + provider.HasCacheEntry(userAgentOne).Should().Be(true); + + // check duplicate + + HttpUserAgentInformation infoDuplicate = provider.Parse(userAgentOne); + + infoDuplicate.Name.Should().Be("Edge"); + infoDuplicate.Version.Should().Be("90.0.818.62"); + + provider.CacheEntryCount.Should().Be(1); + provider.HasCacheEntry(userAgentOne).Should().Be(true); + + // create second + + string userAgentTwo = "Mozilla/5.0 (Android 4.4; Tablet; rv:41.0) Gecko/41.0 Firefox/41.0"; + + HttpUserAgentInformation infoTwo = provider.Parse(userAgentTwo); + + infoTwo.Name.Should().Be("Firefox"); + infoTwo.Version.Should().Be("41.0"); + + provider.CacheEntryCount.Should().Be(2); + provider.HasCacheEntry(userAgentTwo).Should().Be(true); + } + } +} From b268088d2d767c5b93564598cc386325f416dbd7 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 20:19:44 +0200 Subject: [PATCH 25/36] add tests --- .../HttpUserAgentInformation.cs | 2 +- ...HttpUserAgentInformationExtensionsTests.cs | 40 ++++++++++ .../HttpUserAgentInformationTests.cs | 73 +++++++++++++++++++ .../HttpUserAgentParserCachedProviderTests.cs | 5 -- ...HttpUserAgentParserDefaultProviderTests.cs | 23 ++++++ 5 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentInformationExtensionsTests.cs create mode 100644 tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentInformationTests.cs create mode 100644 tests/MyCSharp.HttpUserAgentParser.UnitTests/Providers/HttpUserAgentParserDefaultProviderTests.cs diff --git a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs index fe8533b..cfb1c3e 100644 --- a/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs +++ b/src/MyCSharp.HttpUserAgentParser/HttpUserAgentInformation.cs @@ -1,4 +1,4 @@ -// Copyright © myCSharp 2020-2021, all rights reserved +// Copyright © myCSharp 2020-2021, all rights reserved namespace MyCSharp.HttpUserAgentParser { diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentInformationExtensionsTests.cs b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentInformationExtensionsTests.cs new file mode 100644 index 0000000..0646ffb --- /dev/null +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentInformationExtensionsTests.cs @@ -0,0 +1,40 @@ +// Copyright © myCSharp 2020-2021, all rights reserved + +using FluentAssertions; +using Xunit; + +namespace MyCSharp.HttpUserAgentParser.UnitTests +{ + public class HttpUserAgentInformationExtensionsTests + { + [Theory] + [InlineData("Mozilla/5.0 (Linux; Android 10; HD1913) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.210 Mobile Safari/537.36 EdgA/46.3.4.5155", HttpUserAgentType.Browser, true)] + [InlineData("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62", HttpUserAgentType.Browser, false)] + [InlineData("Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML,like Gecko) Version/9.0 Mobile/13B143 Safari/601.1 (compatible; AdsBot-Google-Mobile; +http://www.google.com/mobile/adsbot.html)", HttpUserAgentType.Robot, false)] + [InlineData("APIs-Google (+https://developers.google.com/webmasters/APIs-Google.html)", HttpUserAgentType.Robot, false)] + [InlineData("Invalid user agent", HttpUserAgentType.Unknown, false)] + public void IsType(string userAgent, HttpUserAgentType expectedType, bool isMobile) + { + HttpUserAgentInformation info = HttpUserAgentInformation.Parse(userAgent); + info.IsType(expectedType).Should().Be(true); + + if (expectedType == HttpUserAgentType.Browser) + { + info.IsBrowser().Should().Be(true); + info.IsRobot().Should().Be(false); + } + else if (expectedType == HttpUserAgentType.Robot) + { + info.IsBrowser().Should().Be(false); + info.IsRobot().Should().Be(true); + } + else if (expectedType == HttpUserAgentType.Unknown) + { + info.IsBrowser().Should().Be(false); + info.IsRobot().Should().Be(false); + } + + info.IsMobile().Should().Be(isMobile); + } + } +} diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentInformationTests.cs b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentInformationTests.cs new file mode 100644 index 0000000..6034440 --- /dev/null +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentInformationTests.cs @@ -0,0 +1,73 @@ +// Copyright © myCSharp 2020-2021, all rights reserved + +using System.Text.RegularExpressions; +using FluentAssertions; +using Xunit; +using Xunit.Sdk; + +namespace MyCSharp.HttpUserAgentParser.UnitTests +{ + public class HttpUserAgentInformationTests + { + [Theory] + [InlineData("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62")] + public void Parse(string userAgent) + { + HttpUserAgentInformation ua1 = HttpUserAgentParser.Parse(userAgent); + HttpUserAgentInformation ua2 = HttpUserAgentInformation.Parse(userAgent); + + ua1.Should().BeEquivalentTo(ua2); + } + + [Theory] + [InlineData("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62")] + public void CreateForRobot(string userAgent) + { + + HttpUserAgentInformation ua = HttpUserAgentInformation.CreateForRobot(userAgent, "Chrome"); + + ua.UserAgent.Should().Be(userAgent); + ua.Type.Should().Be(HttpUserAgentType.Robot); + ua.Platform.Should().Be(null); + ua.Name.Should().Be("Chrome"); + ua.Version.Should().Be(null); + ua.MobileDeviceType.Should().Be(null); + } + + [Theory] + [InlineData("Mozilla/5.0 (Linux; Android 10; HD1913) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.210 Mobile Safari/537.36 EdgA/46.3.4.5155")] + public void CreateForBrowser(string userAgent) + { + HttpUserAgentPlatformInformation platformInformation = + new HttpUserAgentPlatformInformation(new Regex(""), "Android", HttpUserAgentPlatformType.Android); + + HttpUserAgentInformation ua = HttpUserAgentInformation.CreateForBrowser(userAgent, + platformInformation, "Edge", "46.3.4.5155", "Android"); + + ua.UserAgent.Should().Be(userAgent); + ua.Type.Should().Be(HttpUserAgentType.Browser); + ua.Platform.Should().Be(platformInformation); + ua.Name.Should().Be("Edge"); + ua.Version.Should().Be("46.3.4.5155"); + ua.MobileDeviceType.Should().Be("Android"); + } + + [Theory] + [InlineData("Invalid user agent")] + public void CreateForUnknown(string userAgent) + { + HttpUserAgentPlatformInformation platformInformation = + new(new Regex(""), "Batman", HttpUserAgentPlatformType.Linux); + + HttpUserAgentInformation ua = + HttpUserAgentInformation.CreateForUnknown(userAgent, platformInformation, null); + + ua.UserAgent.Should().Be(userAgent); + ua.Type.Should().Be(HttpUserAgentType.Unknown); + ua.Platform.Should().Be(platformInformation); + ua.Name.Should().Be(null); + ua.Version.Should().Be(null); + ua.MobileDeviceType.Should().Be(null); + } + } +} diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/Providers/HttpUserAgentParserCachedProviderTests.cs b/tests/MyCSharp.HttpUserAgentParser.UnitTests/Providers/HttpUserAgentParserCachedProviderTests.cs index 4add80b..43de1ec 100644 --- a/tests/MyCSharp.HttpUserAgentParser.UnitTests/Providers/HttpUserAgentParserCachedProviderTests.cs +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/Providers/HttpUserAgentParserCachedProviderTests.cs @@ -1,10 +1,5 @@ // Copyright © myCSharp 2020-2021, all rights reserved -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using FluentAssertions; using MyCSharp.HttpUserAgentParser.Providers; using Xunit; diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/Providers/HttpUserAgentParserDefaultProviderTests.cs b/tests/MyCSharp.HttpUserAgentParser.UnitTests/Providers/HttpUserAgentParserDefaultProviderTests.cs new file mode 100644 index 0000000..98b5a61 --- /dev/null +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/Providers/HttpUserAgentParserDefaultProviderTests.cs @@ -0,0 +1,23 @@ +// Copyright © myCSharp 2020-2021, all rights reserved + +using FluentAssertions; +using MyCSharp.HttpUserAgentParser.Providers; +using Xunit; + +namespace MyCSharp.HttpUserAgentParser.UnitTests.Providers +{ + public class HttpUserAgentParserDefaultProviderTests + { + [Theory] + [InlineData("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62")] + public void Parse(string userAgent) + { + HttpUserAgentParserDefaultProvider provider = new(); + + HttpUserAgentInformation providerUserAgentInfo = provider.Parse(userAgent); + HttpUserAgentInformation userAgentInfo = HttpUserAgentInformation.Parse(userAgent); + + providerUserAgentInfo.Should().BeEquivalentTo(userAgentInfo); + } + } +} From 59a9a5c62215825d3da5fedcbeb449750942c1d3 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 20:21:22 +0200 Subject: [PATCH 26/36] add tests --- .../HttpUserAgentInformationExtensionsTests.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentInformationExtensionsTests.cs b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentInformationExtensionsTests.cs index 0646ffb..5d50374 100644 --- a/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentInformationExtensionsTests.cs +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentInformationExtensionsTests.cs @@ -16,20 +16,31 @@ public class HttpUserAgentInformationExtensionsTests public void IsType(string userAgent, HttpUserAgentType expectedType, bool isMobile) { HttpUserAgentInformation info = HttpUserAgentInformation.Parse(userAgent); - info.IsType(expectedType).Should().Be(true); if (expectedType == HttpUserAgentType.Browser) { + info.IsType(HttpUserAgentType.Browser).Should().Be(true); + info.IsType(HttpUserAgentType.Robot).Should().Be(false); + info.IsType(HttpUserAgentType.Unknown).Should().Be(false); + info.IsBrowser().Should().Be(true); info.IsRobot().Should().Be(false); } else if (expectedType == HttpUserAgentType.Robot) { + info.IsType(HttpUserAgentType.Browser).Should().Be(false); + info.IsType(HttpUserAgentType.Robot).Should().Be(true); + info.IsType(HttpUserAgentType.Unknown).Should().Be(false); + info.IsBrowser().Should().Be(false); info.IsRobot().Should().Be(true); } else if (expectedType == HttpUserAgentType.Unknown) { + info.IsType(HttpUserAgentType.Browser).Should().Be(false); + info.IsType(HttpUserAgentType.Robot).Should().Be(false); + info.IsType(HttpUserAgentType.Unknown).Should().Be(true); + info.IsBrowser().Should().Be(false); info.IsRobot().Should().Be(false); } From c412b2d881de8988e7573e5936a05d09ff6cacd6 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 21:09:08 +0200 Subject: [PATCH 27/36] add tests --- MyCSharp.HttpUserAgentParser.sln | 7 +++ ...tParserServiceCollectionExtensionsTests.cs | 28 ++++++++++ .../HttpUserAgentParserAccessorTests.cs | 37 +++++++++++++ ...serAgentParser.AspNetCore.UnitTests.csproj | 6 +++ ...yCacheServiceCollectionExtensionssTests.cs | 30 +++++++++++ ...tParserMemoryCachedProviderOptionsTests.cs | 54 +++++++++++++++++++ ...serAgentParserMemoryCachedProviderTests.cs | 45 ++++++++++++++++ ...erAgentParser.MemoryCache.UnitTests.csproj | 29 ++++++++++ ...tParserServiceCollectionExtensionsTests.cs | 2 +- .../HttpUserAgentInformationTests.cs | 1 - .../HttpUserAgentPlatformInformationTests.cs | 25 +++++++++ .../HttpUserAgentPlatformTypeTests.cs | 26 +++++++++ .../HttpUserAgentTypeTests.cs | 19 +++++++ 13 files changed, 307 insertions(+), 2 deletions(-) create mode 100644 tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/DependencyInjection/HttpUserAgentParserServiceCollectionExtensionsTests.cs create mode 100644 tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/HttpUserAgentParserAccessorTests.cs create mode 100644 tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensionssTests.cs create mode 100644 tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/HttpUserAgentParserMemoryCachedProviderOptionsTests.cs create mode 100644 tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/HttpUserAgentParserMemoryCachedProviderTests.cs create mode 100644 tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests.csproj create mode 100644 tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentPlatformInformationTests.cs create mode 100644 tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentPlatformTypeTests.cs create mode 100644 tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentTypeTests.cs diff --git a/MyCSharp.HttpUserAgentParser.sln b/MyCSharp.HttpUserAgentParser.sln index dedeea8..5e9f663 100644 --- a/MyCSharp.HttpUserAgentParser.sln +++ b/MyCSharp.HttpUserAgentParser.sln @@ -17,6 +17,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F54C9296 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyCSharp.HttpUserAgentParser.MemoryCache", "src\MyCSharp.HttpUserAgentParser.MemoryCache\MyCSharp.HttpUserAgentParser.MemoryCache.csproj", "{3C8CCD44-F47C-4624-8997-54C42F02E376}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests", "tests\MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests\MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests.csproj", "{39FC1EC2-2AD3-411F-A545-AB6CCB94FB7E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -43,6 +45,10 @@ Global {3C8CCD44-F47C-4624-8997-54C42F02E376}.Debug|Any CPU.Build.0 = Debug|Any CPU {3C8CCD44-F47C-4624-8997-54C42F02E376}.Release|Any CPU.ActiveCfg = Release|Any CPU {3C8CCD44-F47C-4624-8997-54C42F02E376}.Release|Any CPU.Build.0 = Release|Any CPU + {39FC1EC2-2AD3-411F-A545-AB6CCB94FB7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39FC1EC2-2AD3-411F-A545-AB6CCB94FB7E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39FC1EC2-2AD3-411F-A545-AB6CCB94FB7E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39FC1EC2-2AD3-411F-A545-AB6CCB94FB7E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -53,6 +59,7 @@ Global {F16697F7-74B4-441D-A0C0-1A0572AC3AB0} = {F54C9296-4EF7-40F0-9F20-F23A2270ABC9} {75960783-8BF9-479C-9ECF-E9653B74C9A2} = {F54C9296-4EF7-40F0-9F20-F23A2270ABC9} {3C8CCD44-F47C-4624-8997-54C42F02E376} = {008A2BAB-78B4-42EB-A5D4-DE434438CEF0} + {39FC1EC2-2AD3-411F-A545-AB6CCB94FB7E} = {F54C9296-4EF7-40F0-9F20-F23A2270ABC9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E8B0C994-0BF2-4692-9E22-E48B265B2804} diff --git a/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/DependencyInjection/HttpUserAgentParserServiceCollectionExtensionsTests.cs b/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/DependencyInjection/HttpUserAgentParserServiceCollectionExtensionsTests.cs new file mode 100644 index 0000000..f82442f --- /dev/null +++ b/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/DependencyInjection/HttpUserAgentParserServiceCollectionExtensionsTests.cs @@ -0,0 +1,28 @@ +// Copyright © myCSharp 2020-2021, all rights reserved + +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using MyCSharp.HttpUserAgentParser.AspNetCore.DependencyInjection; +using MyCSharp.HttpUserAgentParser.DependencyInjection; +using Xunit; + +namespace MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests.DependencyInjection +{ + public class HttpUserAgentParserDependencyInjectionOptionsExtensionsTests + { + [Fact] + public void AddHttpUserAgentParserAccessor() + { + ServiceCollection services = new(); + HttpUserAgentParserDependencyInjectionOptions options = new(services); + + options.AddHttpUserAgentParserAccessor(); + + services.Count.Should().Be(1); + + services[0].ServiceType.Should().Be(); + services[0].ImplementationType.Should().Be(); + services[0].Lifetime.Should().Be(ServiceLifetime.Singleton); + } + } +} diff --git a/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/HttpUserAgentParserAccessorTests.cs b/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/HttpUserAgentParserAccessorTests.cs new file mode 100644 index 0000000..306e1ad --- /dev/null +++ b/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/HttpUserAgentParserAccessorTests.cs @@ -0,0 +1,37 @@ +// Copyright © myCSharp 2020-2021, all rights reserved + +using FluentAssertions; +using Microsoft.AspNetCore.Http; +using Moq; +using MyCSharp.HttpUserAgentParser.Providers; +using Xunit; + +namespace MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests +{ + public class HttpUserAgentParserAccessorTests + { + [Theory] + [InlineData("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62")] + public void Get(string userAgent) + { + HttpUserAgentInformation userAgentInformation = HttpUserAgentInformation.Parse(userAgent); + + Mock httpMock = new(); + { + DefaultHttpContext context = new DefaultHttpContext(); + context.Request.Headers["User-Agent"] = userAgent; + httpMock.Setup(_ => _.HttpContext).Returns(context); + } + Mock parserMock = new(); + { + parserMock.Setup(x => x.Parse(userAgent)).Returns(userAgentInformation); + } + + HttpUserAgentParserAccessor accessor = new HttpUserAgentParserAccessor(httpMock.Object, parserMock.Object); + HttpUserAgentInformation info = accessor.Get(); + + info.Should().Be(userAgentInformation); + parserMock.Verify(x => x.Parse(userAgent), Times.Once); + } + } +} diff --git a/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests.csproj b/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests.csproj index e9394f0..4fc63b5 100644 --- a/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests.csproj +++ b/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests.csproj @@ -7,7 +7,9 @@ + + all @@ -19,4 +21,8 @@ + + + + \ No newline at end of file diff --git a/tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensionssTests.cs b/tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensionssTests.cs new file mode 100644 index 0000000..f3604a3 --- /dev/null +++ b/tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/DependencyInjection/HttpUserAgentParserMemoryCacheServiceCollectionExtensionssTests.cs @@ -0,0 +1,30 @@ +// Copyright © myCSharp 2020-2021, all rights reserved + +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using MyCSharp.HttpUserAgentParser.MemoryCache.DependencyInjection; +using MyCSharp.HttpUserAgentParser.Providers; +using Xunit; + +namespace MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests.DependencyInjection +{ + public class HttpUserAgentParserMemoryCacheServiceCollectionExtensionssTests + { + [Fact] + public void AddHttpUserAgentMemoryCachedParser() + { + ServiceCollection services = new(); + + services.AddHttpUserAgentMemoryCachedParser(); + + services.Count.Should().Be(2); + + services[0].ImplementationInstance.Should().BeOfType(); + services[0].Lifetime.Should().Be(ServiceLifetime.Singleton); + + services[1].ServiceType.Should().Be(); + services[1].ImplementationType.Should().Be(); + services[1].Lifetime.Should().Be(ServiceLifetime.Singleton); + } + } +} diff --git a/tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/HttpUserAgentParserMemoryCachedProviderOptionsTests.cs b/tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/HttpUserAgentParserMemoryCachedProviderOptionsTests.cs new file mode 100644 index 0000000..ac84bb4 --- /dev/null +++ b/tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/HttpUserAgentParserMemoryCachedProviderOptionsTests.cs @@ -0,0 +1,54 @@ +// Copyright © myCSharp 2020-2021, all rights reserved + +using FluentAssertions; +using Microsoft.Extensions.Caching.Memory; +using Xunit; + +namespace MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests +{ + public class HttpUserAgentParserMemoryCachedProviderOptionsTests + { + [Fact] + public void Ctor() + { + MemoryCacheOptions cacheOptions = new(); + MemoryCacheEntryOptions cacheEntryOptions = new(); + + HttpUserAgentParserMemoryCachedProviderOptions options = new(cacheOptions, cacheEntryOptions); + + options.CacheOptions.Should().Be(cacheOptions); + options.CacheEntryOptions.Should().Be(cacheEntryOptions); + } + + [Fact] + public void Ctor_MemoryCacheOptions() + { + MemoryCacheOptions cacheOptions = new(); + + HttpUserAgentParserMemoryCachedProviderOptions options = new(cacheOptions); + + options.CacheOptions.Should().Be(cacheOptions); + options.CacheEntryOptions.Should().NotBeNull(); + } + + [Fact] + public void Ctor_MemoryCacheEntryOptions() + { + MemoryCacheEntryOptions cacheEntryOptions = new(); + + HttpUserAgentParserMemoryCachedProviderOptions options = new(cacheEntryOptions); + + options.CacheOptions.Should().NotBeNull(); + options.CacheEntryOptions.Should().Be(cacheEntryOptions); + } + + [Fact] + public void Ctor_Empty() + { + HttpUserAgentParserMemoryCachedProviderOptions options = new(); + + options.CacheOptions.Should().NotBeNull(); + options.CacheEntryOptions.Should().NotBeNull(); + } + } +} diff --git a/tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/HttpUserAgentParserMemoryCachedProviderTests.cs b/tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/HttpUserAgentParserMemoryCachedProviderTests.cs new file mode 100644 index 0000000..53d5c58 --- /dev/null +++ b/tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/HttpUserAgentParserMemoryCachedProviderTests.cs @@ -0,0 +1,45 @@ +// Copyright © myCSharp 2020-2021, all rights reserved + +using FluentAssertions; +using Microsoft.Extensions.Caching.Memory; +using Xunit; + +namespace MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests +{ + public class HttpUserAgentParserMemoryCachedProviderTests + { + [Fact] + public void Parse() + { + HttpUserAgentParserMemoryCachedProviderOptions cachedProviderOptions = new(); + IMemoryCache memoryCache = new Microsoft.Extensions.Caching.Memory.MemoryCache(cachedProviderOptions.CacheOptions); + + HttpUserAgentParserMemoryCachedProvider provider = new(memoryCache, cachedProviderOptions); + + // create first + string userAgentOne = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62"; + + HttpUserAgentInformation infoOne = provider.Parse(userAgentOne); + + infoOne.Name.Should().Be("Edge"); + infoOne.Version.Should().Be("90.0.818.62"); + + // check duplicate + + HttpUserAgentInformation infoDuplicate = provider.Parse(userAgentOne); + + infoDuplicate.Name.Should().Be("Edge"); + infoDuplicate.Version.Should().Be("90.0.818.62"); + + // create second + + string userAgentTwo = "Mozilla/5.0 (Android 4.4; Tablet; rv:41.0) Gecko/41.0 Firefox/41.0"; + + HttpUserAgentInformation infoTwo = provider.Parse(userAgentTwo); + + infoTwo.Name.Should().Be("Firefox"); + infoTwo.Version.Should().Be("41.0"); + } + } +} diff --git a/tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests.csproj b/tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests.csproj new file mode 100644 index 0000000..65a1fe7 --- /dev/null +++ b/tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests.csproj @@ -0,0 +1,29 @@ + + + + Exe + net5.0 + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + \ No newline at end of file diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/DependencyInjection/HttpUserAgentParserServiceCollectionExtensionsTests.cs b/tests/MyCSharp.HttpUserAgentParser.UnitTests/DependencyInjection/HttpUserAgentParserServiceCollectionExtensionsTests.cs index 02e1d2b..e456ca1 100644 --- a/tests/MyCSharp.HttpUserAgentParser.UnitTests/DependencyInjection/HttpUserAgentParserServiceCollectionExtensionsTests.cs +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/DependencyInjection/HttpUserAgentParserServiceCollectionExtensionsTests.cs @@ -8,7 +8,7 @@ namespace MyCSharp.HttpUserAgentParser.UnitTests.DependencyInjection { - public class HttpUserAgentParserServiceCollectionExtensionsTests + public class HttpUserAgentParserMemoryCacheServiceCollectionExtensions { public class TestHttpUserAgentParserProvider : IHttpUserAgentParserProvider { diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentInformationTests.cs b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentInformationTests.cs index 6034440..171d973 100644 --- a/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentInformationTests.cs +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentInformationTests.cs @@ -3,7 +3,6 @@ using System.Text.RegularExpressions; using FluentAssertions; using Xunit; -using Xunit.Sdk; namespace MyCSharp.HttpUserAgentParser.UnitTests { diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentPlatformInformationTests.cs b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentPlatformInformationTests.cs new file mode 100644 index 0000000..d4b79d2 --- /dev/null +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentPlatformInformationTests.cs @@ -0,0 +1,25 @@ +// Copyright © myCSharp 2020-2021, all rights reserved + +using System.Text.RegularExpressions; +using FluentAssertions; +using Xunit; + +namespace MyCSharp.HttpUserAgentParser.UnitTests +{ + public class HttpUserAgentPlatformInformationTests + { + [Theory] + [InlineData("Batman", HttpUserAgentPlatformType.Android)] + [InlineData("Robin", HttpUserAgentPlatformType.Windows)] + public void Ctor(string name, HttpUserAgentPlatformType platform) + { + Regex regex = new(""); + + HttpUserAgentPlatformInformation info = new(regex, name, platform); + + info.Regex.Should().Be(regex); + info.Name.Should().Be(name); + info.PlatformType.Should().Be(platform); + } + } +} diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentPlatformTypeTests.cs b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentPlatformTypeTests.cs new file mode 100644 index 0000000..5692d8a --- /dev/null +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentPlatformTypeTests.cs @@ -0,0 +1,26 @@ +// Copyright © myCSharp 2020-2021, all rights reserved + +using FluentAssertions; +using Xunit; + +namespace MyCSharp.HttpUserAgentParser.UnitTests +{ + public class HttpUserAgentPlatformTypeTests + { + [Theory] + [InlineData(HttpUserAgentPlatformType.Unknown, 0)] + [InlineData(HttpUserAgentPlatformType.Generic, 1)] + [InlineData(HttpUserAgentPlatformType.Windows, 2)] + [InlineData(HttpUserAgentPlatformType.Linux, 3)] + [InlineData(HttpUserAgentPlatformType.Unix, 4)] + [InlineData(HttpUserAgentPlatformType.IOS, 5)] + [InlineData(HttpUserAgentPlatformType.MacOS, 6)] + [InlineData(HttpUserAgentPlatformType.BlackBerry, 7)] + [InlineData(HttpUserAgentPlatformType.Android, 8)] + [InlineData(HttpUserAgentPlatformType.Symbian, 9)] + public void TestValue(HttpUserAgentPlatformType type, byte value) + { + type.Should().BeEquivalentTo(value); + } + } +} diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentTypeTests.cs b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentTypeTests.cs new file mode 100644 index 0000000..f516e5f --- /dev/null +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/HttpUserAgentTypeTests.cs @@ -0,0 +1,19 @@ +// Copyright © myCSharp 2020-2021, all rights reserved + +using FluentAssertions; +using Xunit; + +namespace MyCSharp.HttpUserAgentParser.UnitTests +{ + public class HttpUserAgentTypeTests + { + [Theory] + [InlineData(HttpUserAgentType.Unknown, 0)] + [InlineData(HttpUserAgentType.Browser, 1)] + [InlineData(HttpUserAgentType.Robot, 2)] + public void TestValue(HttpUserAgentType type, byte value) + { + type.Should().BeEquivalentTo(value); + } + } +} From 9dfc7716154a05a1945948f9dda67b1e4bef6a2d Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Sun, 16 May 2021 22:37:08 +0200 Subject: [PATCH 28/36] add tests --- ...HttpUserAgentParserMemoryCachedProvider.cs | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs index 46877be..a5a192f 100644 --- a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs @@ -1,4 +1,4 @@ -// Copyright © myCSharp 2020-2021, all rights reserved +// Copyright © myCSharp 2020-2021, all rights reserved using System; using Microsoft.Extensions.Caching.Memory; @@ -32,11 +32,11 @@ public HttpUserAgentInformation Parse(string userAgent) } [ThreadStatic] - private static CacheKey? t_key; + private static CacheKey? s_tKey; private CacheKey GetKey(string userAgent) { - CacheKey key = t_key ??= new(); + CacheKey key = s_tKey ??= new CacheKey(); key.UserAgent = userAgent; key.Options = _options; @@ -44,11 +44,33 @@ private CacheKey GetKey(string userAgent) return key; } - private class CacheKey + private class CacheKey : IEquatable // required for IMemoryCache { public string UserAgent { get; set; } = null!; public HttpUserAgentParserMemoryCachedProviderOptions Options { get; set; } = null!; + + public bool Equals(CacheKey? other) + { + if (ReferenceEquals(this, other)) return true; + if (ReferenceEquals(other, null)) return false; + + return this.UserAgent == other.UserAgent && this.Options == other.Options; + } + + public override bool Equals(object? obj) + { + return this.Equals(obj as CacheKey); + } + + public override int GetHashCode() + { + int hash = 13; + hash = (hash * 7) + UserAgent.GetHashCode(); + hash = (hash * 7) + Options.GetHashCode(); + + return hash; + } } } -} \ No newline at end of file +} From 59700e3b0f076911965414e4fcce09c0c887f4c5 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Mon, 17 May 2021 15:21:03 +0200 Subject: [PATCH 29/36] add cicd --- .github/workflows/ci.yml | 46 ++++++++++++++++++++++++++++++++ MyCSharp.HttpUserAgentParser.sln | 11 ++++++++ 2 files changed, 57 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..fac6cb5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: NETCore + +on: + push: + branches: + - master + pull_request: + types: [closed] + branches: + - master + +env: + BuildConfig: Release + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 # avoid shallow clone so nbgv can do its work. + + - uses: dotnet/nbgv@master # https://github.com/dotnet/nbgv + id: nbgv + + - name: Versioning + run: echo ${{ steps.nbgv.outputs.SemVer2 }} + + - name: Build with dotnet + run: dotnet build + --configuration ${{ env.BuildConfig }} + /p:Version=${{ steps.nbgv.outputs.AssemblyVersion }} + + - name: Test with dotnet + run: dotnet test + + - name: Pack NuGet + run: dotnet pack + --configuration ${{ env.BuildConfig }} + /p:Version=${{ steps.nbgv.outputs.NuGetPackageVersion }} + + - name: Push to NuGet + run: dotnet nuget push **/*.nupkg + --api-key ${{ secrets.NUGET_DEPLOY_KEY }} + --source https://api.nuget.org/v3/index.json + --no-symbols 1 diff --git a/MyCSharp.HttpUserAgentParser.sln b/MyCSharp.HttpUserAgentParser.sln index 5e9f663..8b6605a 100644 --- a/MyCSharp.HttpUserAgentParser.sln +++ b/MyCSharp.HttpUserAgentParser.sln @@ -19,6 +19,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyCSharp.HttpUserAgentParse EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests", "tests\MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests\MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests.csproj", "{39FC1EC2-2AD3-411F-A545-AB6CCB94FB7E}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_", "_", "{5738CE0D-5E6E-47CB-BFF5-08F45A2C33AD}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitignore = .gitignore + .github\workflows\ci.yml = .github\workflows\ci.yml + LICENSE = LICENSE + NuGet.config = NuGet.config + README.md = README.md + version.json = version.json + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU From 40fb27e075854b5b90c0a33de3a1331eea55b314 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Mon, 17 May 2021 15:21:12 +0200 Subject: [PATCH 30/36] add feedback --- .../HttpUserAgentParserMemoryCachedProvider.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs index a5a192f..f50b50d 100644 --- a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs @@ -63,14 +63,7 @@ public override bool Equals(object? obj) return this.Equals(obj as CacheKey); } - public override int GetHashCode() - { - int hash = 13; - hash = (hash * 7) + UserAgent.GetHashCode(); - hash = (hash * 7) + Options.GetHashCode(); - - return hash; - } + public override int GetHashCode() => HashCode.Combine(this.UserAgent, this.Options); } } } From eaa8a2248051dfe26ff3c572f1aecda3f62f9367 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Mon, 17 May 2021 15:32:16 +0200 Subject: [PATCH 31/36] add nuget stuff --- Directory.Build.props | 18 ++++++++ MyCSharp.HttpUserAgentParser.sln | 1 + ...harp.HttpUserAgentParser.AspNetCore.csproj | 22 ++++----- ...HttpUserAgentParserMemoryCachedProvider.cs | 4 +- ...rAgentParserMemoryCachedProviderOptions.cs | 11 +++-- ...arp.HttpUserAgentParser.MemoryCache.csproj | 24 +++++----- .../MyCSharp.HttpUserAgentParser.csproj | 18 ++++---- ...serAgentParser.AspNetCore.UnitTests.csproj | 44 +++++++++--------- ...erAgentParser.MemoryCache.UnitTests.csproj | 46 +++++++++---------- ...Sharp.HttpUserAgentParser.UnitTests.csproj | 44 +++++++++--------- 10 files changed, 127 insertions(+), 105 deletions(-) create mode 100644 Directory.Build.props diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..3a9d663 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,18 @@ + + + 2.12 + true + MyCSharp.de, Benjamin Abt, Günther Foidl and Contributors + https://github.com/mycsharp/HttpUserAgentParser + MIT + en-US + $(MSBuildProjectName.Contains('Test')) + HTTP User Agent Parser for .NET + true + preview + enable + true + true + embedded + + diff --git a/MyCSharp.HttpUserAgentParser.sln b/MyCSharp.HttpUserAgentParser.sln index 8b6605a..faa3246 100644 --- a/MyCSharp.HttpUserAgentParser.sln +++ b/MyCSharp.HttpUserAgentParser.sln @@ -24,6 +24,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_", "_", "{5738CE0D-5E6E-47 .editorconfig = .editorconfig .gitignore = .gitignore .github\workflows\ci.yml = .github\workflows\ci.yml + Directory.Build.props = Directory.Build.props LICENSE = LICENSE NuGet.config = NuGet.config README.md = README.md diff --git a/src/MyCSharp.HttpUserAgentParser.AspNetCore/MyCSharp.HttpUserAgentParser.AspNetCore.csproj b/src/MyCSharp.HttpUserAgentParser.AspNetCore/MyCSharp.HttpUserAgentParser.AspNetCore.csproj index 4278fcb..241883a 100644 --- a/src/MyCSharp.HttpUserAgentParser.AspNetCore/MyCSharp.HttpUserAgentParser.AspNetCore.csproj +++ b/src/MyCSharp.HttpUserAgentParser.AspNetCore/MyCSharp.HttpUserAgentParser.AspNetCore.csproj @@ -1,17 +1,17 @@ - - net5.0 - preview - enable - + + HTTP User Agent Parser Extensions for ASP.NET Core + HTTP User Agent Parser Extensions for ASP.NET Core + net5.0 + - - - + + + - - - + + + \ No newline at end of file diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs index f50b50d..f618c5f 100644 --- a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs @@ -19,7 +19,7 @@ public HttpUserAgentParserMemoryCachedProvider(IMemoryCache memoryCache, HttpUse public HttpUserAgentInformation Parse(string userAgent) { - CacheKey key = GetKey(userAgent); + CacheKey key = this.GetKey(userAgent); return _memoryCache.GetOrCreate(key, static entry => { @@ -53,7 +53,7 @@ private CacheKey GetKey(string userAgent) public bool Equals(CacheKey? other) { if (ReferenceEquals(this, other)) return true; - if (ReferenceEquals(other, null)) return false; + if (other is null) return false; return this.UserAgent == other.UserAgent && this.Options == other.Options; } diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs index 2be1c40..5c8b3df 100644 --- a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs @@ -1,10 +1,13 @@ -// Copyright © myCSharp 2020-2021, all rights reserved +// Copyright © myCSharp 2020-2021, all rights reserved using System; using Microsoft.Extensions.Caching.Memory; namespace MyCSharp.HttpUserAgentParser.MemoryCache { + /// + /// Provider options for + /// public class HttpUserAgentParserMemoryCachedProviderOptions { /// @@ -22,15 +25,15 @@ public HttpUserAgentParserMemoryCachedProviderOptions(MemoryCacheEntryOptions ca public HttpUserAgentParserMemoryCachedProviderOptions(MemoryCacheOptions? cacheOptions = null, MemoryCacheEntryOptions? cacheEntryOptions = null) { - CacheEntryOptions = cacheEntryOptions ?? new MemoryCacheEntryOptions + this.CacheEntryOptions = cacheEntryOptions ?? new MemoryCacheEntryOptions { // defaults SlidingExpiration = TimeSpan.FromDays(1) }; - CacheOptions = cacheOptions ?? new MemoryCacheOptions + this.CacheOptions = cacheOptions ?? new MemoryCacheOptions { SizeLimit = 256 }; } } -} \ No newline at end of file +} diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/MyCSharp.HttpUserAgentParser.MemoryCache.csproj b/src/MyCSharp.HttpUserAgentParser.MemoryCache/MyCSharp.HttpUserAgentParser.MemoryCache.csproj index 8e8862d..161ab49 100644 --- a/src/MyCSharp.HttpUserAgentParser.MemoryCache/MyCSharp.HttpUserAgentParser.MemoryCache.csproj +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/MyCSharp.HttpUserAgentParser.MemoryCache.csproj @@ -1,18 +1,18 @@ - - net5.0 - preview - enable - + + HTTP User Agent Parser Extensions for IMemoryCache + HTTP User Agent Parser Extensions for IMemoryCache + netstandard2.1 + - - - + + + + - - - - + + + \ No newline at end of file diff --git a/src/MyCSharp.HttpUserAgentParser/MyCSharp.HttpUserAgentParser.csproj b/src/MyCSharp.HttpUserAgentParser/MyCSharp.HttpUserAgentParser.csproj index 87da525..dd386d8 100644 --- a/src/MyCSharp.HttpUserAgentParser/MyCSharp.HttpUserAgentParser.csproj +++ b/src/MyCSharp.HttpUserAgentParser/MyCSharp.HttpUserAgentParser.csproj @@ -1,13 +1,13 @@ - + - - net5.0 - preview - enable - + + HTTP User Agent Parser + Parses user agents for Browser, Platform and Bots. + netstandard2.1 + - - - + + + \ No newline at end of file diff --git a/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests.csproj b/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests.csproj index 4fc63b5..3edaabe 100644 --- a/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests.csproj +++ b/tests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests/MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests.csproj @@ -1,28 +1,28 @@ - - Exe - net5.0 - + + Exe + net5.0 + - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - - - + + + \ No newline at end of file diff --git a/tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests.csproj b/tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests.csproj index 65a1fe7..75ba3b0 100644 --- a/tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests.csproj +++ b/tests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests/MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests.csproj @@ -1,29 +1,29 @@ - - Exe - net5.0 - + + Exe + net5.0 + - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - - - + + + \ No newline at end of file diff --git a/tests/MyCSharp.HttpUserAgentParser.UnitTests/MyCSharp.HttpUserAgentParser.UnitTests.csproj b/tests/MyCSharp.HttpUserAgentParser.UnitTests/MyCSharp.HttpUserAgentParser.UnitTests.csproj index 4753354..790d6e9 100644 --- a/tests/MyCSharp.HttpUserAgentParser.UnitTests/MyCSharp.HttpUserAgentParser.UnitTests.csproj +++ b/tests/MyCSharp.HttpUserAgentParser.UnitTests/MyCSharp.HttpUserAgentParser.UnitTests.csproj @@ -1,28 +1,28 @@ - - Exe - net5.0 - + + Exe + net5.0 + - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - - - + + + \ No newline at end of file From 06e50e4480db8814f2d8f9790bed5c894f4ac542 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Mon, 17 May 2021 15:34:57 +0200 Subject: [PATCH 32/36] add docs --- .../HttpUserAgentParserMemoryCachedProviderOptions.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs index 5c8b3df..8ef3fef 100644 --- a/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs +++ b/src/MyCSharp.HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProviderOptions.cs @@ -7,13 +7,13 @@ namespace MyCSharp.HttpUserAgentParser.MemoryCache { /// /// Provider options for + /// + /// Default of is 256. + /// Default of is 1 day + /// /// public class HttpUserAgentParserMemoryCachedProviderOptions { - /// - /// Default of is 256. - /// Default of is 1 day - /// public MemoryCacheOptions CacheOptions { get; } public MemoryCacheEntryOptions CacheEntryOptions { get; } @@ -32,6 +32,7 @@ public HttpUserAgentParserMemoryCachedProviderOptions(MemoryCacheOptions? cacheO }; this.CacheOptions = cacheOptions ?? new MemoryCacheOptions { + // defaults SizeLimit = 256 }; } From 95593cb03c0e9ebd2038d3eba44ca728027a7446 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Mon, 17 May 2021 16:37:11 +0200 Subject: [PATCH 33/36] add benchmark --- MyCSharp.HttpUserAgentParser.sln | 9 ++++ ...harp.HttpUserAgentParser.Benchmarks.csproj | 22 ++++++++++ .../Program.cs | 44 +++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 perf/MyCSharp.HttpUserAgentParser.Benchmarks/MyCSharp.HttpUserAgentParser.Benchmarks.csproj create mode 100644 perf/MyCSharp.HttpUserAgentParser.Benchmarks/Program.cs diff --git a/MyCSharp.HttpUserAgentParser.sln b/MyCSharp.HttpUserAgentParser.sln index faa3246..defb60f 100644 --- a/MyCSharp.HttpUserAgentParser.sln +++ b/MyCSharp.HttpUserAgentParser.sln @@ -31,6 +31,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_", "_", "{5738CE0D-5E6E-47 version.json = version.json EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "perf", "perf", "{FAAD18A0-E1B8-448D-B611-AFBDA8A89808}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyCSharp.HttpUserAgentParser.Benchmarks", "perf\MyCSharp.HttpUserAgentParser.Benchmarks\MyCSharp.HttpUserAgentParser.Benchmarks.csproj", "{A0D213E9-6408-46D1-AFAF-5096C2F6E027}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -61,6 +65,10 @@ Global {39FC1EC2-2AD3-411F-A545-AB6CCB94FB7E}.Debug|Any CPU.Build.0 = Debug|Any CPU {39FC1EC2-2AD3-411F-A545-AB6CCB94FB7E}.Release|Any CPU.ActiveCfg = Release|Any CPU {39FC1EC2-2AD3-411F-A545-AB6CCB94FB7E}.Release|Any CPU.Build.0 = Release|Any CPU + {A0D213E9-6408-46D1-AFAF-5096C2F6E027}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0D213E9-6408-46D1-AFAF-5096C2F6E027}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0D213E9-6408-46D1-AFAF-5096C2F6E027}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0D213E9-6408-46D1-AFAF-5096C2F6E027}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -72,6 +80,7 @@ Global {75960783-8BF9-479C-9ECF-E9653B74C9A2} = {F54C9296-4EF7-40F0-9F20-F23A2270ABC9} {3C8CCD44-F47C-4624-8997-54C42F02E376} = {008A2BAB-78B4-42EB-A5D4-DE434438CEF0} {39FC1EC2-2AD3-411F-A545-AB6CCB94FB7E} = {F54C9296-4EF7-40F0-9F20-F23A2270ABC9} + {A0D213E9-6408-46D1-AFAF-5096C2F6E027} = {FAAD18A0-E1B8-448D-B611-AFBDA8A89808} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E8B0C994-0BF2-4692-9E22-E48B265B2804} diff --git a/perf/MyCSharp.HttpUserAgentParser.Benchmarks/MyCSharp.HttpUserAgentParser.Benchmarks.csproj b/perf/MyCSharp.HttpUserAgentParser.Benchmarks/MyCSharp.HttpUserAgentParser.Benchmarks.csproj new file mode 100644 index 0000000..3014431 --- /dev/null +++ b/perf/MyCSharp.HttpUserAgentParser.Benchmarks/MyCSharp.HttpUserAgentParser.Benchmarks.csproj @@ -0,0 +1,22 @@ + + + + Exe + net5.0 + true + 9.0 + full + true + + + + + + + + + + + + + diff --git a/perf/MyCSharp.HttpUserAgentParser.Benchmarks/Program.cs b/perf/MyCSharp.HttpUserAgentParser.Benchmarks/Program.cs new file mode 100644 index 0000000..d2f857e --- /dev/null +++ b/perf/MyCSharp.HttpUserAgentParser.Benchmarks/Program.cs @@ -0,0 +1,44 @@ +using System.Reflection; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Running; +using Ng.Services; +using UAParser; + +namespace MyCSharp.HttpUserAgentParser.Benchmarks +{ + [MemoryDiagnoser] + public class UserAgent + { + + private Parser _uaParser; + private UserAgentService _userAgentService; + + private const string TestUserAgent = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62"; + + [GlobalSetup] + public void Setup() + { + _uaParser = UAParser.Parser.GetDefault(new ParserOptions()); + _userAgentService = new UserAgentService(); + } + + [Benchmark] + public void UAParserTest() => _uaParser.Parse(TestUserAgent); + + [Benchmark] + public void UserAgentService() => _userAgentService.Parse(TestUserAgent); + + [Benchmark] + public void HttpUserAgentParserTest() => HttpUserAgentParser.Parse(TestUserAgent); + } + + class Program + { + static void Main() + { + BenchmarkRunner.Run(); + } + } +} From e0da0a37107cc5ddea96d28c6fddb3dc83428975 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Mon, 17 May 2021 16:53:14 +0200 Subject: [PATCH 34/36] add benchmark --- README.md | 20 +- .../ExternalCode/UserAgentServiceUserAgent.cs | 332 ++++++++++++++++++ ...harp.HttpUserAgentParser.Benchmarks.csproj | 1 - .../Program.cs | 17 +- 4 files changed, 359 insertions(+), 11 deletions(-) create mode 100644 perf/MyCSharp.HttpUserAgentParser.Benchmarks/ExternalCode/UserAgentServiceUserAgent.cs diff --git a/README.md b/README.md index eb8dc7f..1c9516e 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,24 @@ public void MyMethod(IHttpUserAgentParserAccessor parserAccessor) } ``` +## Benchmark + +### Parse of `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62` + +```sh +BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042 +AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores +.NET Core SDK=5.0.300-preview.21228.15 + [Host] : .NET Core 5.0.5 (CoreCLR 5.0.521.16609, CoreFX 5.0.521.16609), X64 RyuJIT + DefaultJob : .NET Core 5.0.5 (CoreCLR 5.0.521.16609, CoreFX 5.0.521.16609), X64 RyuJIT +``` + +| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +|-------------------- |------------:|---------:|---------:|-------:|-------:|------:|----------:| +| UA Parser | 1,017.48 us | 6.132 us | 5.736 us | 1.9531 | - | - | 36921 B | +| UserAgentService | 51.23 us | 0.661 us | 0.619 us | 3.3569 | 0.3052 | - | 56616 B | +| HttpUserAgentParser | 26.80 us | 0.310 us | 0.290 us | - | - | - | 432 B | + ## Disclaimer This library is inspired by [UserAgentService by DannyBoyNg](https://github.com/DannyBoyNg/UserAgentService) and contains optimizations for our requirements on [myCSharp.de](https://mycsharp.de). @@ -136,4 +154,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/perf/MyCSharp.HttpUserAgentParser.Benchmarks/ExternalCode/UserAgentServiceUserAgent.cs b/perf/MyCSharp.HttpUserAgentParser.Benchmarks/ExternalCode/UserAgentServiceUserAgent.cs new file mode 100644 index 0000000..e32d2b9 --- /dev/null +++ b/perf/MyCSharp.HttpUserAgentParser.Benchmarks/ExternalCode/UserAgentServiceUserAgent.cs @@ -0,0 +1,332 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace MyCSharp.HttpUserAgentParser.Benchmarks.ExternalCode +{ + /// + /// from https://raw.githubusercontent.com/DannyBoyNg/UserAgentService/master/UserAgentService/UserAgent.cs + /// as copy because ctor is internal + /// + internal class UserAgentServiceUserAgent + { + internal static Dictionary platforms = new() + { + { "windows nt 10.0", "Windows 10" }, + { "windows nt 6.3", "Windows 8.1" }, + { "windows nt 6.2", "Windows 8" }, + { "windows nt 6.1", "Windows 7" }, + { "windows nt 6.0", "Windows Vista" }, + { "windows nt 5.2", "Windows 2003" }, + { "windows nt 5.1", "Windows XP" }, + { "windows nt 5.0", "Windows 2000" }, + { "windows nt 4.0", "Windows NT 4.0" }, + { "winnt4.0", "Windows NT 4.0" }, + { "winnt 4.0", "Windows NT" }, + { "winnt", "Windows NT" }, + { "windows 98", "Windows 98" }, + { "win98", "Windows 98" }, + { "windows 95", "Windows 95" }, + { "win95", "Windows 95" }, + { "windows phone", "Windows Phone" }, + { "windows", "Unknown Windows OS" }, + { "android", "Android" }, + { "blackberry", "BlackBerry" }, + { "iphone", "iOS" }, + { "ipad", "iOS" }, + { "ipod", "iOS" }, + { "os x", "Mac OS X" }, + { "ppc mac", "Power PC Mac" }, + { "freebsd", "FreeBSD" }, + { "ppc", "Macintosh" }, + { "linux", "Linux" }, + { "debian", "Debian" }, + { "sunos", "Sun Solaris" }, + { "beos", "BeOS" }, + { "apachebench", "ApacheBench" }, + { "aix", "AIX" }, + { "irix", "Irix" }, + { "osf", "DEC OSF" }, + { "hp-ux", "HP-UX" }, + { "netbsd", "NetBSD" }, + { "bsdi", "BSDi" }, + { "openbsd", "OpenBSD" }, + { "gnu", "GNU/Linux" }, + { "unix", "Unknown Unix OS" }, + { "symbian", "Symbian OS" }, + }; + + internal static Dictionary browsers = new() + { + { "OPR", "Opera" }, + { "Flock", "Flock" }, + { "Edge", "Edge" }, + { "Edg", "Edge" }, + { "Chrome", "Chrome" }, + { "Opera.*?Version", "Opera" }, + { "Opera", "Opera" }, + { "MSIE", "Internet Explorer" }, + { "Internet Explorer", "Internet Explorer" }, + { "Trident.* rv", "Internet Explorer" }, + { "Shiira", "Shiira" }, + { "Firefox", "Firefox" }, + { "Chimera", "Chimera" }, + { "Phoenix", "Phoenix" }, + { "Firebird", "Firebird" }, + { "Camino", "Camino" }, + { "Netscape", "Netscape" }, + { "OmniWeb", "OmniWeb" }, + { "Safari", "Safari" }, + { "Mozilla", "Mozilla" }, + { "Konqueror", "Konqueror" }, + { "icab", "iCab" }, + { "Lynx", "Lynx" }, + { "Links", "Links" }, + { "hotjava", "HotJava" }, + { "amaya", "Amaya" }, + { "IBrowse", "IBrowse" }, + { "Maxthon", "Maxthon" }, + { "Ubuntu", "Ubuntu Web Browser" }, + { "Vivaldi", "Vivaldi" }, + }; + + internal static Dictionary mobiles = new() + { + // Legacy + { "mobileexplorer", "Mobile Explorer" }, + { "palmsource", "Palm" }, + { "palmscape", "Palmscape" }, + // Phones and Manufacturers + { "motorola", "Motorola" }, + { "nokia", "Nokia" }, + { "palm", "Palm" }, + { "iphone", "Apple iPhone" }, + { "ipad", "iPad" }, + { "ipod", "Apple iPod Touch" }, + { "sony", "Sony Ericsson" }, + { "ericsson", "Sony Ericsson" }, + { "blackberry", "BlackBerry" }, + { "cocoon", "O2 Cocoon" }, + { "blazer", "Treo" }, + { "lg", "LG" }, + { "amoi", "Amoi" }, + { "xda", "XDA" }, + { "mda", "MDA" }, + { "vario", "Vario" }, + { "htc", "HTC" }, + { "samsung", "Samsung" }, + { "sharp", "Sharp" }, + { "sie-", "Siemens" }, + { "alcatel", "Alcatel" }, + { "benq", "BenQ" }, + { "ipaq", "HP iPaq" }, + { "mot-", "Motorola" }, + { "playstation portable", "PlayStation Portable" }, + { "playstation 3", "PlayStation 3" }, + { "playstation vita", "PlayStation Vita" }, + { "hiptop", "Danger Hiptop" }, + { "nec-", "NEC" }, + { "panasonic", "Panasonic" }, + { "philips", "Philips" }, + { "sagem", "Sagem" }, + { "sanyo", "Sanyo" }, + { "spv", "SPV" }, + { "zte", "ZTE" }, + { "sendo", "Sendo" }, + { "nintendo dsi", "Nintendo DSi" }, + { "nintendo ds", "Nintendo DS" }, + { "nintendo 3ds", "Nintendo 3DS" }, + { "wii", "Nintendo Wii" }, + { "open web", "Open Web" }, + { "openweb", "OpenWeb" }, + // Operating Systems + { "android", "Android" }, + { "symbian", "Symbian" }, + { "SymbianOS", "SymbianOS" }, + { "elaine", "Palm" }, + { "series60", "Symbian S60" }, + { "windows ce", "Windows CE" }, + // Browsers + { "obigo", "Obigo" }, + { "netfront", "Netfront Browser" }, + { "openwave", "Openwave Browser" }, + { "mobilexplorer", "Mobile Explorer" }, + { "operamini", "Opera Mini" }, + { "opera mini", "Opera Mini" }, + { "opera mobi", "Opera Mobile" }, + { "fennec", "Firefox Mobile" }, + // Other + { "digital paths", "Digital Paths" }, + { "avantgo", "AvantGo" }, + { "xiino", "Xiino" }, + { "novarra", "Novarra Transcoder" }, + { "vodafone", "Vodafone" }, + { "docomo", "NTT DoCoMo" }, + { "o2", "O2" }, + // Fallback + { "mobile", "Generic Mobile" }, + { "wireless", "Generic Mobile" }, + { "j2me", "Generic Mobile" }, + { "midp", "Generic Mobile" }, + { "cldc", "Generic Mobile" }, + { "up.link", "Generic Mobile" }, + { "up.browser", "Generic Mobile" }, + { "smartphone", "Generic Mobile" }, + { "cellphone", "Generic Mobile" }, + }; + + internal static Dictionary robots = new() + { + { "googlebot", "Googlebot" }, + { "msnbot", "MSNBot" }, + { "baiduspider", "Baiduspider" }, + { "bingbot", "Bing" }, + { "slurp", "Inktomi Slurp" }, + { "yahoo", "Yahoo" }, + { "ask jeeves", "Ask Jeeves" }, + { "fastcrawler", "FastCrawler" }, + { "infoseek", "InfoSeek Robot 1.0" }, + { "lycos", "Lycos" }, + { "yandex", "YandexBot" }, + { "mediapartners-google", "MediaPartners Google" }, + { "CRAZYWEBCRAWLER", "Crazy Webcrawler" }, + { "adsbot-google", "AdsBot Google" }, + { "feedfetcher-google", "Feedfetcher Google" }, + { "curious george", "Curious George" }, + { "ia_archiver", "Alexa Crawler" }, + { "MJ12bot", "Majestic-12" }, + { "Uptimebot", "Uptimebot" }, + }; + + internal string Agent = ""; + + /// + /// Gets or sets a value indicating whether this UserAgent is a browser. + /// + /// + /// true if this UserAgent is a browser; otherwise, false. + /// + public bool IsBrowser { get; set; } = false; + /// + /// Gets or sets a value indicating whether this UserAgent is a robot. + /// + /// + /// true if this UserAgent is a robot; otherwise, false. + /// + public bool IsRobot { get; set; } = false; + /// + /// Gets or sets a value indicating whether this UserAgent is a mobile device. + /// + /// + /// true if this UserAgent is a mobile device; otherwise, false. + /// + public bool IsMobile { get; set; } = false; + /// + /// Gets or sets the platform. + /// + /// + /// The platform or operating system. + /// + public string Platform { get; set; } = ""; + /// + /// Gets or sets the browser. + /// + /// + /// The browser. + /// + public string Browser { get; set; } = ""; + /// + /// Gets or sets the browser version. + /// + /// + /// The browser version. + /// + public string BrowserVersion { get; set; } = ""; + /// + /// Gets or sets the mobile device. + /// + /// + /// The mobile device. + /// + public string Mobile { get; set; } = ""; + /// + /// Gets or sets the robot. + /// + /// + /// The robot. + /// + public string Robot { get; set; } = ""; + + internal UserAgentServiceUserAgent(string? userAgentString = null) + { + if (userAgentString != null) + { + Agent = userAgentString.Trim(); + this.SetPlatform(); + if (this.SetRobot()) return; + if (this.SetBrowser()) return; + if (this.SetMobile()) return; + } + } + + internal bool SetPlatform() + { + foreach (var item in platforms) + { + if (Regex.IsMatch(Agent, $"{Regex.Escape(item.Key)}", RegexOptions.IgnoreCase)) + { + this.Platform = item.Value; + return true; + } + } + this.Platform = "Unknown Platform"; + return false; + } + + internal bool SetBrowser() + { + foreach (var item in browsers) + { + var match = Regex.Match(Agent, $@"{item.Key}.*?([0-9\.]+)", RegexOptions.IgnoreCase); + if (match.Success) + { + this.IsBrowser = true; + this.BrowserVersion = match.Groups[1].Value; + this.Browser = item.Value; + this.SetMobile(); + return true; + } + } + return false; + } + + internal bool SetRobot() + { + foreach (var item in robots) + { + if (Regex.IsMatch(Agent, $"{Regex.Escape(item.Key)}", RegexOptions.IgnoreCase)) + { + this.IsRobot = true; + this.Robot = item.Value; + this.SetMobile(); + return true; + } + } + return false; + } + + internal bool SetMobile() + { + foreach (var item in mobiles) + { + if (Agent?.IndexOf(item.Key, StringComparison.OrdinalIgnoreCase) != -1) + { + this.IsMobile = true; + this.Mobile = item.Value; + return true; + } + } + return false; + } + } +} diff --git a/perf/MyCSharp.HttpUserAgentParser.Benchmarks/MyCSharp.HttpUserAgentParser.Benchmarks.csproj b/perf/MyCSharp.HttpUserAgentParser.Benchmarks/MyCSharp.HttpUserAgentParser.Benchmarks.csproj index 3014431..bbfef02 100644 --- a/perf/MyCSharp.HttpUserAgentParser.Benchmarks/MyCSharp.HttpUserAgentParser.Benchmarks.csproj +++ b/perf/MyCSharp.HttpUserAgentParser.Benchmarks/MyCSharp.HttpUserAgentParser.Benchmarks.csproj @@ -11,7 +11,6 @@ - diff --git a/perf/MyCSharp.HttpUserAgentParser.Benchmarks/Program.cs b/perf/MyCSharp.HttpUserAgentParser.Benchmarks/Program.cs index d2f857e..c12ea8e 100644 --- a/perf/MyCSharp.HttpUserAgentParser.Benchmarks/Program.cs +++ b/perf/MyCSharp.HttpUserAgentParser.Benchmarks/Program.cs @@ -1,8 +1,6 @@ -using System.Reflection; using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; -using Ng.Services; +using MyCSharp.HttpUserAgentParser.Benchmarks.ExternalCode; using UAParser; namespace MyCSharp.HttpUserAgentParser.Benchmarks @@ -12,25 +10,26 @@ public class UserAgent { private Parser _uaParser; - private UserAgentService _userAgentService; private const string TestUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62"; + [GlobalSetup] public void Setup() { _uaParser = UAParser.Parser.GetDefault(new ParserOptions()); - _userAgentService = new UserAgentService(); } - [Benchmark] + [Benchmark(Description = "UA Parser")] public void UAParserTest() => _uaParser.Parse(TestUserAgent); - [Benchmark] - public void UserAgentService() => _userAgentService.Parse(TestUserAgent); - [Benchmark] + [Benchmark(Description = "UserAgentService")] + public void UserAgentServiceTest() => new UserAgentServiceUserAgent(TestUserAgent); + + + [Benchmark(Description = "HttpUserAgentParser")] public void HttpUserAgentParserTest() => HttpUserAgentParser.Parse(TestUserAgent); } From c5ab07159318c2165cb256ea2a56f6162bc9af95 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Mon, 17 May 2021 17:00:06 +0200 Subject: [PATCH 35/36] add benchmarks --- README.md | 10 +-- .../ExternalCode/UserAgentServiceUserAgent.cs | 2 + .../Program.cs | 35 +--------- .../UserAgentBenchmarks.cs | 70 +++++++++++++++++++ 4 files changed, 80 insertions(+), 37 deletions(-) create mode 100644 perf/MyCSharp.HttpUserAgentParser.Benchmarks/UserAgentBenchmarks.cs diff --git a/README.md b/README.md index 1c9516e..41d99d8 100644 --- a/README.md +++ b/README.md @@ -117,11 +117,11 @@ AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores DefaultJob : .NET Core 5.0.5 (CoreCLR 5.0.521.16609, CoreFX 5.0.521.16609), X64 RyuJIT ``` -| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | -|-------------------- |------------:|---------:|---------:|-------:|-------:|------:|----------:| -| UA Parser | 1,017.48 us | 6.132 us | 5.736 us | 1.9531 | - | - | 36921 B | -| UserAgentService | 51.23 us | 0.661 us | 0.619 us | 3.3569 | 0.3052 | - | 56616 B | -| HttpUserAgentParser | 26.80 us | 0.310 us | 0.290 us | - | - | - | 432 B | +| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +|-------------------- |------------:|----------:|----------:|--------:|-------:|------:|----------:| +| 'UA Parser' | 3,238.59 us | 27.435 us | 25.663 us | 7.8125 | - | - | 168225 B | +| UserAgentService | 391.11 us | 5.126 us | 4.795 us | 35.1563 | 3.4180 | - | 589664 B | +| HttpUserAgentParser | 67.07 us | 0.740 us | 0.693 us | - | - | - | 848 B | ## Disclaimer diff --git a/perf/MyCSharp.HttpUserAgentParser.Benchmarks/ExternalCode/UserAgentServiceUserAgent.cs b/perf/MyCSharp.HttpUserAgentParser.Benchmarks/ExternalCode/UserAgentServiceUserAgent.cs index e32d2b9..661a3e8 100644 --- a/perf/MyCSharp.HttpUserAgentParser.Benchmarks/ExternalCode/UserAgentServiceUserAgent.cs +++ b/perf/MyCSharp.HttpUserAgentParser.Benchmarks/ExternalCode/UserAgentServiceUserAgent.cs @@ -1,3 +1,5 @@ +// Copyright © myCSharp 2020-2021, all rights reserved + using System; using System.Collections.Generic; using System.Text.RegularExpressions; diff --git a/perf/MyCSharp.HttpUserAgentParser.Benchmarks/Program.cs b/perf/MyCSharp.HttpUserAgentParser.Benchmarks/Program.cs index c12ea8e..743334c 100644 --- a/perf/MyCSharp.HttpUserAgentParser.Benchmarks/Program.cs +++ b/perf/MyCSharp.HttpUserAgentParser.Benchmarks/Program.cs @@ -1,43 +1,14 @@ -using BenchmarkDotNet.Attributes; +// Copyright © myCSharp 2020-2021, all rights reserved + using BenchmarkDotNet.Running; -using MyCSharp.HttpUserAgentParser.Benchmarks.ExternalCode; -using UAParser; namespace MyCSharp.HttpUserAgentParser.Benchmarks { - [MemoryDiagnoser] - public class UserAgent - { - - private Parser _uaParser; - - private const string TestUserAgent = - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62"; - - - [GlobalSetup] - public void Setup() - { - _uaParser = UAParser.Parser.GetDefault(new ParserOptions()); - } - - [Benchmark(Description = "UA Parser")] - public void UAParserTest() => _uaParser.Parse(TestUserAgent); - - - [Benchmark(Description = "UserAgentService")] - public void UserAgentServiceTest() => new UserAgentServiceUserAgent(TestUserAgent); - - - [Benchmark(Description = "HttpUserAgentParser")] - public void HttpUserAgentParserTest() => HttpUserAgentParser.Parse(TestUserAgent); - } - class Program { static void Main() { - BenchmarkRunner.Run(); + BenchmarkRunner.Run(); } } } diff --git a/perf/MyCSharp.HttpUserAgentParser.Benchmarks/UserAgentBenchmarks.cs b/perf/MyCSharp.HttpUserAgentParser.Benchmarks/UserAgentBenchmarks.cs new file mode 100644 index 0000000..76a77bd --- /dev/null +++ b/perf/MyCSharp.HttpUserAgentParser.Benchmarks/UserAgentBenchmarks.cs @@ -0,0 +1,70 @@ +// Copyright © myCSharp 2020-2021, all rights reserved + +using System.Collections.Generic; +using System.Linq; +using BenchmarkDotNet.Attributes; +using MyCSharp.HttpUserAgentParser.Benchmarks.ExternalCode; +using UAParser; + +namespace MyCSharp.HttpUserAgentParser.Benchmarks +{ + [MemoryDiagnoser] +#if OS_WIN + [EtwProfiler] // needs admin-rights +#endif + public class UserAgentBenchmarks + { + private Parser _uaParser; + + private string[] _testUserAgentMix; + + private static IEnumerable GetTestUserAgents() + { + yield return + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"; + yield return "APIs-Google (+https://developers.google.com/webmasters/APIs-Google.html)"; + yield return "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0"; + yield return "yeah I'm unknown user agent, just to bring some fun to the mix"; + } + + [GlobalSetup] + public void Setup() + { + _uaParser = UAParser.Parser.GetDefault(new ParserOptions()); + _testUserAgentMix = GetTestUserAgents().ToArray(); + } + + [Benchmark(Description = "UA Parser")] + public void UAParserTest() + { + string[] testUserAgentMix = _testUserAgentMix; + + for (int i = 0; i < testUserAgentMix.Length; ++i) + { + _uaParser.Parse(testUserAgentMix[i]); + } + } + + [Benchmark(Description = "UserAgentService")] + public void UserAgentServiceTest() + { + string[] testUserAgentMix = _testUserAgentMix; + + for (int i = 0; i < testUserAgentMix.Length; ++i) + { + new UserAgentServiceUserAgent(testUserAgentMix[i]); + } + } + + [Benchmark(Description = "HttpUserAgentParser")] + public void HttpUserAgentParserTest() + { + string[] testUserAgentMix = _testUserAgentMix; + + for (int i = 0; i < testUserAgentMix.Length; ++i) + { + HttpUserAgentParser.Parse(testUserAgentMix[i]); + } + } + } +} From 8ab6364daae2a7f09ce804f183fa4c3e6b79ba02 Mon Sep 17 00:00:00 2001 From: Benjamin Abt Date: Mon, 17 May 2021 17:00:18 +0200 Subject: [PATCH 36/36] fix doc --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 41d99d8..2190b88 100644 --- a/README.md +++ b/README.md @@ -107,8 +107,6 @@ public void MyMethod(IHttpUserAgentParserAccessor parserAccessor) ## Benchmark -### Parse of `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62` - ```sh BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042 AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores