FileShare/FileShare-Services/Core/ServiceRequestBinder.cs

62 lines
2.4 KiB
C#
Raw Permalink Normal View History

using System.Text.Json;
using System.Text.Json.Serialization;
2026-05-22 14:29:22 +08:00
namespace FileShare_Services.Core
{
/// <summary>
/// Binds unified endpoint request models from JSON bodies or query parameters.
/// </summary>
internal static class ServiceRequestBinder
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
{
NumberHandling = JsonNumberHandling.AllowReadingFromString,
};
/// <summary>
/// Bind a JSON request body. Empty bodies are treated as an empty JSON object.
/// </summary>
public static T BindBody<T>(ServiceEndpointContext context)
{
var json = string.IsNullOrWhiteSpace(context.Body) ? "{}" : context.Body;
return Deserialize<T>(json, "body");
}
/// <summary>
/// Bind route and query parameters to a request DTO.
/// </summary>
public static T BindQuery<T>(ServiceEndpointContext context)
{
var values = new Dictionary<string, string>(context.Query, StringComparer.OrdinalIgnoreCase);
foreach (var routeValue in context.RouteValues)
{
values[routeValue.Key] = routeValue.Value;
}
var json = JsonSerializer.Serialize(values, JsonOptions);
return Deserialize<T>(json, "query");
}
/// <summary>
/// 将 JSON 反序列化为目标类型,反序列化失败或结果为 null 时抛出 <see cref="ArgumentException"/>。
/// </summary>
/// <typeparam name="T">目标类型。</typeparam>
/// <param name="json">JSON 字符串。</param>
/// <param name="source">来源标识body 或 query用于异常消息。</param>
/// <returns>反序列化后的实例。</returns>
/// <exception cref="ArgumentException">JSON 无法绑定到目标类型时抛出。</exception>
private static T Deserialize<T>(string json, string source)
{
try
{
return JsonSerializer.Deserialize<T>(json, JsonOptions)
?? throw new ArgumentException($"Request {source} cannot be bound to {typeof(T).Name}.");
}
catch (JsonException ex)
{
throw new ArgumentException($"Request {source} cannot be bound to {typeof(T).Name}.", ex);
}
}
}
}