Files
Backend-Api/ServiceHost/Conventions/ParameterBindingConvention.cs

129 lines
5.1 KiB
C#

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) ||
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) ||
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;
}
}