Skip to content

Commit 09bb0cf

Browse files
authored
Enable initial browser capabilities (#70)
This implements just the built in capabilities and does not provide an ability at the moment of augmenting with one's own patterns. Derived from the pattern in System.Web with a few modifications for perf reasons and new APIs available.
1 parent 34dae7e commit 09bb0cf

17 files changed

+1150
-17
lines changed

src/Microsoft.AspNetCore.SystemWebAdapters/Adapters/SystemWebAdaptersExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Diagnostics.CodeAnalysis;
88
using System.Web;
99
using System.Web.Caching;
10+
using System.Web.Configuration;
1011
using Microsoft.AspNetCore.Builder;
1112
using Microsoft.AspNetCore.Http.Features;
1213
using Microsoft.AspNetCore.SystemWebAdapters.Internal;
@@ -20,6 +21,7 @@ public static ISystemWebAdapterBuilder AddSystemWebAdapters(this IServiceCollect
2021
{
2122
services.AddHttpContextAccessor();
2223
services.AddSingleton<Cache>();
24+
services.AddSingleton<BrowserCapabilitiesFactory>();
2325

2426
return new Builder(services);
2527
}

src/Microsoft.AspNetCore.SystemWebAdapters/Configuration/BrowserCapabilitiesFactory.cs

Lines changed: 676 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Web.Configuration;
5+
6+
public class HttpCapabilitiesBase
7+
{
8+
private readonly ParsedBrowserResult _data;
9+
10+
private protected HttpCapabilitiesBase(BrowserCapabilitiesFactory factory, string userAgent)
11+
{
12+
_data = factory.Process(userAgent);
13+
}
14+
15+
public string? Browser => _data["browser"];
16+
17+
public string? Version => _data["version"];
18+
19+
public int MajorVersion => _data.GetInt("majorversion");
20+
21+
public double MinorVersion => _data.GetDouble("minorversion");
22+
23+
public string? Platform => _data["platform"];
24+
25+
public bool Crawler => _data.GetBoolean("crawler");
26+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Diagnostics;
6+
using System.Globalization;
7+
8+
namespace System.Web.Configuration;
9+
10+
internal class ParsedBrowserResult
11+
{
12+
private readonly Dictionary<string, string?> _data = new(StringComparer.OrdinalIgnoreCase);
13+
14+
[Conditional("NotNeededYet")]
15+
[Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Retained in case we need the data later")]
16+
public void AddBrowser(string browser)
17+
{
18+
}
19+
20+
public string? this[string key]
21+
{
22+
get => _data.TryGetValue(key, out var value) ? value : null;
23+
set => _data[key] = value;
24+
}
25+
26+
public int GetInt(string key) => int.TryParse(this[key], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) ? result : throw new HttpUnhandledException($"Invalid string from browser capabilities '{key}'");
27+
28+
public bool GetBoolean(string key) => bool.TryParse(this[key], out var result) && result;
29+
30+
public double GetDouble(string key)
31+
{
32+
const NumberStyles Style = NumberStyles.Float | NumberStyles.AllowDecimalPoint;
33+
34+
var value = this[key];
35+
36+
if (value is not null)
37+
{
38+
if (double.TryParse(value, Style, CultureInfo.InvariantCulture, out var result))
39+
{
40+
return result;
41+
}
42+
43+
// Handle if there's more than one decimal i.e. .4.1 -> .4
44+
var firstDecimal = value.IndexOf('.', StringComparison.Ordinal);
45+
46+
if (firstDecimal != -1)
47+
{
48+
var nextDecimal = value.IndexOf('.', firstDecimal + 1);
49+
50+
if (nextDecimal != -1)
51+
{
52+
if (double.TryParse(value.AsSpan()[..nextDecimal], Style, CultureInfo.InvariantCulture, out var result2))
53+
{
54+
return result2;
55+
}
56+
}
57+
}
58+
}
59+
60+
throw new HttpUnhandledException($"Invalid string from browser capabilities '{key}'");
61+
}
62+
}

src/Microsoft.AspNetCore.SystemWebAdapters/ExcludedApis.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ M:System.Web.Caching.Cache.#ctor(Microsoft.AspNetCore.Http.HttpContext)
4545

4646
M:System.Web.Caching.Cache.#ctor(Microsoft.Extensions.Caching.Memory.IMemoryCache)
4747

48-
# Remote app
48+
# Authentication
4949
T:Microsoft.AspNetCore.SystemWebAdapters.RemoteAppAuthenticationExtensions
5050
T:Microsoft.AspNetCore.SystemWebAdapters.RemoteServiceOptions
5151
T:Microsoft.AspNetCore.SystemWebAdapters.Authentication.IRemoteAppAuthenticationResultProcessor
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
namespace System.Web
4+
using System.Web.Configuration;
5+
6+
namespace System.Web;
7+
8+
public class HttpBrowserCapabilities : HttpCapabilitiesBase
59
{
6-
public class HttpBrowserCapabilities
10+
internal HttpBrowserCapabilities(BrowserCapabilitiesFactory factory, string userAgent)
11+
: base(factory, userAgent)
712
{
13+
814
}
915
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Web;
5+
6+
[Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations", Justification = Constants.ApiFromAspNet)]
7+
public class HttpBrowserCapabilitiesBase
8+
{
9+
public virtual string? Browser => throw new NotImplementedException();
10+
11+
public virtual string? Version => throw new NotImplementedException();
12+
13+
public virtual int MajorVersion => throw new NotImplementedException();
14+
15+
public virtual double MinorVersion => throw new NotImplementedException();
16+
17+
public virtual string? Platform => throw new NotImplementedException();
18+
19+
public virtual bool Crawler => throw new NotImplementedException();
20+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Web;
5+
6+
public class HttpBrowserCapabilitiesWrapper : HttpBrowserCapabilitiesBase
7+
{
8+
private readonly HttpBrowserCapabilities _capabilities;
9+
10+
public HttpBrowserCapabilitiesWrapper(HttpBrowserCapabilities capabilities)
11+
{
12+
_capabilities = capabilities;
13+
}
14+
15+
public override string? Browser => _capabilities.Browser;
16+
17+
public override int MajorVersion => _capabilities.MajorVersion;
18+
19+
public override double MinorVersion => _capabilities.MinorVersion;
20+
21+
public override string? Platform => _capabilities.Platform;
22+
23+
public override string? Version => _capabilities.Version;
24+
25+
public override bool Crawler => _capabilities.Crawler;
26+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Web;
5+
6+
public class HttpException : SystemException
7+
{
8+
public HttpException()
9+
{
10+
}
11+
12+
public HttpException(string message) : base(message)
13+
{
14+
}
15+
16+
public HttpException(string message, Exception innerException)
17+
: base(message, innerException)
18+
{
19+
}
20+
}

src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequest.cs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
using System.Net;
1010
using System.Security.Principal;
1111
using System.Text;
12+
using System.Web.Configuration;
1213
using Microsoft.AspNetCore.Http.Extensions;
1314
using Microsoft.AspNetCore.Http.Features;
1415
using Microsoft.AspNetCore.Http.Headers;
1516
using Microsoft.AspNetCore.SystemWebAdapters;
1617
using Microsoft.AspNetCore.SystemWebAdapters.Internal;
18+
using Microsoft.Extensions.DependencyInjection;
1719
using Microsoft.Net.Http.Headers;
1820

1921
namespace System.Web
@@ -114,14 +116,7 @@ public NameValueCollection ServerVariables
114116
{
115117
if (_serverVariables is null)
116118
{
117-
if (_request.HttpContext.Features.Get<IServerVariablesFeature>() is IServerVariablesFeature feature)
118-
{
119-
_serverVariables = feature.ToNameValueCollection();
120-
}
121-
else
122-
{
123-
throw new PlatformNotSupportedException("IServerVariablesFeature is not available.");
124-
}
119+
_serverVariables = _request.HttpContext.Features.GetRequired<IServerVariablesFeature>().ToNameValueCollection();
125120
}
126121

127122
return _serverVariables;
@@ -170,7 +165,25 @@ public bool IsLocal
170165

171166
public string? UserHostName => _request.HttpContext.Connection.RemoteIpAddress?.ToString();
172167

173-
public HttpBrowserCapabilities Browser => _browser ??= new();
168+
public HttpBrowserCapabilities Browser
169+
{
170+
get
171+
{
172+
if (_browser is null)
173+
{
174+
var factory = _request.HttpContext.RequestServices.GetService<BrowserCapabilitiesFactory>();
175+
176+
if (factory is null)
177+
{
178+
throw new InvalidOperationException("Browser capabilities requires AddSystemWebAdapters() to be called on service collection");
179+
}
180+
181+
_browser = new(factory, _request.Headers.UserAgent);
182+
}
183+
184+
return _browser;
185+
}
186+
}
174187

175188
public byte[] BinaryRead(int count)
176189
{

0 commit comments

Comments
 (0)