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