From d1907e342e656b57b8482d82e8ed109bd90dfb11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 30 Aug 2017 16:02:29 +0300 Subject: [PATCH] Added application service convention for AspNet Core. --- .../AspNetCore/Mvc/AbpAppServiceConvention.cs | 275 ++++++++++++++++++ .../AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs | 6 + .../AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs | 32 ++ .../Mvc/AbpControllerAssemblySetting.cs | 34 +++ .../AbpControllerAssemblySettingBuilder.cs | 27 ++ .../AspNetCore/Mvc/AbpMvcOptionsExtensions.cs | 35 +++ .../Mvc/ControllerAssemblySettingList.cs | 16 + .../IAbpControllerAssemblySettingBuilder.cs | 12 + .../Reflection/AbpMemberInfoExtensions.cs | 52 ++++ .../Services/ApplicationService.cs | 2 + .../Services/AsyncCrudAppService.cs | 2 +- .../Application/Services/CrudAppService.cs | 2 +- .../Services/CrudAppServiceBase.cs | 3 +- .../Services/IAsyncCrudAppService.cs | 3 +- .../Application/Services/ICrudAppService.cs | 3 +- src/Volo.Abp/Volo/Abp/Http/HttpVerbHelper.cs | 44 +++ .../Volo/Abp/Http/RemoteServiceAttribute.cs | 82 ++++++ .../Volo/Abp/Reflection/ReflectionHelper.cs | 21 ++ .../Volo/Abp/Reflection/TypeHelper.cs | 67 +++++ .../TestApp/Application/IPersonAppService.cs | 2 +- .../TestApp/Application/PersonAppService.cs | 4 +- 21 files changed, 713 insertions(+), 11 deletions(-) create mode 100644 src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConvention.cs create mode 100644 src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs create mode 100644 src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySetting.cs create mode 100644 src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySettingBuilder.cs create mode 100644 src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs create mode 100644 src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ControllerAssemblySettingList.cs create mode 100644 src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IAbpControllerAssemblySettingBuilder.cs create mode 100644 src/Volo.Abp/System/Reflection/AbpMemberInfoExtensions.cs create mode 100644 src/Volo.Abp/Volo/Abp/Http/HttpVerbHelper.cs create mode 100644 src/Volo.Abp/Volo/Abp/Http/RemoteServiceAttribute.cs create mode 100644 src/Volo.Abp/Volo/Abp/Reflection/TypeHelper.cs diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConvention.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConvention.cs new file mode 100644 index 0000000000..c3e6ce1740 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConvention.cs @@ -0,0 +1,275 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.Application.Services; +using Volo.Abp.Http; +using Volo.Abp.Reflection; + +namespace Volo.Abp.AspNetCore.Mvc +{ + public class AbpAppServiceConvention : IApplicationModelConvention + { + private readonly Lazy _configuration; + + public AbpAppServiceConvention(IServiceCollection services) + { + _configuration = new Lazy(() => + { + return services + .GetSingletonInstance() + .ServiceProvider + .GetRequiredService>() + .Value; + }, true); + } + + public void Apply(ApplicationModel application) + { + foreach (var controller in application.Controllers) + { + var type = controller.ControllerType.AsType(); + var configuration = GetControllerSettingOrNull(type); + + if (typeof(IApplicationService).GetTypeInfo().IsAssignableFrom(type)) + { + controller.ControllerName = controller.ControllerName.RemovePostFix(ApplicationService.CommonPostfixes); + configuration?.ControllerModelConfigurer(controller); + + ConfigureArea(controller, configuration); + ConfigureRemoteService(controller, configuration); + } + else + { + var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault(type.GetTypeInfo()); + if (remoteServiceAtt != null && remoteServiceAtt.IsEnabledFor(type)) + { + ConfigureRemoteService(controller, configuration); + } + } + } + } + + private void ConfigureArea(ControllerModel controller, [CanBeNull] AbpControllerAssemblySetting configuration) + { + if (configuration == null) + { + return; + } + + if (controller.RouteValues.ContainsKey("area")) + { + return; + } + + controller.RouteValues["area"] = configuration.ModuleName; + } + + private void ConfigureRemoteService(ControllerModel controller, [CanBeNull] AbpControllerAssemblySetting configuration) + { + ConfigureApiExplorer(controller); + ConfigureSelector(controller, configuration); + ConfigureParameters(controller); + } + + private void ConfigureParameters(ControllerModel controller) + { + foreach (var action in controller.Actions) + { + foreach (var prm in action.Parameters) + { + if (prm.BindingInfo != null) + { + continue; + } + + if (!TypeHelper.IsPrimitiveExtendedIncludingNullable(prm.ParameterInfo.ParameterType)) + { + if (CanUseFormBodyBinding(action, prm)) + { + prm.BindingInfo = BindingInfo.GetBindingInfo(new[] { new FromBodyAttribute() }); + } + } + } + } + } + + private bool CanUseFormBodyBinding(ActionModel action, ParameterModel parameter) + { + if (_configuration.Value.FormBodyBindingIgnoredTypes.Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType))) + { + return false; + } + + foreach (var selector in action.Selectors) + { + if (selector.ActionConstraints == null) + { + continue; + } + + foreach (var actionConstraint in selector.ActionConstraints) + { + var httpMethodActionConstraint = actionConstraint as HttpMethodActionConstraint; + if (httpMethodActionConstraint == null) + { + continue; + } + + if (httpMethodActionConstraint.HttpMethods.All(hm => hm.IsIn("GET", "DELETE", "TRACE", "HEAD"))) + { + return false; + } + } + } + + return true; + } + + private void ConfigureApiExplorer(ControllerModel controller) + { + if (controller.ApiExplorer.GroupName.IsNullOrEmpty()) + { + controller.ApiExplorer.GroupName = controller.ControllerName; + } + + if (controller.ApiExplorer.IsVisible == null) + { + var controllerType = controller.ControllerType.AsType(); + var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault(controllerType.GetTypeInfo()); + if (remoteServiceAtt != null) + { + controller.ApiExplorer.IsVisible = + remoteServiceAtt.IsEnabledFor(controllerType) && + remoteServiceAtt.IsMetadataEnabledFor(controllerType); + } + else + { + controller.ApiExplorer.IsVisible = true; + } + } + + foreach (var action in controller.Actions) + { + ConfigureApiExplorer(action); + } + } + + private void ConfigureApiExplorer(ActionModel action) + { + if (action.ApiExplorer.IsVisible == null) + { + var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault(action.ActionMethod); + if (remoteServiceAtt != null) + { + action.ApiExplorer.IsVisible = + remoteServiceAtt.IsEnabledFor(action.ActionMethod) && + remoteServiceAtt.IsMetadataEnabledFor(action.ActionMethod); + } + } + } + + private void ConfigureSelector(ControllerModel controller, [CanBeNull] AbpControllerAssemblySetting configuration) + { + RemoveEmptySelectors(controller.Selectors); + + if (controller.Selectors.Any(selector => selector.AttributeRouteModel != null)) + { + return; + } + + var moduleName = GetModuleNameOrDefault(controller.ControllerType.AsType()); + + foreach (var action in controller.Actions) + { + ConfigureSelector(moduleName, controller.ControllerName, action, configuration); + } + } + + private void ConfigureSelector(string moduleName, string controllerName, ActionModel action, [CanBeNull] AbpControllerAssemblySetting configuration) + { + RemoveEmptySelectors(action.Selectors); + + if (!action.Selectors.Any()) + { + AddAbpServiceSelector(moduleName, controllerName, action, configuration); + } + else + { + NormalizeSelectorRoutes(moduleName, controllerName, action); + } + } + + private void AddAbpServiceSelector(string moduleName, string controllerName, ActionModel action, [CanBeNull] AbpControllerAssemblySetting configuration) + { + var abpServiceSelectorModel = new SelectorModel + { + AttributeRouteModel = CreateAbpServiceAttributeRouteModel(moduleName, controllerName, action) + }; + + var verb = configuration?.UseConventionalHttpVerbs == true + ? HttpVerbHelper.GetConventionalVerbForMethodName(action.ActionName) + : HttpVerbHelper.DefaultHttpVerb; + + abpServiceSelectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { verb })); + + action.Selectors.Add(abpServiceSelectorModel); + } + + private static void NormalizeSelectorRoutes(string moduleName, string controllerName, ActionModel action) + { + foreach (var selector in action.Selectors) + { + if (selector.AttributeRouteModel == null) + { + selector.AttributeRouteModel = CreateAbpServiceAttributeRouteModel( + moduleName, + controllerName, + action + ); + } + } + } + + private string GetModuleNameOrDefault(Type controllerType) + { + return GetControllerSettingOrNull(controllerType)?.ModuleName ?? + AbpControllerAssemblySetting.DefaultServiceModuleName; + } + + [CanBeNull] + private AbpControllerAssemblySetting GetControllerSettingOrNull(Type controllerType) + { + return _configuration.Value.ControllerAssemblySettings.GetSettingOrNull(controllerType); + } + + private static AttributeRouteModel CreateAbpServiceAttributeRouteModel(string moduleName, string controllerName, ActionModel action) + { + return new AttributeRouteModel( + new RouteAttribute( + $"api/services/{moduleName}/{controllerName}/{action.ActionName}" + ) + ); + } + + private static void RemoveEmptySelectors(IList selectors) + { + selectors + .Where(IsEmptySelector) + .ToList() + .ForEach(s => selectors.Remove(s)); + } + + private static bool IsEmptySelector(SelectorModel selector) + { + return selector.AttributeRouteModel == null && selector.ActionConstraints.IsNullOrEmpty(); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs index dd6bc14a65..3f0ce600da 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Options; using Volo.Abp.AspNetCore.EmbeddedFiles; using Volo.Abp.DependencyInjection; using Volo.Abp.Modularity; +using Microsoft.AspNetCore.Mvc; namespace Volo.Abp.AspNetCore.Mvc { @@ -34,6 +35,11 @@ namespace Volo.Abp.AspNetCore.Mvc ) ) ); + + services.Configure(mvcOptions => + { + mvcOptions.AddAbp(services); + }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs new file mode 100644 index 0000000000..d302985911 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Volo.Abp.AspNetCore.Mvc +{ + public class AbpAspNetCoreMvcOptions + { + //TODO: Group into a class since they are related. + public ControllerAssemblySettingList ControllerAssemblySettings { get; } + public List FormBodyBindingIgnoredTypes { get; } + + public AbpAspNetCoreMvcOptions() + { + FormBodyBindingIgnoredTypes = new List + { + typeof(IFormFile) + }; + } + + public AbpControllerAssemblySettingBuilder CreateControllersForAppServices( + Assembly assembly, + string moduleName = AbpControllerAssemblySetting.DefaultServiceModuleName, + bool useConventionalHttpVerbs = true) + { + var setting = new AbpControllerAssemblySetting(moduleName, assembly, useConventionalHttpVerbs); + ControllerAssemblySettings.Add(setting); + return new AbpControllerAssemblySettingBuilder(setting); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySetting.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySetting.cs new file mode 100644 index 0000000000..ed89e7ae9b --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySetting.cs @@ -0,0 +1,34 @@ +using System; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApplicationModels; + +namespace Volo.Abp.AspNetCore.Mvc +{ + public class AbpControllerAssemblySetting + { + /// + /// "app". + /// + public const string DefaultServiceModuleName = "app"; + + public string ModuleName { get; } + + public Assembly Assembly { get; } + + public bool UseConventionalHttpVerbs { get; } + + public Func TypePredicate { get; set; } + + public Action ControllerModelConfigurer { get; set; } + + public AbpControllerAssemblySetting(string moduleName, Assembly assembly, bool useConventionalHttpVerbs) + { + ModuleName = moduleName; + Assembly = assembly; + UseConventionalHttpVerbs = useConventionalHttpVerbs; + + TypePredicate = type => true; + ControllerModelConfigurer = controller => { }; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySettingBuilder.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySettingBuilder.cs new file mode 100644 index 0000000000..4799870728 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySettingBuilder.cs @@ -0,0 +1,27 @@ +using System; +using Microsoft.AspNetCore.Mvc.ApplicationModels; + +namespace Volo.Abp.AspNetCore.Mvc +{ + public class AbpControllerAssemblySettingBuilder : IAbpControllerAssemblySettingBuilder + { + private readonly AbpControllerAssemblySetting _setting; + + public AbpControllerAssemblySettingBuilder(AbpControllerAssemblySetting setting) + { + _setting = setting; + } + + public AbpControllerAssemblySettingBuilder Where(Func predicate) + { + _setting.TypePredicate = predicate; + return this; + } + + public AbpControllerAssemblySettingBuilder ConfigureControllerModel(Action configurer) + { + _setting.ControllerModelConfigurer = configurer; + return this; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs new file mode 100644 index 0000000000..5b3b1fcd75 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs @@ -0,0 +1,35 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Mvc +{ + internal static class AbpMvcOptionsExtensions + { + public static void AddAbp(this MvcOptions options, IServiceCollection services) + { + AddConventions(options, services); + AddFilters(options); + AddModelBinders(options); + } + + private static void AddConventions(MvcOptions options, IServiceCollection services) + { + options.Conventions.Add(new AbpAppServiceConvention(services)); + } + + private static void AddFilters(MvcOptions options) + { + //options.Filters.AddService(typeof(AbpAuthorizationFilter)); + //options.Filters.AddService(typeof(AbpAuditActionFilter)); + //options.Filters.AddService(typeof(AbpValidationActionFilter)); + //options.Filters.AddService(typeof(AbpUowActionFilter)); + //options.Filters.AddService(typeof(AbpExceptionFilter)); + //options.Filters.AddService(typeof(AbpResultFilter)); + } + + private static void AddModelBinders(MvcOptions options) + { + //options.ModelBinderProviders.Add(new AbpDateTimeModelBinderProvider()); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ControllerAssemblySettingList.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ControllerAssemblySettingList.cs new file mode 100644 index 0000000000..36112a9ccc --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ControllerAssemblySettingList.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; + +namespace Volo.Abp.AspNetCore.Mvc +{ + public class ControllerAssemblySettingList : List + { + [CanBeNull] + public AbpControllerAssemblySetting GetSettingOrNull(Type controllerType) + { + return this.FirstOrDefault(controllerSetting => controllerSetting.Assembly == controllerType.GetAssembly()); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IAbpControllerAssemblySettingBuilder.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IAbpControllerAssemblySettingBuilder.cs new file mode 100644 index 0000000000..ccd85b3b9a --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IAbpControllerAssemblySettingBuilder.cs @@ -0,0 +1,12 @@ +using System; +using Microsoft.AspNetCore.Mvc.ApplicationModels; + +namespace Volo.Abp.AspNetCore.Mvc +{ + public interface IAbpControllerAssemblySettingBuilder + { + AbpControllerAssemblySettingBuilder Where(Func predicate); + + AbpControllerAssemblySettingBuilder ConfigureControllerModel(Action configurer); + } +} \ No newline at end of file diff --git a/src/Volo.Abp/System/Reflection/AbpMemberInfoExtensions.cs b/src/Volo.Abp/System/Reflection/AbpMemberInfoExtensions.cs new file mode 100644 index 0000000000..2c3c9d33fb --- /dev/null +++ b/src/Volo.Abp/System/Reflection/AbpMemberInfoExtensions.cs @@ -0,0 +1,52 @@ +using System.Linq; + +namespace System.Reflection +{ + /// + /// Extensions to . + /// + public static class AbpMemberInfoExtensions + { + /// + /// Gets a single attribute for a member. + /// + /// Type of the attribute + /// The member that will be checked for the attribute + /// Include inherited attributes + /// Returns the attribute object if found. Returns null if not found. + public static TAttribute GetSingleAttributeOrNull(this MemberInfo memberInfo, bool inherit = true) + where TAttribute : Attribute + { + if (memberInfo == null) + { + throw new ArgumentNullException(nameof(memberInfo)); + } + + var attrs = memberInfo.GetCustomAttributes(typeof(TAttribute), inherit).ToArray(); + if (attrs.Length > 0) + { + return (TAttribute)attrs[0]; + } + + return default(TAttribute); + } + + + public static TAttribute GetSingleAttributeOfTypeOrBaseTypesOrNull(this Type type, bool inherit = true) + where TAttribute : Attribute + { + var attr = type.GetTypeInfo().GetSingleAttributeOrNull(); + if (attr != null) + { + return attr; + } + + if (type.GetTypeInfo().BaseType == null) + { + return null; + } + + return type.GetTypeInfo().BaseType.GetSingleAttributeOfTypeOrBaseTypesOrNull(inherit); + } + } +} diff --git a/src/Volo.Abp/Volo/Abp/Application/Services/ApplicationService.cs b/src/Volo.Abp/Volo/Abp/Application/Services/ApplicationService.cs index 457361f46c..c8a61c1f07 100644 --- a/src/Volo.Abp/Volo/Abp/Application/Services/ApplicationService.cs +++ b/src/Volo.Abp/Volo/Abp/Application/Services/ApplicationService.cs @@ -2,6 +2,8 @@ namespace Volo.Abp.Application.Services { public abstract class ApplicationService : AbpServiceBase, IApplicationService { + public static string[] CommonPostfixes = { "AppService", "ApplicationService", "Service" }; + /* Will be added when implemented - AbpSession - ... diff --git a/src/Volo.Abp/Volo/Abp/Application/Services/AsyncCrudAppService.cs b/src/Volo.Abp/Volo/Abp/Application/Services/AsyncCrudAppService.cs index f69a11fe51..a38ef5b5ba 100644 --- a/src/Volo.Abp/Volo/Abp/Application/Services/AsyncCrudAppService.cs +++ b/src/Volo.Abp/Volo/Abp/Application/Services/AsyncCrudAppService.cs @@ -6,7 +6,7 @@ using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; using Volo.Abp.Linq; -namespace Abp.Application.Services +namespace Volo.Abp.Application.Services { public abstract class AsyncCrudAppService : AsyncCrudAppService diff --git a/src/Volo.Abp/Volo/Abp/Application/Services/CrudAppService.cs b/src/Volo.Abp/Volo/Abp/Application/Services/CrudAppService.cs index 22fc061b27..65e0d36ccc 100644 --- a/src/Volo.Abp/Volo/Abp/Application/Services/CrudAppService.cs +++ b/src/Volo.Abp/Volo/Abp/Application/Services/CrudAppService.cs @@ -4,7 +4,7 @@ using Volo.Abp.Application.Dtos; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; -namespace Abp.Application.Services +namespace Volo.Abp.Application.Services { public abstract class CrudAppService : CrudAppService diff --git a/src/Volo.Abp/Volo/Abp/Application/Services/CrudAppServiceBase.cs b/src/Volo.Abp/Volo/Abp/Application/Services/CrudAppServiceBase.cs index 0a40e55e5b..24f89410ba 100644 --- a/src/Volo.Abp/Volo/Abp/Application/Services/CrudAppServiceBase.cs +++ b/src/Volo.Abp/Volo/Abp/Application/Services/CrudAppServiceBase.cs @@ -2,11 +2,10 @@ using System.Linq; using System.Linq.Dynamic.Core; using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; -namespace Abp.Application.Services +namespace Volo.Abp.Application.Services { /// /// This is a common base class for CrudAppService and AsyncCrudAppService classes. diff --git a/src/Volo.Abp/Volo/Abp/Application/Services/IAsyncCrudAppService.cs b/src/Volo.Abp/Volo/Abp/Application/Services/IAsyncCrudAppService.cs index 9bfe3add43..1fad8eb211 100644 --- a/src/Volo.Abp/Volo/Abp/Application/Services/IAsyncCrudAppService.cs +++ b/src/Volo.Abp/Volo/Abp/Application/Services/IAsyncCrudAppService.cs @@ -1,9 +1,8 @@ using System; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; -namespace Abp.Application.Services +namespace Volo.Abp.Application.Services { public interface IAsyncCrudAppService : IAsyncCrudAppService diff --git a/src/Volo.Abp/Volo/Abp/Application/Services/ICrudAppService.cs b/src/Volo.Abp/Volo/Abp/Application/Services/ICrudAppService.cs index 77c4692f66..8839392fcf 100644 --- a/src/Volo.Abp/Volo/Abp/Application/Services/ICrudAppService.cs +++ b/src/Volo.Abp/Volo/Abp/Application/Services/ICrudAppService.cs @@ -1,8 +1,7 @@ using System; using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; -namespace Abp.Application.Services +namespace Volo.Abp.Application.Services { public interface ICrudAppService : ICrudAppService diff --git a/src/Volo.Abp/Volo/Abp/Http/HttpVerbHelper.cs b/src/Volo.Abp/Volo/Abp/Http/HttpVerbHelper.cs new file mode 100644 index 0000000000..0652d76b59 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Http/HttpVerbHelper.cs @@ -0,0 +1,44 @@ +using System; + +namespace Volo.Abp.Http +{ + public class HttpVerbHelper //TODO: Internal? + { + public const string DefaultHttpVerb = "POST"; + + public static string GetConventionalVerbForMethodName(string methodName) + { + if (methodName.StartsWith("Get", StringComparison.OrdinalIgnoreCase)) + { + return "GET"; + } + + if (methodName.StartsWith("Put", StringComparison.OrdinalIgnoreCase) || + methodName.StartsWith("Update", StringComparison.OrdinalIgnoreCase)) + { + return "PUT"; + } + + if (methodName.StartsWith("Delete", StringComparison.OrdinalIgnoreCase) || + methodName.StartsWith("Remove", StringComparison.OrdinalIgnoreCase)) + { + return "DELETE"; + } + + if (methodName.StartsWith("Patch", StringComparison.OrdinalIgnoreCase)) + { + return "PATCH"; + } + + if (methodName.StartsWith("Post", StringComparison.OrdinalIgnoreCase) || + methodName.StartsWith("Create", StringComparison.OrdinalIgnoreCase) || + methodName.StartsWith("Insert", StringComparison.OrdinalIgnoreCase)) + { + return "POST"; + } + + //Default + return DefaultHttpVerb; + } + } +} diff --git a/src/Volo.Abp/Volo/Abp/Http/RemoteServiceAttribute.cs b/src/Volo.Abp/Volo/Abp/Http/RemoteServiceAttribute.cs new file mode 100644 index 0000000000..14d3e73684 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Http/RemoteServiceAttribute.cs @@ -0,0 +1,82 @@ +using System; +using System.Reflection; + +namespace Volo.Abp.Http +{ + [Serializable] + [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Method)] + public class RemoteServiceAttribute : Attribute + { + /// + /// Default: true. + /// + public bool IsEnabled { get; set; } + + /// + /// Default: true. + /// + public bool IsMetadataEnabled { get; set; } + + public RemoteServiceAttribute(bool isEnabled = true) + { + IsEnabled = isEnabled; + IsMetadataEnabled = true; + } + + public virtual bool IsEnabledFor(Type type) + { + return IsEnabled; + } + + public virtual bool IsEnabledFor(MethodInfo method) + { + return IsEnabled; + } + + public virtual bool IsMetadataEnabledFor(Type type) + { + return IsMetadataEnabled; + } + + public virtual bool IsMetadataEnabledFor(MethodInfo method) + { + return IsMetadataEnabled; + } + + public static bool IsExplicitlyEnabledFor(Type type) + { + var remoteServiceAttr = type.GetTypeInfo().GetSingleAttributeOrNull(); + return remoteServiceAttr != null && remoteServiceAttr.IsEnabledFor(type); + } + + public static bool IsExplicitlyDisabledFor(Type type) + { + var remoteServiceAttr = type.GetTypeInfo().GetSingleAttributeOrNull(); + return remoteServiceAttr != null && !remoteServiceAttr.IsEnabledFor(type); + } + + public static bool IsMetadataExplicitlyEnabledFor(Type type) + { + var remoteServiceAttr = type.GetTypeInfo().GetSingleAttributeOrNull(); + return remoteServiceAttr != null && remoteServiceAttr.IsMetadataEnabledFor(type); + } + + public static bool IsMetadataExplicitlyDisabledFor(Type type) + { + var remoteServiceAttr = type.GetTypeInfo().GetSingleAttributeOrNull(); + return remoteServiceAttr != null && !remoteServiceAttr.IsMetadataEnabledFor(type); + } + + public static bool IsMetadataExplicitlyDisabledFor(MethodInfo method) + { + var remoteServiceAttr = method.GetSingleAttributeOrNull(); + return remoteServiceAttr != null && !remoteServiceAttr.IsMetadataEnabledFor(method); + } + + public static bool IsMetadataExplicitlyEnabledFor(MethodInfo method) + { + var remoteServiceAttr = method.GetSingleAttributeOrNull(); + return remoteServiceAttr != null && remoteServiceAttr.IsMetadataEnabledFor(method); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Reflection/ReflectionHelper.cs b/src/Volo.Abp/Volo/Abp/Reflection/ReflectionHelper.cs index 7175f6fe7c..ce0eeae715 100644 --- a/src/Volo.Abp/Volo/Abp/Reflection/ReflectionHelper.cs +++ b/src/Volo.Abp/Volo/Abp/Reflection/ReflectionHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; namespace Volo.Abp.Reflection @@ -70,5 +71,25 @@ namespace Volo.Abp.Reflection AddImplementedGenericTypes(result, givenTypeInfo.BaseType, genericType); } + + /// + /// Tries to gets an of attribute defined for a class member and it's declaring type including inherited attributes. + /// Returns default value if it's not declared at all. + /// + /// Type of the attribute + /// MemberInfo + /// Default value (null as default) + /// Inherit attribute from base classes + public static TAttribute GetSingleAttributeOrDefault(MemberInfo memberInfo, TAttribute defaultValue = default(TAttribute), bool inherit = true) + where TAttribute : Attribute + { + //Get attribute on the member + if (memberInfo.IsDefined(typeof(TAttribute), inherit)) + { + return memberInfo.GetCustomAttributes(typeof(TAttribute), inherit).Cast().First(); + } + + return defaultValue; + } } } diff --git a/src/Volo.Abp/Volo/Abp/Reflection/TypeHelper.cs b/src/Volo.Abp/Volo/Abp/Reflection/TypeHelper.cs new file mode 100644 index 0000000000..863cd12e96 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Reflection/TypeHelper.cs @@ -0,0 +1,67 @@ +using System; +using System.Reflection; + +namespace Volo.Abp.Reflection +{ + /// + /// Some simple type-checking methods used internally. + /// + public static class TypeHelper + { + public static bool IsFunc(object obj) + { + if (obj == null) + { + return false; + } + + var type = obj.GetType(); + if (!type.GetTypeInfo().IsGenericType) + { + return false; + } + + return type.GetGenericTypeDefinition() == typeof(Func<>); + } + + public static bool IsFunc(object obj) + { + return obj != null && obj.GetType() == typeof(Func); + } + + public static bool IsPrimitiveExtendedIncludingNullable(Type type, bool includeEnums = false) + { + if (IsPrimitiveExtended(type, includeEnums)) + { + return true; + } + + if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + return IsPrimitiveExtended(type.GenericTypeArguments[0], includeEnums); + } + + return false; + } + + private static bool IsPrimitiveExtended(Type type, bool includeEnums) + { + if (type.GetTypeInfo().IsPrimitive) + { + return true; + } + + if (includeEnums && type.GetTypeInfo().IsEnum) + { + return true; + } + + return type == typeof (string) || + type == typeof (decimal) || + type == typeof (DateTime) || + type == typeof (DateTimeOffset) || + type == typeof (TimeSpan) || + type == typeof (Guid); + } + } +} diff --git a/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPersonAppService.cs b/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPersonAppService.cs index c4d3041f33..203aeaba8f 100644 --- a/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPersonAppService.cs +++ b/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPersonAppService.cs @@ -1,4 +1,4 @@ -using Abp.Application.Services; +using Volo.Abp.Application.Services; namespace Volo.Abp.TestApp.Application { diff --git a/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PersonAppService.cs b/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PersonAppService.cs index cd98f195db..d738173ade 100644 --- a/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PersonAppService.cs +++ b/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PersonAppService.cs @@ -1,6 +1,6 @@ -using Abp.Application.Services; -using Volo.Abp.TestApp.Domain; +using Volo.Abp.TestApp.Domain; using Volo.Abp.Domain.Repositories; +using Volo.Abp.Application.Services; namespace Volo.Abp.TestApp.Application {