using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ModelBinding; namespace ServiceHost.Conventions; /// /// Convention برای تنظیم خودکار binding source پارامترها /// GET: FromQuery (اگر attribute نداشته باشد) /// POST/PUT/PATCH/DELETE: FromBody برای انواع پیچیده (اگر attribute نداشته باشد) /// 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; } } } } } /// /// چک می‌کند که آیا کاربر خودش attribute binding source گذاشته یا نه /// (FromBody, FromQuery, FromForm, FromRoute, FromHeader) /// 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); } /// /// چک می‌کند که آیا پارامتر جزو route template است یا نه /// مثلا: [HttpGet("{id}")] یا [Route("update/{id}")] /// 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; } /// /// چک می‌کند که آیا action از نوع GET است /// private static bool IsGetMethod(ActionModel action) { return action.Attributes.Any(a => a is HttpGetAttribute); } /// /// چک می‌کند که آیا نوع پارامتر، نوع پیچیده است یا خیر /// انواع ساده: Primitive types, string, decimal, DateTime, Guid, Enum /// 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; } }