feat: implement custom parameter binding convention for ASP.NET Core
This commit is contained in:
126
ServiceHost/Conventions/ParameterBindingConvention.cs
Normal file
126
ServiceHost/Conventions/ParameterBindingConvention.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace ServiceHost.Conventions;
|
||||
|
||||
/// <summary>
|
||||
/// Convention برای تنظیم خودکار binding source پارامترها
|
||||
/// GET: FromQuery (اگر attribute نداشته باشد)
|
||||
/// POST/PUT/PATCH/DELETE: FromBody برای انواع پیچیده (اگر attribute نداشته باشد)
|
||||
/// </summary>
|
||||
public class ParameterBindingConvention : IApplicationModelConvention
|
||||
{
|
||||
public void Apply(ApplicationModel application)
|
||||
{
|
||||
foreach (var controller in application.Controllers)
|
||||
{
|
||||
foreach (var action in controller.Actions)
|
||||
{
|
||||
foreach (var parameter in action.Parameters)
|
||||
{
|
||||
// فقط اگر کاربر خودش attribute مشخص کرده باشد، skip کن
|
||||
// نه اگر ASP.NET Core خودش binding source پیشفرض گذاشته
|
||||
if (HasExplicitBindingSourceAttribute(parameter))
|
||||
continue;
|
||||
|
||||
// اگر پارامتر از route میآید، skip کن
|
||||
if (IsRouteParameter(action, parameter))
|
||||
continue;
|
||||
|
||||
// برای GET از Query استفاده کن
|
||||
if (IsGetMethod(action))
|
||||
{
|
||||
parameter.BindingInfo = parameter.BindingInfo ?? new BindingInfo();
|
||||
parameter.BindingInfo.BindingSource = BindingSource.Query;
|
||||
}
|
||||
// برای POST, PUT, PATCH, DELETE از Body استفاده کن (فقط برای انواع پیچیده)
|
||||
else if (IsComplexType(parameter))
|
||||
{
|
||||
parameter.BindingInfo = parameter.BindingInfo ?? new BindingInfo();
|
||||
parameter.BindingInfo.BindingSource = BindingSource.Body;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// چک میکند که آیا کاربر خودش attribute binding source گذاشته یا نه
|
||||
/// (FromBody, FromQuery, FromForm, FromRoute, FromHeader)
|
||||
/// </summary>
|
||||
private static bool HasExplicitBindingSourceAttribute(ParameterModel parameter)
|
||||
{
|
||||
// فقط attribute های manual کاربر را چک کن، نه binding source پیشفرض ASP.NET Core
|
||||
var parameterInfo = parameter.ParameterInfo;
|
||||
return parameterInfo.GetCustomAttributes(false)
|
||||
.Any(a => a is IBindingSourceMetadata);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// چک میکند که آیا پارامتر جزو route template است یا نه
|
||||
/// مثلا: [HttpGet("{id}")] یا [Route("update/{id}")]
|
||||
/// </summary>
|
||||
private static bool IsRouteParameter(ActionModel action, ParameterModel parameter)
|
||||
{
|
||||
var parameterName = parameter.ParameterName;
|
||||
|
||||
// چک کردن selectors در سطح action
|
||||
foreach (var selector in action.Selectors)
|
||||
{
|
||||
if (selector.AttributeRouteModel?.Template != null)
|
||||
{
|
||||
if (selector.AttributeRouteModel.Template.Contains($"{{{parameterName}}}", StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// چک کردن selectors در سطح controller
|
||||
foreach (var selector in action.Controller.Selectors)
|
||||
{
|
||||
if (selector.AttributeRouteModel?.Template != null)
|
||||
{
|
||||
if (selector.AttributeRouteModel.Template.Contains($"{{{parameterName}}}", StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// چک میکند که آیا action از نوع GET است
|
||||
/// </summary>
|
||||
private static bool IsGetMethod(ActionModel action)
|
||||
{
|
||||
return action.Attributes.Any(a => a is HttpGetAttribute);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// چک میکند که آیا نوع پارامتر، نوع پیچیده است یا خیر
|
||||
/// انواع ساده: Primitive types, string, decimal, DateTime, Guid, Enum
|
||||
/// </summary>
|
||||
private static bool IsComplexType(ParameterModel parameter)
|
||||
{
|
||||
var type = parameter.ParameterInfo.ParameterType;
|
||||
|
||||
// Nullable types را handle کن
|
||||
var underlyingType = Nullable.GetUnderlyingType(type) ?? type;
|
||||
|
||||
// انواع ساده را چک کن
|
||||
if (underlyingType.IsPrimitive ||
|
||||
underlyingType == typeof(string) ||
|
||||
underlyingType == typeof(decimal) ||
|
||||
underlyingType == typeof(DateTime) ||
|
||||
underlyingType == typeof(DateTimeOffset) ||
|
||||
underlyingType == typeof(TimeSpan) ||
|
||||
underlyingType == typeof(Guid) ||
|
||||
underlyingType.IsEnum)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ using GozareshgirProgramManager.Application.Modules.Users.Commands.CreateUser;
|
||||
using GozareshgirProgramManager.Infrastructure;
|
||||
using GozareshgirProgramManager.Infrastructure.Persistence.Seed;
|
||||
using Microsoft.OpenApi;
|
||||
using ServiceHost.Conventions;
|
||||
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
@@ -170,7 +171,11 @@ builder.Services.AddAuthorization(options =>
|
||||
|
||||
// });
|
||||
|
||||
builder.Services.AddControllers().AddJsonOptions(options =>
|
||||
builder.Services.AddControllers(options =>
|
||||
{
|
||||
options.Conventions.Add(new ParameterBindingConvention());
|
||||
})
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user