diff --git a/src/Volo.Abp/Volo/Abp/AbpKernelModule.cs b/src/Volo.Abp/Volo/Abp/AbpKernelModule.cs
index 8b772e5597..702155a3e1 100644
--- a/src/Volo.Abp/Volo/Abp/AbpKernelModule.cs
+++ b/src/Volo.Abp/Volo/Abp/AbpKernelModule.cs
@@ -5,6 +5,7 @@ using Volo.Abp.Modularity;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Reflection;
using Volo.Abp.Uow;
+using Volo.Abp.Validation;
namespace Volo.Abp
{
@@ -13,6 +14,7 @@ namespace Volo.Abp
public override void PreConfigureServices(IServiceCollection services)
{
services.OnRegistred(UnitOfWorkInterceptorRegistrar.RegisterIfNeeded);
+ services.OnRegistred(ValidationInterceptorRegistrar.RegisterIfNeeded);
//TODO: Move to a dedicated class
services.OnExposing(context =>
diff --git a/src/Volo.Abp/Volo/Abp/Application/Services/ApplicationService.cs b/src/Volo.Abp/Volo/Abp/Application/Services/ApplicationService.cs
index 684d90b8af..e5e7012aa6 100644
--- a/src/Volo.Abp/Volo/Abp/Application/Services/ApplicationService.cs
+++ b/src/Volo.Abp/Volo/Abp/Application/Services/ApplicationService.cs
@@ -1,9 +1,17 @@
+using System.Collections.Generic;
+using Volo.Abp.Aspects;
+
namespace Volo.Abp.Application.Services
{
- public abstract class ApplicationService : AbpServiceBase, IApplicationService
+ public abstract class ApplicationService : AbpServiceBase, IApplicationService, IAvoidDuplicateCrossCuttingConcerns
{
public static string[] CommonPostfixes { get; set; } = { "AppService", "ApplicationService", "Service" };
+ ///
+ /// Gets the applied cross cutting concerns.
+ ///
+ public List AppliedCrossCuttingConcerns { get; } = new List();
+
/* Will be added when implemented
- AbpSession
- ...
diff --git a/src/Volo.Abp/Volo/Abp/Aspects/AbpCrossCuttingConcerns.cs b/src/Volo.Abp/Volo/Abp/Aspects/AbpCrossCuttingConcerns.cs
new file mode 100644
index 0000000000..87319241e7
--- /dev/null
+++ b/src/Volo.Abp/Volo/Abp/Aspects/AbpCrossCuttingConcerns.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using JetBrains.Annotations;
+
+namespace Volo.Abp.Aspects
+{
+ internal static class AbpCrossCuttingConcerns
+ {
+ public const string Auditing = "AbpAuditing";
+ public const string Validation = "AbpValidation";
+ public const string UnitOfWork = "AbpUnitOfWork";
+ public const string Authorization = "AbpAuthorization";
+
+ public static void AddApplied(object obj, params string[] concerns)
+ {
+ if (concerns.IsNullOrEmpty())
+ {
+ throw new ArgumentNullException(nameof(concerns), $"{nameof(concerns)} should be provided!");
+ }
+
+ (obj as IAvoidDuplicateCrossCuttingConcerns)?.AppliedCrossCuttingConcerns.AddRange(concerns);
+ }
+
+ public static void RemoveApplied(object obj, params string[] concerns)
+ {
+ if (concerns.IsNullOrEmpty())
+ {
+ throw new ArgumentNullException(nameof(concerns), $"{nameof(concerns)} should be provided!");
+ }
+
+ var crossCuttingEnabledObj = obj as IAvoidDuplicateCrossCuttingConcerns;
+ if (crossCuttingEnabledObj == null)
+ {
+ return;
+ }
+
+ foreach (var concern in concerns)
+ {
+ crossCuttingEnabledObj.AppliedCrossCuttingConcerns.RemoveAll(c => c == concern);
+ }
+ }
+
+ public static bool IsApplied([NotNull] object obj, [NotNull] string concern)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+
+ if (concern == null)
+ {
+ throw new ArgumentNullException(nameof(concern));
+ }
+
+ return (obj as IAvoidDuplicateCrossCuttingConcerns)?.AppliedCrossCuttingConcerns.Contains(concern) ?? false;
+ }
+
+ public static IDisposable Applying(object obj, params string[] concerns)
+ {
+ AddApplied(obj, concerns);
+ return new DisposeAction(() =>
+ {
+ RemoveApplied(obj, concerns);
+ });
+ }
+
+ public static string[] GetApplieds(object obj)
+ {
+ var crossCuttingEnabledObj = obj as IAvoidDuplicateCrossCuttingConcerns;
+ if (crossCuttingEnabledObj == null)
+ {
+ return new string[0];
+ }
+
+ return crossCuttingEnabledObj.AppliedCrossCuttingConcerns.ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp/Volo/Abp/Aspects/IAvoidDuplicateCrossCuttingConcerns.cs b/src/Volo.Abp/Volo/Abp/Aspects/IAvoidDuplicateCrossCuttingConcerns.cs
new file mode 100644
index 0000000000..2eceab9f80
--- /dev/null
+++ b/src/Volo.Abp/Volo/Abp/Aspects/IAvoidDuplicateCrossCuttingConcerns.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace Volo.Abp.Aspects
+{
+ public interface IAvoidDuplicateCrossCuttingConcerns
+ {
+ List AppliedCrossCuttingConcerns { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp/Volo/Abp/Validation/DisableValidationAttribute.cs b/src/Volo.Abp/Volo/Abp/Validation/DisableValidationAttribute.cs
new file mode 100644
index 0000000000..6c78cdb8d8
--- /dev/null
+++ b/src/Volo.Abp/Volo/Abp/Validation/DisableValidationAttribute.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Volo.Abp.Validation
+{
+ ///
+ /// Can be added to a method to disable auto validation.
+ ///
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property)]
+ public class DisableValidationAttribute : Attribute
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp/Volo/Abp/Validation/EnableValidationAttribute.cs b/src/Volo.Abp/Volo/Abp/Validation/EnableValidationAttribute.cs
new file mode 100644
index 0000000000..7c13e36b6b
--- /dev/null
+++ b/src/Volo.Abp/Volo/Abp/Validation/EnableValidationAttribute.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Volo.Abp.Validation
+{
+ ///
+ /// Can be added to a method to enable auto validation if validation is disabled for it's class.
+ ///
+ [AttributeUsage(AttributeTargets.Method)]
+ public class EnableValidationAttribute : Attribute
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp/Volo/Abp/Validation/IShouldNormalize.cs b/src/Volo.Abp/Volo/Abp/Validation/IShouldNormalize.cs
new file mode 100644
index 0000000000..bf543bba6e
--- /dev/null
+++ b/src/Volo.Abp/Volo/Abp/Validation/IShouldNormalize.cs
@@ -0,0 +1,13 @@
+namespace Volo.Abp.Validation
+{
+ ///
+ /// This interface is used to normalize inputs before method execution.
+ ///
+ public interface IShouldNormalize
+ {
+ ///
+ /// This method is called lastly before method execution (after validation if exists).
+ ///
+ void Normalize();
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp/Volo/Abp/Validation/IValidationConfiguration.cs b/src/Volo.Abp/Volo/Abp/Validation/IValidationConfiguration.cs
new file mode 100644
index 0000000000..7040be55bc
--- /dev/null
+++ b/src/Volo.Abp/Volo/Abp/Validation/IValidationConfiguration.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+
+namespace Volo.Abp.Validation
+{
+ public class AbpValidationOptions
+ {
+ public List IgnoredTypes { get; }
+
+ public AbpValidationOptions()
+ {
+ IgnoredTypes = new List();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp/Volo/Abp/Validation/MethodInvocationValidator.cs b/src/Volo.Abp/Volo/Abp/Validation/MethodInvocationValidator.cs
new file mode 100644
index 0000000000..fa2a887fe6
--- /dev/null
+++ b/src/Volo.Abp/Volo/Abp/Validation/MethodInvocationValidator.cs
@@ -0,0 +1,250 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Reflection;
+using Microsoft.Extensions.Options;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Reflection;
+
+namespace Volo.Abp.Validation
+{
+ ///
+ /// This class is used to validate a method call (invocation) for method arguments.
+ ///
+ public class MethodInvocationValidator : ITransientDependency
+ {
+ private const int MaxRecursiveParameterValidationDepth = 8;
+
+ protected MethodInfo Method { get; private set; }
+ protected object[] ParameterValues { get; private set; }
+ protected ParameterInfo[] Parameters { get; private set; }
+ protected List ValidationErrors { get; }
+ protected List ObjectsToBeNormalized { get; }
+
+ private readonly AbpValidationOptions _options;
+
+ ///
+ /// Creates a new instance.
+ ///
+ public MethodInvocationValidator(IOptions options)
+ {
+ _options = options.Value;
+
+ ValidationErrors = new List();
+ ObjectsToBeNormalized = new List();
+ }
+
+ /// Method to be validated
+ /// List of arguments those are used to call the .
+ public virtual void Initialize(MethodInfo method, object[] parameterValues)
+ {
+ Check.NotNull(method, nameof(method));
+ Check.NotNull(parameterValues, nameof(parameterValues));
+
+ Method = method;
+ ParameterValues = parameterValues;
+ Parameters = method.GetParameters();
+ }
+
+ ///
+ /// Validates the method invocation.
+ ///
+ public void Validate()
+ {
+ CheckInitialized();
+
+ if (Parameters.IsNullOrEmpty())
+ {
+ return;
+ }
+
+ if (!Method.IsPublic)
+ {
+ return;
+ }
+
+ if (IsValidationDisabled())
+ {
+ return;
+ }
+
+ if (Parameters.Length != ParameterValues.Length)
+ {
+ throw new Exception("Method parameter count does not match with argument count!");
+ }
+
+ if (ValidationErrors.Any() && HasSingleNullArgument())
+ {
+ ThrowValidationError();
+ }
+
+ for (var i = 0; i < Parameters.Length; i++)
+ {
+ ValidateMethodParameter(Parameters[i], ParameterValues[i]);
+ }
+
+ if (ValidationErrors.Any())
+ {
+ ThrowValidationError();
+ }
+
+ foreach (var objectToBeNormalized in ObjectsToBeNormalized)
+ {
+ objectToBeNormalized.Normalize();
+ }
+ }
+
+ protected virtual void CheckInitialized()
+ {
+ if (Method == null)
+ {
+ throw new AbpException("This object has not been initialized. Call Initialize method first.");
+ }
+ }
+
+ protected virtual bool IsValidationDisabled()
+ {
+ if (Method.IsDefined(typeof(EnableValidationAttribute), true))
+ {
+ return false;
+ }
+
+ return ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(Method) != null;
+ }
+
+ protected virtual bool HasSingleNullArgument()
+ {
+ return Parameters.Length == 1 && ParameterValues[0] == null;
+ }
+
+ protected virtual void ThrowValidationError()
+ {
+ throw new AbpValidationException(
+ "Method arguments are not valid! See ValidationErrors for details.",
+ ValidationErrors
+ );
+ }
+
+ ///
+ /// Validates given parameter for given value.
+ ///
+ /// Parameter of the method to validate
+ /// Value to validate
+ protected virtual void ValidateMethodParameter(ParameterInfo parameterInfo, object parameterValue)
+ {
+ if (parameterValue == null)
+ {
+ if (!parameterInfo.IsOptional &&
+ !parameterInfo.IsOut &&
+ !TypeHelper.IsPrimitiveExtendedIncludingNullable(parameterInfo.ParameterType, includeEnums: true))
+ {
+ ValidationErrors.Add(new ValidationResult(parameterInfo.Name + " is null!", new[] { parameterInfo.Name }));
+ }
+
+ return;
+ }
+
+ ValidateObjectRecursively(parameterValue, 1);
+ }
+
+ protected virtual void ValidateObjectRecursively(object validatingObject, int currentDepth)
+ {
+ if (currentDepth > MaxRecursiveParameterValidationDepth)
+ {
+ return;
+ }
+
+ if (validatingObject == null)
+ {
+ return;
+ }
+
+ SetDataAnnotationAttributeErrors(validatingObject);
+
+ //Validate items of enumerable
+ if (validatingObject is IEnumerable && !(validatingObject is IQueryable))
+ {
+ foreach (var item in (validatingObject as IEnumerable))
+ {
+ ValidateObjectRecursively(item, currentDepth + 1);
+ }
+ }
+
+ //Add list to be normalized later
+ if (validatingObject is IShouldNormalize)
+ {
+ ObjectsToBeNormalized.Add(validatingObject as IShouldNormalize);
+ }
+
+ //Do not recursively validate for enumerable objects
+ if (validatingObject is IEnumerable)
+ {
+ return;
+ }
+
+ var validatingObjectType = validatingObject.GetType();
+
+ //Do not recursively validate for primitive objects
+ if (TypeHelper.IsPrimitiveExtendedIncludingNullable(validatingObjectType))
+ {
+ return;
+ }
+
+ if (_options.IgnoredTypes.Any(t => t.IsInstanceOfType(validatingObject)))
+ {
+ return;
+ }
+
+ var properties = TypeDescriptor.GetProperties(validatingObject).Cast();
+ foreach (var property in properties)
+ {
+ if (property.Attributes.OfType().Any())
+ {
+ continue;
+ }
+
+ ValidateObjectRecursively(property.GetValue(validatingObject), currentDepth + 1);
+ }
+ }
+
+ ///
+ /// Checks all properties for DataAnnotations attributes.
+ ///
+ protected virtual void SetDataAnnotationAttributeErrors(object validatingObject)
+ {
+ var properties = TypeDescriptor.GetProperties(validatingObject).Cast();
+ foreach (var property in properties)
+ {
+ var validationAttributes = property.Attributes.OfType().ToArray();
+ if (validationAttributes.IsNullOrEmpty())
+ {
+ continue;
+ }
+
+ var validationContext = new ValidationContext(validatingObject)
+ {
+ DisplayName = property.DisplayName,
+ MemberName = property.Name
+ };
+
+ foreach (var attribute in validationAttributes)
+ {
+ var result = attribute.GetValidationResult(property.GetValue(validatingObject), validationContext);
+ if (result != null)
+ {
+ ValidationErrors.Add(result);
+ }
+ }
+ }
+
+ if (validatingObject is IValidatableObject)
+ {
+ var results = (validatingObject as IValidatableObject).Validate(new ValidationContext(validatingObject));
+ ValidationErrors.AddRange(results);
+ }
+ }
+ }
+}
diff --git a/src/Volo.Abp/Volo/Abp/Validation/ValidationInterceptor.cs b/src/Volo.Abp/Volo/Abp/Validation/ValidationInterceptor.cs
new file mode 100644
index 0000000000..bb81a02bba
--- /dev/null
+++ b/src/Volo.Abp/Volo/Abp/Validation/ValidationInterceptor.cs
@@ -0,0 +1,36 @@
+using System;
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Aspects;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.DynamicProxy;
+
+namespace Volo.Abp.Validation
+{
+ public class ValidationInterceptor : AbpInterceptor, ITransientDependency
+ {
+ private readonly IServiceProvider _serviceProvider;
+
+ public ValidationInterceptor(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ }
+
+ public override void Intercept(IAbpMethodInvocation invocation)
+ {
+ if (AbpCrossCuttingConcerns.IsApplied(invocation.TargetObject, AbpCrossCuttingConcerns.Validation))
+ {
+ invocation.Proceed();
+ return;
+ }
+
+ using (var scope = _serviceProvider.CreateScope())
+ {
+ var validator = scope.ServiceProvider.GetRequiredService();
+ validator.Initialize(invocation.Method, invocation.Arguments);
+ validator.Validate();
+ }
+
+ invocation.Proceed();
+ }
+ }
+}
diff --git a/src/Volo.Abp/Volo/Abp/Validation/ValidationInterceptorRegistrar.cs b/src/Volo.Abp/Volo/Abp/Validation/ValidationInterceptorRegistrar.cs
new file mode 100644
index 0000000000..85e6c07bb6
--- /dev/null
+++ b/src/Volo.Abp/Volo/Abp/Validation/ValidationInterceptorRegistrar.cs
@@ -0,0 +1,17 @@
+using Volo.Abp.Application.Services;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Validation
+{
+ public static class ValidationInterceptorRegistrar
+ {
+ public static void RegisterIfNeeded(IOnServiceRegistredContext context)
+ {
+ if (typeof(IApplicationService).IsAssignableFrom(context.ImplementationType))
+ {
+ //TODO: Notice that it may add the interceptor more than one for every exposed service type!?
+ context.Interceptors.Add();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Volo.Abp.Tests/Volo.Abp.Tests.csproj b/test/Volo.Abp.Tests/Volo.Abp.Tests.csproj
index 70f48c988b..6f578e566d 100644
--- a/test/Volo.Abp.Tests/Volo.Abp.Tests.csproj
+++ b/test/Volo.Abp.Tests/Volo.Abp.Tests.csproj
@@ -16,6 +16,7 @@
+
diff --git a/test/Volo.Abp.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs b/test/Volo.Abp.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs
new file mode 100644
index 0000000000..a72c84d54b
--- /dev/null
+++ b/test/Volo.Abp.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs
@@ -0,0 +1,352 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using Microsoft.Extensions.DependencyInjection;
+using Shouldly;
+using Volo.Abp.Application.Services;
+using Volo.Abp.Autofac;
+using Volo.Abp.Modularity;
+using Volo.Abp.TestBase;
+using Xunit;
+
+namespace Volo.Abp.Validation
+{
+ public class ApplicationService_Validation_Tests : AbpIntegratedTest
+ {
+ private readonly IMyAppService _myAppService;
+
+ public ApplicationService_Validation_Tests()
+ {
+ _myAppService = ServiceProvider.GetRequiredService();
+ }
+
+ protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
+ {
+ options.UseAutofac();
+ }
+
+ [Fact]
+ public void Should_Work_Proper_With_Right_Inputs()
+ {
+ var output = _myAppService.MyMethod(new MyMethodInput { MyStringValue = "test" });
+ output.Result.ShouldBe(42);
+ }
+
+ [Fact]
+ public void Should_Not_Work_With_Wrong_Inputs()
+ {
+ Assert.Throws(() => _myAppService.MyMethod(new MyMethodInput())); //MyStringValue is not supplied!
+ Assert.Throws(() => _myAppService.MyMethod(new MyMethodInput { MyStringValue = "a" })); //MyStringValue's min length should be 3!
+ }
+
+ [Fact]
+ public void Should_Work_With_Right_Nesned_Inputs()
+ {
+ var output = _myAppService.MyMethod2(new MyMethod2Input
+ {
+ MyStringValue2 = "test 1",
+ Input1 = new MyMethodInput { MyStringValue = "test 2" },
+ DateTimeValue = DateTime.Now
+ });
+ output.Result.ShouldBe(42);
+ }
+
+ [Fact]
+ public void Should_Not_Work_With_Wrong_Nesned_Inputs_1()
+ {
+ Assert.Throws(() =>
+ _myAppService.MyMethod2(new MyMethod2Input
+ {
+ MyStringValue2 = "test 1",
+ Input1 = new MyMethodInput() //MyStringValue is not set
+ }));
+ }
+
+ [Fact]
+ public void Should_Not_Work_With_Wrong_Nesned_Inputs_2()
+ {
+ Assert.Throws(() =>
+ _myAppService.MyMethod2(new MyMethod2Input //Input1 is not set
+ {
+ MyStringValue2 = "test 1"
+ }));
+ }
+
+ [Fact]
+ public void Should_Not_Work_With_Wrong_List_Input_1()
+ {
+ Assert.Throws(() =>
+ _myAppService.MyMethod3(
+ new MyMethod3Input
+ {
+ MyStringValue2 = "test 1",
+ ListItems = new List
+ {
+ new MyClassInList {ValueInList = null}
+ }
+ }));
+ }
+
+ [Fact]
+ public void Should_Not_Work_With_Wrong_Array_Input_1()
+ {
+ Assert.Throws(() =>
+ _myAppService.MyMethod3(
+ new MyMethod3Input
+ {
+ MyStringValue2 = "test 1",
+ ArrayItems = new[]
+ {
+ new MyClassInList {ValueInList = null}
+ }
+ }));
+ }
+
+ [Fact]
+ public void Should_Not_Work_If_Array_Is_Null()
+ {
+ Assert.Throws(() =>
+ _myAppService.MyMethod4(new MyMethod4Input()) //ArrayItems is null!
+ );
+ }
+
+ [Fact]
+ public void Should_Work_If_Array_Is_Null_But_DisabledValidation_For_Method()
+ {
+ _myAppService.MyMethod4_2(new MyMethod4Input());
+ }
+
+ [Fact]
+ public void Should_Work_If_Array_Is_Null_But_DisabledValidation_For_Property()
+ {
+ _myAppService.MyMethod5(new MyMethod5Input());
+ }
+
+ [Fact]
+ public void Should_Use_IValidatableObject()
+ {
+ Assert.Throws(() =>
+ {
+ _myAppService.MyMethod6(new MyMethod6Input
+ {
+ MyStringValue = "test value" //MyIntValue has not set!
+ });
+ });
+ }
+
+ [Fact]
+ public void Should_Normalize_Nested_Dtos()
+ {
+ var input = new MyMethod7Input
+ {
+ Inner = new MyMethod7Input.MyMethod7InputInner
+ {
+ Value = 10
+ }
+ };
+
+ _myAppService.MyMethod7(input);
+
+ input.Inner.Value.ShouldBe(12);
+ }
+
+ [Fact]
+ public void Should_Stop_Recursive_Validation_In_A_Constant_Depth()
+ {
+ _myAppService.MyMethod8(new MyClassWithRecursiveReference { Value = "42" }).Result.ShouldBe(42);
+ }
+
+ [Fact]
+ public void Should_Allow_Null_For_Nullable_Enums()
+ {
+ _myAppService.MyMethodWithNullableEnum(null);
+ }
+
+ [DependsOn(typeof(AbpAutofacModule))]
+ public class ValidationTestModule : AbpModule
+ {
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ services.AddType();
+ }
+ }
+
+ public interface IMyAppService : IApplicationService
+ {
+ MyMethodOutput MyMethod(MyMethodInput input);
+ MyMethodOutput MyMethod2(MyMethod2Input input);
+ MyMethodOutput MyMethod3(MyMethod3Input input);
+ MyMethodOutput MyMethod4(MyMethod4Input input);
+ MyMethodOutput MyMethod4_2(MyMethod4Input input);
+ MyMethodOutput MyMethod5(MyMethod5Input input);
+ MyMethodOutput MyMethod6(MyMethod6Input input);
+ MyMethodOutput MyMethod7(MyMethod7Input input);
+ MyMethodOutput MyMethod8(MyClassWithRecursiveReference input);
+ void MyMethodWithNullableEnum(MyEnum? value);
+ }
+
+ public class MyAppService : ApplicationService, IMyAppService
+ {
+ public MyMethodOutput MyMethod(MyMethodInput input)
+ {
+ return new MyMethodOutput { Result = 42 };
+ }
+
+ public MyMethodOutput MyMethod2(MyMethod2Input input)
+ {
+ return new MyMethodOutput { Result = 42 };
+ }
+
+ public MyMethodOutput MyMethod3(MyMethod3Input input)
+ {
+ return new MyMethodOutput { Result = 42 };
+ }
+
+ public MyMethodOutput MyMethod4(MyMethod4Input input)
+ {
+ return new MyMethodOutput { Result = 42 };
+ }
+
+ [DisableValidation]
+ public MyMethodOutput MyMethod4_2(MyMethod4Input input)
+ {
+ return new MyMethodOutput { Result = 42 };
+ }
+
+ public MyMethodOutput MyMethod5(MyMethod5Input input)
+ {
+ return new MyMethodOutput { Result = 42 };
+ }
+
+ public MyMethodOutput MyMethod6(MyMethod6Input input)
+ {
+ return new MyMethodOutput { Result = 42 };
+ }
+
+ public MyMethodOutput MyMethod7(MyMethod7Input input)
+ {
+ return new MyMethodOutput { Result = 42 };
+ }
+
+ public MyMethodOutput MyMethod8(MyClassWithRecursiveReference input)
+ {
+ return new MyMethodOutput { Result = 42 };
+ }
+
+ public void MyMethodWithNullableEnum(MyEnum? value)
+ {
+
+ }
+ }
+
+ public class MyMethodInput
+ {
+ [Required]
+ [MinLength(3)]
+ public string MyStringValue { get; set; }
+ }
+
+ public class MyMethod2Input
+ {
+ [Required]
+ [MinLength(2)]
+ public string MyStringValue2 { get; set; }
+
+ public DateTime DateTimeValue { get; set; }
+
+ [Required]
+ public MyMethodInput Input1 { get; set; }
+ }
+
+ public class MyMethod3Input
+ {
+ [Required]
+ [MinLength(2)]
+ public string MyStringValue2 { get; set; }
+
+ public List ListItems { get; set; }
+
+ public MyClassInList[] ArrayItems { get; set; }
+ }
+
+ public class MyMethod4Input
+ {
+ [Required]
+ public MyClassInList[] ArrayItems { get; set; }
+ }
+
+ public class MyMethod5Input
+ {
+ [DisableValidation]
+ public MyClassInList[] ArrayItems { get; set; }
+ }
+
+ public class MyMethod6Input : IValidatableObject
+ {
+ [Required]
+ [MinLength(2)]
+ public string MyStringValue { get; set; }
+
+ public int MyIntValue { get; set; }
+
+ public IEnumerable Validate(ValidationContext validationContext)
+ {
+ if (MyIntValue < 18)
+ {
+ yield return new ValidationResult("MyIntValue must be greather than or equal to 18");
+ }
+ }
+ }
+
+ public class MyMethod7Input : IShouldNormalize
+ {
+ public MyMethod7InputInner Inner { get; set; }
+
+ public void Normalize()
+ {
+ Inner.Value++;
+ }
+
+ public class MyMethod7InputInner : IShouldNormalize
+ {
+ public int Value { get; set; }
+
+ public void Normalize()
+ {
+ Value++;
+ }
+ }
+ }
+
+ public class MyClassInList
+ {
+ [Required]
+ [MinLength(3)]
+ public string ValueInList { get; set; }
+ }
+
+ public class MyMethodOutput
+ {
+ public int Result { get; set; }
+ }
+
+ public class MyClassWithRecursiveReference
+ {
+ public MyClassWithRecursiveReference Reference { get; }
+
+ [Required]
+ public string Value { get; set; }
+
+ public MyClassWithRecursiveReference()
+ {
+ Reference = this;
+ }
+ }
+
+ public enum MyEnum
+ {
+ Value1,
+ Value2
+ }
+ }
+}
\ No newline at end of file