129 lines
5.1 KiB
C#
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;
|
|
}
|
|
}
|
|
|