diff --git a/ServiceHost/Conventions/ParameterBindingConvention.cs b/ServiceHost/Conventions/ParameterBindingConvention.cs new file mode 100644 index 00000000..ccc0fa73 --- /dev/null +++ b/ServiceHost/Conventions/ParameterBindingConvention.cs @@ -0,0 +1,126 @@ +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)) + 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; + } + + /// + /// چک می‌کند که آیا 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; + } +} + diff --git a/ServiceHost/Program.cs b/ServiceHost/Program.cs index 40ca19b2..4378ec2c 100644 --- a/ServiceHost/Program.cs +++ b/ServiceHost/Program.cs @@ -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()); });