Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- @azure Rule - Use Azure Best Practices: When generating code for Azure, running terminal commands for Azure, or performing operations related to Azure, invoke your `get_azure_best_practices` tool if available.
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
</PropertyGroup>

<PropertyGroup Label="Project Defaults">
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
</PropertyGroup>

<PropertyGroup Label="C#">
<LangVersion>12.0</LangVersion>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
Expand Down
6 changes: 3 additions & 3 deletions global.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"sdk": {
"version": "9.0.100"
}
"sdk": {
"version": "10.0.100-preview.2.25164.34"
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright © myCSharp.de - all rights reserved

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;

namespace MyCSharp.HttpClientHints.AspNetCore;

Expand All @@ -9,15 +10,28 @@ namespace MyCSharp.HttpClientHints.AspNetCore;
/// </summary>
public static class HttpClientHintsHttpContextExtensions
{
/// <summary>
/// The cache key used to store the client hints in the HttpContext.Items dictionary.
/// </summary>
private const string ClientHintsCacheKey = "__HttpClientHints";

/// <summary>
/// Retrieves the <see cref="HttpClientHints"/> from the current HTTP context.
/// </summary>
/// <param name="context">The HTTP context containing the request headers.</param>
/// <returns>An instance of <see cref="HttpClientHints"/> populated with the relevant header values.</returns>
public static HttpClientHints GetClientHints(this HttpContext context)
{
IHeaderDictionary headers = context.Request.Headers;
return headers.GetClientHints();
// Check if client hints are already cached for this request
if (context.Items.TryGetValue(ClientHintsCacheKey, out var cached) && cached is HttpClientHints hints)
{
return hints;
}

// Create and cache new client hints
var newHints = context.Request.Headers.GetClientHints();
context.Items[ClientHintsCacheKey] = newHints;
return newHints;
}

/// <summary>
Expand All @@ -27,25 +41,36 @@ public static HttpClientHints GetClientHints(this HttpContext context)
/// <returns>An instance of <see cref="HttpClientHints"/> populated with the relevant header values.</returns>
public static HttpClientHints GetClientHints(this IHeaderDictionary headers)
{
// user agent
string? userAgent = headers["User-Agent"].FirstOrDefault();
string? ua = headers["Sec-CH-UA"].FirstOrDefault();
// User Agent
headers.TryGetValue("User-Agent", out StringValues userAgentValues);
string? userAgent = userAgentValues.Count > 0 ? userAgentValues[0] : null;

headers.TryGetValue("Sec-CH-UA", out StringValues uaValues);
string? ua = uaValues.Count > 0 ? uaValues[0] : null;

// platform
string? platform = headers["Sec-CH-UA-Platform"].FirstOrDefault();
string? platformVersion = headers["Sec-CH-UA-Platform-Version"].FirstOrDefault();
// Platform
headers.TryGetValue("Sec-CH-UA-Platform", out StringValues platformValues);
string? platform = platformValues.Count > 0 ? platformValues[0] : null;

headers.TryGetValue("Sec-CH-UA-Platform-Version", out StringValues platformVersionValues);
string? platformVersion = platformVersionValues.Count > 0 ? platformVersionValues[0] : null;

// architecture
string? architecture = headers["Sec-CH-UA-Arch"].FirstOrDefault();
// Architecture
headers.TryGetValue("Sec-CH-UA-Arch", out StringValues architectureValues);
string? architecture = architectureValues.Count > 0 ? architectureValues[0] : null;

// other
string? fullVersionList = headers["Sec-CH-UA-Full-Version-List"].FirstOrDefault();
// Other
headers.TryGetValue("Sec-CH-UA-Full-Version-List", out StringValues fullVersionListValues);
string? fullVersionList = fullVersionListValues.Count > 0 ? fullVersionListValues[0] : null;

// device
string? model = headers["Sec-CH-UA-Model"].FirstOrDefault();
bool? mobile = HttpClientHintsInterpreter.IsMobile(headers["Sec-CH-UA-Mobile"].FirstOrDefault());
// Device
headers.TryGetValue("Sec-CH-UA-Model", out StringValues modelValues);
string? model = modelValues.Count > 0 ? modelValues[0] : null;

headers.TryGetValue("Sec-CH-UA-Mobile", out StringValues mobileValues);
bool? mobile = HttpClientHintsInterpreter.IsMobile(mobileValues.Count > 0 ? mobileValues[0] : null);

// return the HttpClientHints record
// Return the HttpClientHints record
return new(userAgent, platform, platformVersion, architecture, model, fullVersionList, ua, mobile, headers);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,32 @@
/// </summary>
/// <value>The name of the response header as a <see cref="string"/>.</value>
/// <remarks>These settings are set by <see cref="HttpClientHintsRegistration"/>. Do not set these values manually.</remarks>
public required string ResponseHeader { get; set; }
public required string ResponseHeader
{
get;
set
{
field = value;
HasResponseHeaders = string.IsNullOrEmpty(value) is false;
}
}

public bool HasResponseHeaders { get; internal set; }

Check failure on line 25 in src/MyCSharp.HttpClientHints.AspNetCore/HttpClientHintsMiddlewareConfig.cs

View workflow job for this annotation

GitHub Actions / build / build

Missing XML comment for publicly visible type or member 'HttpClientHintsMiddlewareConfig.HasResponseHeaders'

Check failure on line 25 in src/MyCSharp.HttpClientHints.AspNetCore/HttpClientHintsMiddlewareConfig.cs

View workflow job for this annotation

GitHub Actions / build / build

Missing XML comment for publicly visible type or member 'HttpClientHintsMiddlewareConfig.HasResponseHeaders'

Check failure on line 25 in src/MyCSharp.HttpClientHints.AspNetCore/HttpClientHintsMiddlewareConfig.cs

View workflow job for this annotation

GitHub Actions / build / build

Missing XML comment for publicly visible type or member 'HttpClientHintsMiddlewareConfig.HasResponseHeaders'

/// <summary>
/// Gets or sets the lifetime of the client hints in seconds.
/// </summary>
/// <value>A <see cref="string"/> containing an <see cref="int"/> representing the lifetime in seconds, or <c>null</c> if unspecified.</value>
/// <remarks>These settings are set by <see cref="HttpClientHintsRegistration"/>. Do not set these values manually.</remarks>
public string? LifeTime { get; set; }
public string? LifeTime
{
get;
set
{
field = value;
HasLifetime = string.IsNullOrEmpty(value) is false;
}
}

public bool HasLifetime { get; internal set; }

Check failure on line 42 in src/MyCSharp.HttpClientHints.AspNetCore/HttpClientHintsMiddlewareConfig.cs

View workflow job for this annotation

GitHub Actions / build / build

Missing XML comment for publicly visible type or member 'HttpClientHintsMiddlewareConfig.HasLifetime'

Check failure on line 42 in src/MyCSharp.HttpClientHints.AspNetCore/HttpClientHintsMiddlewareConfig.cs

View workflow job for this annotation

GitHub Actions / build / build

Missing XML comment for publicly visible type or member 'HttpClientHintsMiddlewareConfig.HasLifetime'

Check failure on line 42 in src/MyCSharp.HttpClientHints.AspNetCore/HttpClientHintsMiddlewareConfig.cs

View workflow job for this annotation

GitHub Actions / build / build

Missing XML comment for publicly visible type or member 'HttpClientHintsMiddlewareConfig.HasLifetime'
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
// Copyright © myCSharp.de - all rights reserved

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;

namespace MyCSharp.HttpClientHints.AspNetCore;

/// <summary>
/// Middleware for adding HTTP Client Hints headers to the response.
/// Initializes a new instance of the <see cref="HttpClientHintsRequestMiddleware"/> class.
/// </summary>
/// <param name="next">The next middleware in the request pipeline.</param>
/// <param name="options">The options for configuring the middleware.</param>
public class HttpClientHintsRequestMiddleware(RequestDelegate next, IOptions<HttpClientHintsMiddlewareConfig> options)

Check failure on line 6 in src/MyCSharp.HttpClientHints.AspNetCore/HttpClientHintsRequestMiddleware.cs

View workflow job for this annotation

GitHub Actions / build / build

Missing XML comment for publicly visible type or member 'HttpClientHintsRequestMiddleware.HttpClientHintsRequestMiddleware(RequestDelegate, IOptions<HttpClientHintsMiddlewareConfig>)'

Check failure on line 6 in src/MyCSharp.HttpClientHints.AspNetCore/HttpClientHintsRequestMiddleware.cs

View workflow job for this annotation

GitHub Actions / build / build

Missing XML comment for publicly visible type or member 'HttpClientHintsRequestMiddleware'

Check failure on line 6 in src/MyCSharp.HttpClientHints.AspNetCore/HttpClientHintsRequestMiddleware.cs

View workflow job for this annotation

GitHub Actions / build / build

Missing XML comment for publicly visible type or member 'HttpClientHintsRequestMiddleware.HttpClientHintsRequestMiddleware(RequestDelegate, IOptions<HttpClientHintsMiddlewareConfig>)'

Check failure on line 6 in src/MyCSharp.HttpClientHints.AspNetCore/HttpClientHintsRequestMiddleware.cs

View workflow job for this annotation

GitHub Actions / build / build

Missing XML comment for publicly visible type or member 'HttpClientHintsRequestMiddleware'
{
// Cache the options value and pre-compute conditions to avoid repeated checks
private readonly HttpClientHintsMiddlewareConfig _options = options.Value;

/// <summary>
Expand All @@ -22,12 +15,12 @@
/// <returns>A <see cref="Task"/> that represents the asynchronous operation.</returns>
public async Task InvokeAsync(HttpContext context)
{
// Add Client Hints headers to the response
if (string.IsNullOrEmpty(_options.ResponseHeader) is false)
if (_options.HasResponseHeaders)
{
// Set headers directly without additional checks
context.Response.Headers["Accept-CH"] = _options.ResponseHeader;

if (_options.LifeTime is not null)
if (_options.HasLifetime)
{
context.Response.Headers["Accept-CH-Lifetime"] = _options.LifeTime;
}
Expand Down
4 changes: 2 additions & 2 deletions src/MyCSharp.HttpClientHints/HttpClientHintsInterpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ public static class HttpClientHintsInterpreter
/// </returns>
public static bool? IsMobile(string? mobileHeaderValue)
{
if (mobileHeaderValue is "?1")
if (string.Equals(mobileHeaderValue, "?1", StringComparison.Ordinal))
{
return true;
}

if (mobileHeaderValue is "?0")
if (string.Equals(mobileHeaderValue, "?0", StringComparison.Ordinal))
{
return false;
}
Expand Down