diff --git a/Volo.Abp.sln b/Volo.Abp.sln index ac4f6f1738..6cbfed4ab1 100644 --- a/Volo.Abp.sln +++ b/Volo.Abp.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26730.12 +VisualStudioVersion = 15.0.26730.16 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}" EndProject @@ -142,7 +142,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.EntityFrameworkCor EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleConsoleDemo", "test\SimpleConsoleDemo\SimpleConsoleDemo.csproj", "{2B48CF90-DBDB-469F-941C-5B5AECEEACE0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.EntityFrameworkCore.Tests.SecondContext", "test\Volo.Abp.EntityFrameworkCore.Tests.SecondContext\Volo.Abp.EntityFrameworkCore.Tests.SecondContext.csproj", "{127FC2BF-DC40-4370-B845-16088328264C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.EntityFrameworkCore.Tests.SecondContext", "test\Volo.Abp.EntityFrameworkCore.Tests.SecondContext\Volo.Abp.EntityFrameworkCore.Tests.SecondContext.csproj", "{127FC2BF-DC40-4370-B845-16088328264C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Mvc.Versioning.Tests", "test\Volo.Abp.AspNetCore.Mvc.Versioning.Tests\Volo.Abp.AspNetCore.Mvc.Versioning.Tests.csproj", "{A8C8B76D-0869-4C11-AC55-DB9DD115788E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -366,6 +368,10 @@ Global {127FC2BF-DC40-4370-B845-16088328264C}.Debug|Any CPU.Build.0 = Debug|Any CPU {127FC2BF-DC40-4370-B845-16088328264C}.Release|Any CPU.ActiveCfg = Release|Any CPU {127FC2BF-DC40-4370-B845-16088328264C}.Release|Any CPU.Build.0 = Release|Any CPU + {A8C8B76D-0869-4C11-AC55-DB9DD115788E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A8C8B76D-0869-4C11-AC55-DB9DD115788E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A8C8B76D-0869-4C11-AC55-DB9DD115788E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A8C8B76D-0869-4C11-AC55-DB9DD115788E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -434,6 +440,7 @@ Global {3AF7C7F5-6513-47D4-8DD0-6E1AF14568D8} = {37087D1B-3693-4E96-983D-A69F210BDE53} {2B48CF90-DBDB-469F-941C-5B5AECEEACE0} = {37087D1B-3693-4E96-983D-A69F210BDE53} {127FC2BF-DC40-4370-B845-16088328264C} = {37087D1B-3693-4E96-983D-A69F210BDE53} + {A8C8B76D-0869-4C11-AC55-DB9DD115788E} = {37087D1B-3693-4E96-983D-A69F210BDE53} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs b/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs index feaf44e07a..50c18af252 100644 --- a/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs +++ b/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs @@ -65,7 +65,7 @@ namespace AbpDesk.Web.Mvc services.Configure(options => { - options.AppServiceControllers.CreateFor(typeof(AbpDeskApplicationModule).Assembly); + options.ConventionalControllers.Create(typeof(AbpDeskApplicationModule).Assembly); }); } diff --git a/src/Volo.Abp.AspNetCore.Mvc/Microsoft/Extensions/DependencyInjection/AbpApiVersioningOptionsExtensions.cs b/src/Volo.Abp.AspNetCore.Mvc/Microsoft/Extensions/DependencyInjection/AbpApiVersioningOptionsExtensions.cs new file mode 100644 index 0000000000..c792b563d1 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Microsoft/Extensions/DependencyInjection/AbpApiVersioningOptionsExtensions.cs @@ -0,0 +1,73 @@ +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Versioning; +using Microsoft.AspNetCore.Mvc.Versioning.Conventions; +using Volo.Abp.ApiVersioning; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.Conventions; +using Volo.Abp.AspNetCore.Mvc.Versioning; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class AbpApiVersioningOptionsExtensions + { + public static void ConfigureAbp(this ApiVersioningOptions options, IServiceCollection services) + { + //TODO: Use new builder will be released with Api Versioning 2.1 instead of reflection! + + services.AddTransient(); + + services.Configure(op => + { + //TODO: Configuring api version should be done directly inside ConfigureAbp, + //TODO: not in a callback that will be called by MVC later! For that, we immediately need to controllerAssemblySettings + + foreach (var setting in op.ConventionalControllers.ConventionalControllerSettings) + { + if (setting.ApiVersionConfigurer == null) + { + ConfigureApiVersionsByConvention(options, setting); + } + else + { + setting.ApiVersionConfigurer.Invoke(options); + } + } + }); + } + + private static void ConfigureApiVersionsByConvention(ApiVersioningOptions options, ConventionalControllerSetting setting) + { + foreach (var controllerType in setting.ControllerTypes) + { + var controllerBuilder = typeof(ApiVersionConventionBuilder) + .GetMethod(nameof(ApiVersionConventionBuilder.Controller), + BindingFlags.Instance | BindingFlags.Public) + .MakeGenericMethod(controllerType) + .Invoke(options.Conventions, null); + + if (setting.ApiVersions.Any()) + { + foreach (var apiVersion in setting.ApiVersions) + { + typeof(ControllerApiVersionConventionBuilder<>) + .MakeGenericType(controllerType) + .GetMethod("HasApiVersion") + .Invoke(controllerBuilder, new object[] {apiVersion}); + } + } + else + { + if (!controllerType.IsDefined(typeof(ApiVersionAttribute), true)) + { + typeof(ControllerApiVersionConventionBuilder<>) + .MakeGenericType(controllerType) + .GetMethod("IsApiVersionNeutral") + .Invoke(controllerBuilder, null); + } + } + } + } + } +} diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj b/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj index 746c1b2bef..0eada12839 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceControllerFeatureProvider.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceControllerFeatureProvider.cs deleted file mode 100644 index 376268eef9..0000000000 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceControllerFeatureProvider.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Reflection; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Volo.Abp.Application.Services; -using Volo.Abp.Reflection; - -namespace Volo.Abp.AspNetCore.Mvc -{ - public class AbpAppServiceControllerFeatureProvider : ControllerFeatureProvider - { - private readonly IAbpApplication _application; - - public AbpAppServiceControllerFeatureProvider(IAbpApplication application) - { - _application = application; - } - - protected override bool IsController(TypeInfo typeInfo) - { - var type = typeInfo.AsType(); - - if (!typeof(IRemoteService).IsAssignableFrom(type) || - !typeInfo.IsPublic || typeInfo.IsAbstract || typeInfo.IsGenericType) - { - return false; - } - - var remoteServiceAttr = ReflectionHelper.GetSingleAttributeOrDefault(typeInfo); - - if (remoteServiceAttr != null && !remoteServiceAttr.IsEnabledFor(type)) - { - return false; - } - - //TODO: Move this to a lazy loaded field for efficiency. - var configuration = _application.ServiceProvider.GetRequiredService>().Value.AppServiceControllers.ControllerAssemblySettings.GetSettingOrNull(type); - return configuration != null && (configuration.TypePredicate == null || configuration.TypePredicate(type)); - } - } -} \ 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 072e523877..b06cefdcd1 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 @@ -12,10 +12,13 @@ using Volo.Abp.DependencyInjection; using Volo.Abp.Modularity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ViewComponents; using Microsoft.Extensions.DependencyInjection.Extensions; +using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.Http; +using Volo.Abp.Http.Modeling; namespace Volo.Abp.AspNetCore.Mvc { @@ -41,6 +44,21 @@ namespace Volo.Abp.AspNetCore.Mvc ) ) ); + + services.Configure(options => + { + options.IgnoredInterfaces.AddIfNotContains(typeof(IAsyncActionFilter)); + options.IgnoredInterfaces.AddIfNotContains(typeof(IFilterMetadata)); + options.IgnoredInterfaces.AddIfNotContains(typeof(IActionFilter)); + }); + + services.Configure(options => + { + options.ConventionalControllers.Create(typeof(AbpAspNetCoreMvcModule).Assembly, o => + { + o.RootPath = "abp"; + }); + }); } public override void PostConfigureServices(IServiceCollection services) @@ -60,7 +78,7 @@ namespace Volo.Abp.AspNetCore.Mvc var partManager = services.GetSingletonInstance(); var application = services.GetSingletonInstance(); - partManager.FeatureProviders.Add(new AbpAppServiceControllerFeatureProvider(application)); + partManager.FeatureProviders.Add(new AbpConventionalControllerFeatureProvider(application)); services.Configure(mvcOptions => { @@ -97,8 +115,8 @@ namespace Volo.Abp.AspNetCore.Mvc .ServiceProvider .GetRequiredService>() .Value - .AppServiceControllers - .ControllerAssemblySettings + .ConventionalControllers + .ConventionalControllerSettings .Select(s => s.Assembly) .Distinct(); 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 index 781824846a..6dec2fde14 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs @@ -1,12 +1,14 @@ -namespace Volo.Abp.AspNetCore.Mvc +using Volo.Abp.AspNetCore.Mvc.Conventions; + +namespace Volo.Abp.AspNetCore.Mvc { public class AbpAspNetCoreMvcOptions { - public AppServiceControllerOptions AppServiceControllers { get; } + public ConventionalControllerOptions ConventionalControllers { get; } public AbpAspNetCoreMvcOptions() { - AppServiceControllers = new AppServiceControllerOptions(); + ConventionalControllers = new ConventionalControllerOptions(); } } } \ 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 deleted file mode 100644 index cad6332995..0000000000 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySetting.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Reflection; -using JetBrains.Annotations; -using Microsoft.AspNetCore.Mvc.ApplicationModels; - -namespace Volo.Abp.AspNetCore.Mvc -{ - public class AbpControllerAssemblySetting - { - [NotNull] - public Assembly Assembly { get; } - - [NotNull] - public string RootPath { get; } - - [CanBeNull] - public Func TypePredicate { get; set; } - - [CanBeNull] - public Action ControllerModelConfigurer { get; set; } - - [CanBeNull] - public Func UrlControllerNameNormalizer { get; set; } - - [CanBeNull] - public Func UrlActionNameNormalizer { get; set; } - - public AbpControllerAssemblySetting([NotNull] Assembly assembly, [NotNull] string rootPath) - { - Check.NotNull(assembly, rootPath); - - Assembly = assembly; - RootPath = rootPath; - } - } -} \ 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 deleted file mode 100644 index ea45af6181..0000000000 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySettingBuilder.cs +++ /dev/null @@ -1,39 +0,0 @@ -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; - } - - public AbpControllerAssemblySettingBuilder NormalizeControllerNameInUrl(Func normalizer) - { - _setting.UrlControllerNameNormalizer = normalizer; - return this; - } - - public AbpControllerAssemblySettingBuilder NormalizeActionNameInUrl(Func normalizer) - { - _setting.UrlActionNameNormalizer = normalizer; - 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 index e0cb1c2232..b18d155573 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.ExceptionHandling; using Volo.Abp.AspNetCore.Mvc.Uow; using Volo.Abp.AspNetCore.Mvc.Validation; @@ -17,7 +18,7 @@ namespace Volo.Abp.AspNetCore.Mvc private static void AddConventions(MvcOptions options, IServiceCollection services) { - options.Conventions.Add(new AbpAppServiceConventionWrapper(services)); + options.Conventions.Add(new AbpServiceConventionWrapper(services)); } private static void AddFilters(MvcOptions options) diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs index 869f1ba136..19db653dc8 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs @@ -1,10 +1,11 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Application.Services; using Volo.Abp.Http.Modeling; namespace Volo.Abp.AspNetCore.Mvc.ApiExploring { - [Area("abp")] - public class AbpApiDefinitionController : AbpController + [Route("api/abp/api-definition")] + public class AbpApiDefinitionController : AbpController, IRemoteService { private readonly IApiDescriptionModelProvider _modelProvider; @@ -14,7 +15,6 @@ namespace Volo.Abp.AspNetCore.Mvc.ApiExploring } [HttpGet] - [Route("api/abp/api-description")] public ApplicationApiDescriptionModel Get() { return _modelProvider.CreateApiModel(); diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AppServiceControllerOptions.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AppServiceControllerOptions.cs deleted file mode 100644 index 8e118ae561..0000000000 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AppServiceControllerOptions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using Microsoft.AspNetCore.Http; -using Volo.Abp.Http.Modeling; - -namespace Volo.Abp.AspNetCore.Mvc -{ - public class AppServiceControllerOptions - { - public ControllerAssemblySettingList ControllerAssemblySettings { get; } - - public List FormBodyBindingIgnoredTypes { get; } - - public AppServiceControllerOptions() - { - ControllerAssemblySettings = new ControllerAssemblySettingList(); - - FormBodyBindingIgnoredTypes = new List - { - typeof(IFormFile) - }; - } - - public AbpControllerAssemblySettingBuilder CreateFor(Assembly assembly, string rootPath = ModuleApiDescriptionModel.DefaultRootPath) - { - var setting = new AbpControllerAssemblySetting(assembly, rootPath); - 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/AspNetCoreApiDescriptionModelProvider.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs index 6179f860c6..156e4114ae 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; +using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ApiExplorer; @@ -10,6 +12,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Volo.Abp.Application.Services; +using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.Utils; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Modeling; @@ -22,19 +25,24 @@ namespace Volo.Abp.AspNetCore.Mvc private readonly IApiDescriptionGroupCollectionProvider _descriptionProvider; private readonly AbpAspNetCoreMvcOptions _options; + private readonly ApiDescriptionModelOptions _modelOptions; public AspNetCoreApiDescriptionModelProvider( IApiDescriptionGroupCollectionProvider descriptionProvider, - IOptions options) + IOptions options, + IOptions modelOptions) { _descriptionProvider = descriptionProvider; _options = options.Value; + _modelOptions = modelOptions.Value; Logger = NullLogger.Instance; } public ApplicationApiDescriptionModel CreateApiModel() { + //TODO: Can cache the model? + var model = ApplicationApiDescriptionModel.Create(); foreach (var descriptionGroupItem in _descriptionProvider.ApiDescriptionGroups.Items) @@ -56,10 +64,11 @@ namespace Volo.Abp.AspNetCore.Mvc private void AddApiDescriptionToModel(ApiDescription apiDescription, ApplicationApiDescriptionModel model) { var controllerType = apiDescription.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.AsType(); + var setting = FindSetting(controllerType); - var moduleModel = model.GetOrAddModule(GetRootPath(controllerType)); + var moduleModel = model.GetOrAddModule(GetRootPath(controllerType, setting)); - var controllerModel = moduleModel.GetOrAddController(GetControllerName(apiDescription), controllerType); + var controllerModel = moduleModel.GetOrAddController(controllerType.FullName, CalculateControllerName(controllerType, setting), controllerType, _modelOptions.IgnoredInterfaces); var method = apiDescription.ActionDescriptor.GetMethodInfo(); @@ -70,20 +79,27 @@ namespace Volo.Abp.AspNetCore.Mvc return; } - var actionModel = controllerModel.AddAction(ActionApiDescriptionModel.Create( - method, + var actionModel = controllerModel.AddAction(uniqueMethodName, ActionApiDescriptionModel.Create( uniqueMethodName, + method, apiDescription.RelativePath, - apiDescription.HttpMethod + apiDescription.HttpMethod, + setting?.ApiVersions.Select(v => v.ToString()).ToList() ?? new List() //TODO: Also get from ApiVersion attributes if available..? )); AddParameterDescriptionsToModel(actionModel, method, apiDescription); } - private static string GetControllerName(ApiDescription apiDescription) + private static string CalculateControllerName(Type controllerType, ConventionalControllerSetting setting) { - return apiDescription.GroupName?.RemovePostFix(ApplicationService.CommonPostfixes) - ?? apiDescription.ActionDescriptor.AsControllerActionDescriptor().ControllerName; + var controllerName = controllerType.Name.RemovePostFix("Controller").RemovePostFix(ApplicationService.CommonPostfixes); + + if (setting?.UrlControllerNameNormalizer != null) + { + controllerName = setting.UrlControllerNameNormalizer(new UrlControllerNameNormalizerContext(setting.RootPath, controllerName)); + } + + return controllerName; } private static string GetUniqueActionName(MethodInfo method) @@ -155,19 +171,11 @@ namespace Volo.Abp.AspNetCore.Mvc return modelNameProvider.Name; } - private string GetRootPath(Type controllerType) + private static string GetRootPath([NotNull] Type controllerType, [CanBeNull] ConventionalControllerSetting setting) { - if (controllerType == null) - { - return ModuleApiDescriptionModel.DefaultRootPath; - } - - foreach (var controllerSetting in _options.AppServiceControllers.ControllerAssemblySettings) + if (setting != null) { - if (Equals(controllerType.Assembly, controllerSetting.Assembly)) - { - return controllerSetting.RootPath; - } + return setting.RootPath; } var areaAttr = controllerType.GetCustomAttributes().OfType().FirstOrDefault(); @@ -178,5 +186,19 @@ namespace Volo.Abp.AspNetCore.Mvc return ModuleApiDescriptionModel.DefaultRootPath; } + + [CanBeNull] + private ConventionalControllerSetting FindSetting(Type controllerType) + { + foreach (var controllerSetting in _options.ConventionalControllers.ConventionalControllerSettings) + { + if (controllerSetting.ControllerTypes.Contains(controllerType)) + { + return controllerSetting; + } + } + + return null; + } } } 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 deleted file mode 100644 index c7701994bc..0000000000 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ControllerAssemblySettingList.cs +++ /dev/null @@ -1,16 +0,0 @@ -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.Assembly); - } - } -} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalControllerFeatureProvider.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalControllerFeatureProvider.cs new file mode 100644 index 0000000000..e04574a03b --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalControllerFeatureProvider.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Volo.Abp.AspNetCore.Mvc.Conventions +{ + public class AbpConventionalControllerFeatureProvider : ControllerFeatureProvider + { + private readonly IAbpApplication _application; + + public AbpConventionalControllerFeatureProvider(IAbpApplication application) + { + _application = application; + } + + protected override bool IsController(TypeInfo typeInfo) + { + //TODO: Move this to a lazy loaded field for efficiency. + var configuration = _application.ServiceProvider + .GetRequiredService>().Value + .ConventionalControllers + .ConventionalControllerSettings + .GetSettingOrNull(typeInfo.AsType()); + + return configuration != null; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConvention.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs similarity index 86% rename from src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConvention.cs rename to src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs index 98a9daca43..946be023cc 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConvention.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs @@ -14,13 +14,13 @@ using Volo.Abp.Http; using Volo.Abp.Http.Modeling; using Volo.Abp.Reflection; -namespace Volo.Abp.AspNetCore.Mvc +namespace Volo.Abp.AspNetCore.Mvc.Conventions { - public class AbpAppServiceConvention : IAbpAppServiceConvention, ITransientDependency + public class AbpServiceConvention : IAbpServiceConvention, ITransientDependency { private readonly AbpAspNetCoreMvcOptions _options; - public AbpAppServiceConvention(IOptions options) + public AbpServiceConvention(IOptions options) { _options = options.Value; } @@ -37,11 +37,13 @@ namespace Volo.Abp.AspNetCore.Mvc var controllerType = controller.ControllerType.AsType(); var configuration = GetControllerSettingOrNull(controllerType); - if (IsRemoteService(controllerType)) + //TODO: We can remove different behaviour for ImplementsRemoteServiceInterface. If there is a configuration, then it should be applied! + //TODO: But also consider ConventionalControllerSetting.IsRemoteService method too..! + + if (ImplementsRemoteServiceInterface(controllerType)) { controller.ControllerName = controller.ControllerName.RemovePostFix(ApplicationService.CommonPostfixes); configuration?.ControllerModelConfigurer?.Invoke(controller); - //ConfigureArea(controller, configuration); ConfigureRemoteService(controller, configuration); } else @@ -55,7 +57,7 @@ namespace Volo.Abp.AspNetCore.Mvc } } - protected virtual void ConfigureRemoteService(ControllerModel controller, [CanBeNull] AbpControllerAssemblySetting configuration) + protected virtual void ConfigureRemoteService(ControllerModel controller, [CanBeNull] ConventionalControllerSetting configuration) { ConfigureApiExplorer(controller); ConfigureSelector(controller, configuration); @@ -92,7 +94,7 @@ namespace Volo.Abp.AspNetCore.Mvc protected virtual bool CanUseFormBodyBinding(ActionModel action, ParameterModel parameter) { - if (_options.AppServiceControllers.FormBodyBindingIgnoredTypes.Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType))) + if (_options.ConventionalControllers.FormBodyBindingIgnoredTypes.Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType))) { return false; } @@ -165,7 +167,7 @@ namespace Volo.Abp.AspNetCore.Mvc } } - protected virtual void ConfigureSelector(ControllerModel controller, [CanBeNull] AbpControllerAssemblySetting configuration) + protected virtual void ConfigureSelector(ControllerModel controller, [CanBeNull] ConventionalControllerSetting configuration) { RemoveEmptySelectors(controller.Selectors); @@ -182,7 +184,7 @@ namespace Volo.Abp.AspNetCore.Mvc } } - protected virtual void ConfigureSelector(string rootPath, string controllerName, ActionModel action, [CanBeNull] AbpControllerAssemblySetting configuration) + protected virtual void ConfigureSelector(string rootPath, string controllerName, ActionModel action, [CanBeNull] ConventionalControllerSetting configuration) { RemoveEmptySelectors(action.Selectors); @@ -196,7 +198,7 @@ namespace Volo.Abp.AspNetCore.Mvc } } - protected virtual void AddAbpServiceSelector(string rootPath, string controllerName, ActionModel action, [CanBeNull] AbpControllerAssemblySetting configuration) + protected virtual void AddAbpServiceSelector(string rootPath, string controllerName, ActionModel action, [CanBeNull] ConventionalControllerSetting configuration) { var httpMethod = SelectHttpMethod(action, configuration); @@ -209,12 +211,12 @@ namespace Volo.Abp.AspNetCore.Mvc action.Selectors.Add(abpServiceSelectorModel); } - protected virtual string SelectHttpMethod(ActionModel action, AbpControllerAssemblySetting configuration) + protected virtual string SelectHttpMethod(ActionModel action, ConventionalControllerSetting configuration) { return HttpMethodHelper.GetConventionalVerbForMethodName(action.ActionName); } - protected virtual void NormalizeSelectorRoutes(string rootPath, string controllerName, ActionModel action, [CanBeNull] AbpControllerAssemblySetting configuration) + protected virtual void NormalizeSelectorRoutes(string rootPath, string controllerName, ActionModel action, [CanBeNull] ConventionalControllerSetting configuration) { foreach (var selector in action.Selectors) { @@ -233,12 +235,12 @@ namespace Volo.Abp.AspNetCore.Mvc } [CanBeNull] - protected virtual AbpControllerAssemblySetting GetControllerSettingOrNull(Type controllerType) + protected virtual ConventionalControllerSetting GetControllerSettingOrNull(Type controllerType) { - return _options.AppServiceControllers.ControllerAssemblySettings.GetSettingOrNull(controllerType); + return _options.ConventionalControllers.ConventionalControllerSettings.GetSettingOrNull(controllerType); } - protected virtual AttributeRouteModel CreateAbpServiceAttributeRouteModel(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] AbpControllerAssemblySetting configuration) + protected virtual AttributeRouteModel CreateAbpServiceAttributeRouteModel(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] ConventionalControllerSetting configuration) { return new AttributeRouteModel( new RouteAttribute( @@ -247,7 +249,7 @@ namespace Volo.Abp.AspNetCore.Mvc ); } - protected virtual string CalculateRouteTemplate(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] AbpControllerAssemblySetting configuration) + protected virtual string CalculateRouteTemplate(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] ConventionalControllerSetting configuration) { var controllerNameInUrl = NormalizeUrlControllerName(rootPath, controllerName, action, httpMethod, configuration); @@ -276,7 +278,7 @@ namespace Volo.Abp.AspNetCore.Mvc return url; } - protected virtual string NormalizeUrlActionName(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] AbpControllerAssemblySetting configuration) + protected virtual string NormalizeUrlActionName(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] ConventionalControllerSetting configuration) { var actionNameInUrl = HttpMethodHelper .RemoveHttpMethodPrefix(action.ActionName, httpMethod) @@ -298,7 +300,7 @@ namespace Volo.Abp.AspNetCore.Mvc ); } - protected virtual string NormalizeUrlControllerName(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] AbpControllerAssemblySetting configuration) + protected virtual string NormalizeUrlControllerName(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] ConventionalControllerSetting configuration) { if(configuration?.UrlControllerNameNormalizer == null) { @@ -308,9 +310,7 @@ namespace Volo.Abp.AspNetCore.Mvc return configuration.UrlControllerNameNormalizer( new UrlControllerNameNormalizerContext( rootPath, - controllerName, - action, - httpMethod + controllerName ) ); } @@ -328,7 +328,7 @@ namespace Volo.Abp.AspNetCore.Mvc return selector.AttributeRouteModel == null && selector.ActionConstraints.IsNullOrEmpty(); } - protected virtual bool IsRemoteService(Type controllerType) + protected virtual bool ImplementsRemoteServiceInterface(Type controllerType) { return typeof(IRemoteService).GetTypeInfo().IsAssignableFrom(controllerType); } diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConventionWrapper.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConventionWrapper.cs similarity index 50% rename from src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConventionWrapper.cs rename to src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConventionWrapper.cs index c4fff972b6..10a45a659a 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConventionWrapper.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConventionWrapper.cs @@ -3,16 +3,16 @@ using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; -namespace Volo.Abp.AspNetCore.Mvc +namespace Volo.Abp.AspNetCore.Mvc.Conventions { [DisableConventionalRegistration] - public class AbpAppServiceConventionWrapper : IApplicationModelConvention + public class AbpServiceConventionWrapper : IApplicationModelConvention { - private readonly Lazy _convention; + private readonly Lazy _convention; - public AbpAppServiceConventionWrapper(IServiceCollection services) + public AbpServiceConventionWrapper(IServiceCollection services) { - _convention = services.GetRequiredServiceLazy(); + _convention = services.GetRequiredServiceLazy(); } public void Apply(ApplicationModel application) diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerOptions.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerOptions.cs new file mode 100644 index 0000000000..a574716ef1 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerOptions.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Http; +using Volo.Abp.Http.Modeling; + +namespace Volo.Abp.AspNetCore.Mvc.Conventions +{ + public class ConventionalControllerOptions + { + public ConventionalControllerSettingList ConventionalControllerSettings { get; } + + public List FormBodyBindingIgnoredTypes { get; } + + public ConventionalControllerOptions() + { + ConventionalControllerSettings = new ConventionalControllerSettingList(); + + FormBodyBindingIgnoredTypes = new List + { + typeof(IFormFile) + }; + } + + public ConventionalControllerOptions Create(Assembly assembly, [CanBeNull] Action optionsAction = null) + { + var setting = new ConventionalControllerSetting(assembly, ModuleApiDescriptionModel.DefaultRootPath); + optionsAction?.Invoke(setting); + setting.Initialize(); + ConventionalControllerSettings.Add(setting); + return this; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSetting.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSetting.cs new file mode 100644 index 0000000000..10ba9b0912 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSetting.cs @@ -0,0 +1,91 @@ +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.Versioning; +using Volo.Abp.Application.Services; +using Volo.Abp.Reflection; + +namespace Volo.Abp.AspNetCore.Mvc.Conventions +{ + public class ConventionalControllerSetting + { + [NotNull] + public Assembly Assembly { get; } + + [NotNull] + public HashSet ControllerTypes { get; } + + [NotNull] + public string RootPath + { + get => _rootPath; + set + { + Check.NotNull(value, nameof(value)); + _rootPath = value; + } + } + private string _rootPath; + + [CanBeNull] + public Func TypePredicate { get; set; } + + [CanBeNull] + public Action ControllerModelConfigurer { get; set; } + + [CanBeNull] + public Func UrlControllerNameNormalizer { get; set; } + + [CanBeNull] + public Func UrlActionNameNormalizer { get; set; } + + public List ApiVersions { get; } + + public Action ApiVersionConfigurer { get; set; } + + public ConventionalControllerSetting([NotNull] Assembly assembly, [NotNull] string rootPath) + { + Check.NotNull(assembly, rootPath); + + Assembly = assembly; + RootPath = rootPath; + + ControllerTypes = new HashSet(); + + ApiVersions = new List(); + } + + public void Initialize() + { + var types = Assembly.GetTypes() + .Where(IsRemoteService) + .WhereIf(TypePredicate != null, TypePredicate); + + foreach (var type in types) + { + ControllerTypes.Add(type); + } + } + + private static bool IsRemoteService(Type type) + { + if (!typeof(IRemoteService).IsAssignableFrom(type) || !type.IsPublic || type.IsAbstract || type.IsGenericType) + { + return false; + } + + var remoteServiceAttr = ReflectionHelper.GetSingleAttributeOrDefault(type); + + if (remoteServiceAttr != null && !remoteServiceAttr.IsEnabledFor(type)) + { + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSettingList.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSettingList.cs new file mode 100644 index 0000000000..a460a8fe00 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSettingList.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; + +namespace Volo.Abp.AspNetCore.Mvc.Conventions +{ + public class ConventionalControllerSettingList : List + { + [CanBeNull] + public ConventionalControllerSetting GetSettingOrNull(Type controllerType) + { + return this.FirstOrDefault(controllerSetting => controllerSetting.ControllerTypes.Contains(controllerType)); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/IAbpServiceConvention.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/IAbpServiceConvention.cs new file mode 100644 index 0000000000..aac601d0b4 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/IAbpServiceConvention.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Mvc.ApplicationModels; + +namespace Volo.Abp.AspNetCore.Mvc.Conventions +{ + public interface IAbpServiceConvention : IApplicationModelConvention + { + } +} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/UrlActionNameNormalizerContext.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/UrlActionNameNormalizerContext.cs similarity index 93% rename from src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/UrlActionNameNormalizerContext.cs rename to src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/UrlActionNameNormalizerContext.cs index 631b8b50de..ebc122af05 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/UrlActionNameNormalizerContext.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/UrlActionNameNormalizerContext.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc.ApplicationModels; -namespace Volo.Abp.AspNetCore.Mvc +namespace Volo.Abp.AspNetCore.Mvc.Conventions { public class UrlActionNameNormalizerContext { diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/UrlControllerNameNormalizerContext.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/UrlControllerNameNormalizerContext.cs new file mode 100644 index 0000000000..2cb3bdc733 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/UrlControllerNameNormalizerContext.cs @@ -0,0 +1,15 @@ +namespace Volo.Abp.AspNetCore.Mvc.Conventions +{ + public class UrlControllerNameNormalizerContext + { + public string RootPath { get; } + + public string ControllerName { get; } + + public UrlControllerNameNormalizerContext(string rootPath, string controllerName) + { + RootPath = rootPath; + ControllerName = controllerName; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IAbpAppServiceConvention.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IAbpAppServiceConvention.cs deleted file mode 100644 index 899d66b4c2..0000000000 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IAbpAppServiceConvention.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Mvc.ApplicationModels; - -namespace Volo.Abp.AspNetCore.Mvc -{ - public interface IAbpAppServiceConvention : IApplicationModelConvention - { - } -} \ 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 deleted file mode 100644 index ccd85b3b9a..0000000000 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IAbpControllerAssemblySettingBuilder.cs +++ /dev/null @@ -1,12 +0,0 @@ -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.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/UrlControllerNameNormalizerContext.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/UrlControllerNameNormalizerContext.cs deleted file mode 100644 index 90bffe8df4..0000000000 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/UrlControllerNameNormalizerContext.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.AspNetCore.Mvc.ApplicationModels; - -namespace Volo.Abp.AspNetCore.Mvc -{ - //TODO: Re-consider properties of this class. - public class UrlControllerNameNormalizerContext - { - public string RootPath { get; } - - public string ControllerName { get; } - - public ActionModel Action { get; } - - public string HttpMethod { get; } - - public UrlControllerNameNormalizerContext(string rootPath, string controllerName, ActionModel action, string httpMethod) - { - RootPath = rootPath; - ControllerName = controllerName; - Action = action; - HttpMethod = httpMethod; - } - } -} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Versioning/HttpContextRequestedApiVersion.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Versioning/HttpContextRequestedApiVersion.cs new file mode 100644 index 0000000000..5678ccfe31 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Versioning/HttpContextRequestedApiVersion.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.ApiVersioning; + +namespace Volo.Abp.AspNetCore.Mvc.Versioning +{ + public class HttpContextRequestedApiVersion : IRequestedApiVersion + { + public string Current => _httpContextAccessor.HttpContext?.GetRequestedApiVersion().ToString(); + + private readonly IHttpContextAccessor _httpContextAccessor; + + public HttpContextRequestedApiVersion(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs b/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs index c3ec5c76b5..d68d21ec3e 100644 --- a/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs +++ b/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs @@ -15,6 +15,7 @@ namespace Microsoft.Extensions.DependencyInjection public static IServiceCollection AddHttpClientProxies(this IServiceCollection services, Assembly assembly, string remoteServiceName = RemoteServiceConfigurationDictionary.DefaultName) { + //TODO: Make a configuration option and add remoteServiceName inside it! //TODO: Add option to change type filter var serviceTypes = assembly.GetTypes().Where(t => diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionCache.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionCache.cs index 4fc370a2e2..4476ef90cd 100644 --- a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionCache.cs +++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionCache.cs @@ -42,7 +42,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying { using (var client = _httpClientFactory.Create()) { - var response = await client.GetAsync(baseUrl + "api/abp/api-description"); + var response = await client.GetAsync(baseUrl + "api/abp/api-definition"); if (!response.IsSuccessStatusCode) { throw new AbpException("Remote service returns error!"); diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs index b58e43e1ec..34df0deecb 100644 --- a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs +++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs @@ -16,9 +16,9 @@ namespace Volo.Abp.Http.Client.DynamicProxying _descriptionCache = descriptionCache; } - public async Task FindActionAsync(RemoteServiceConfiguration proxyConfig, Type serviceType, MethodInfo method) + public async Task FindActionAsync(string baseUrl, Type serviceType, MethodInfo method) { - var apiDescription = await _descriptionCache.GetAsync(proxyConfig.BaseUrl); + var apiDescription = await _descriptionCache.GetAsync(baseUrl); //TODO: Cache finding? @@ -35,7 +35,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying foreach (var action in controller.Actions.Values) { - if (action.NameOnClass == method.Name && action.ParametersOnMethod.Count == methodParameters.Length) + if (action.Name == method.Name && action.ParametersOnMethod.Count == methodParameters.Length) { var found = true; diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiVersionInfo.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiVersionInfo.cs new file mode 100644 index 0000000000..88035cf94b --- /dev/null +++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiVersionInfo.cs @@ -0,0 +1,22 @@ +using System; + +namespace Volo.Abp.Http.Client.DynamicProxying +{ + public class ApiVersionInfo //TODO: Rename to not conflict with api versioning apis + { + public string BindingSource { get; } + public string Version { get; } + + public ApiVersionInfo(string bindingSource, string version) + { + BindingSource = bindingSource; + Version = version; + } + + public bool ShouldSendInQueryString() + { + //TODO: Constant! TODO: Other sources! + return !BindingSource.IsIn("Path"); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs index fbd533dbba..21f36577d8 100644 --- a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs +++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; using System.Reflection; using System.Threading.Tasks; using Microsoft.Extensions.Options; @@ -56,10 +57,20 @@ namespace Volo.Abp.Http.Client.DynamicProxying } else { - invocation.ReturnValue = _jsonSerializer.Deserialize( - invocation.Method.ReturnType, - AsyncHelper.RunSync(() => MakeRequest(invocation)) - ); + var responseAsString = AsyncHelper.RunSync(() => MakeRequest(invocation)); + + //TODO: Think on that + if (TypeHelper.IsPrimitiveExtendedIncludingNullable(invocation.Method.ReturnType, true)) + { + invocation.ReturnValue = Convert.ChangeType(responseAsString, invocation.Method.ReturnType); + } + else + { + invocation.ReturnValue = _jsonSerializer.Deserialize( + invocation.Method.ReturnType, + responseAsString + ); + } } } @@ -94,16 +105,17 @@ namespace Volo.Abp.Http.Client.DynamicProxying { using (var client = _httpClientFactory.Create()) { - var proxyConfig = GetProxyConfig(); - var action = await _apiDescriptionFinder.FindActionAsync(proxyConfig, typeof(TService), invocation.Method); - var url = proxyConfig.BaseUrl + UrlBuilder.GenerateUrlWithParameters(action, invocation.ArgumentsDictionary); + var baseUrl = GetBaseUrl(); + var action = await _apiDescriptionFinder.FindActionAsync(baseUrl, typeof(TService), invocation.Method); + var apiVersion = GetApiVersionInfo(action); + var url = baseUrl + UrlBuilder.GenerateUrlWithParameters(action, invocation.ArgumentsDictionary, apiVersion); var requestMessage = new HttpRequestMessage(action.GetHttpMethod(), url) { - Content = RequestPayloadBuilder.BuildContent(action, invocation.ArgumentsDictionary, _jsonSerializer) + Content = RequestPayloadBuilder.BuildContent(action, invocation.ArgumentsDictionary, _jsonSerializer, apiVersion) }; - AddHeaders(invocation, action, requestMessage); + AddHeaders(invocation, action, requestMessage, apiVersion); var response = await client.SendAsync(requestMessage); @@ -116,9 +128,47 @@ namespace Volo.Abp.Http.Client.DynamicProxying } } - private static void AddHeaders(IAbpMethodInvocation invocation, ActionApiDescriptionModel action, HttpRequestMessage requestMessage) + private ApiVersionInfo GetApiVersionInfo(ActionApiDescriptionModel action) + { + var apiVersion = FindBestApiVersion(action); + + //TODO: Make names configurable? + var versionParam = action.Parameters.FirstOrDefault(p => p.Name == "apiVersion" && p.BindingSourceId == ParameterBindingSources.Path) ?? + action.Parameters.FirstOrDefault(p => p.Name == "api-version" && p.BindingSourceId == ParameterBindingSources.Query); + + return new ApiVersionInfo(versionParam?.BindingSourceId, apiVersion); + } + + private string FindBestApiVersion(ActionApiDescriptionModel action) + { + var configuredVersion = GetConfiguredApiVersion(); + + if (action.SupportedVersions.IsNullOrEmpty()) + { + return configuredVersion ?? "1.0"; + } + + if (action.SupportedVersions.Contains(configuredVersion)) + { + return configuredVersion; + } + + return action.SupportedVersions.Last(); //TODO: Ensure to get the latest version! + } + + private static void AddHeaders(IAbpMethodInvocation invocation, ActionApiDescriptionModel action, HttpRequestMessage requestMessage, ApiVersionInfo apiVersion) { - foreach (var headerParameter in action.Parameters.Where(p => p.BindingSourceId == ParameterBindingSources.Header)) + if (!apiVersion.Version.IsNullOrEmpty()) + { + //TODO: What about other media types? + requestMessage.Headers.Add("accept", $"text/plain; v={apiVersion.Version}"); + requestMessage.Headers.Add("accept", $"application/json; v={apiVersion.Version}"); + requestMessage.Headers.Add("api-version", apiVersion.Version); + } + + var headers = action.Parameters.Where(p => p.BindingSourceId == ParameterBindingSources.Header).ToArray(); + + foreach (var headerParameter in headers) { var value = HttpActionParameterHelper.FindParameterValue(invocation.ArgumentsDictionary, headerParameter); if (value != null) @@ -128,16 +178,24 @@ namespace Volo.Abp.Http.Client.DynamicProxying } } - private RemoteServiceConfiguration GetProxyConfig() + private string GetBaseUrl() { var clientConfig = _clientOptions.HttpClientProxies.GetOrDefault(typeof(TService)) - ?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(TService).FullName}."); + ?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(TService).FullName}."); - return _remoteServiceOptions.RemoteServices.GetOrDefault(clientConfig.RemoteServiceName) - ?? _remoteServiceOptions.RemoteServices.Default - ?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(TService).FullName}."); + return _remoteServiceOptions.RemoteServices.GetOrDefault(clientConfig.RemoteServiceName)?.BaseUrl + ?? _remoteServiceOptions.RemoteServices.Default?.BaseUrl + ?? throw new AbpException($"Could not find Base URL for {typeof(TService).FullName}."); } + private string GetConfiguredApiVersion() + { + var clientConfig = _clientOptions.HttpClientProxies.GetOrDefault(typeof(TService)) + ?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(TService).FullName}."); + + return _remoteServiceOptions.RemoteServices.GetOrDefault(clientConfig.RemoteServiceName)?.Version + ?? _remoteServiceOptions.RemoteServices.Default?.Version; + } private async Task ThrowExceptionForResponseAsync(HttpResponseMessage response) { diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/HttpActionParameterHelper.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/HttpActionParameterHelper.cs index 5eb5eba1de..36c3171dc2 100644 --- a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/HttpActionParameterHelper.cs +++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/HttpActionParameterHelper.cs @@ -8,7 +8,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying { public static object FindParameterValue(IReadOnlyDictionary methodArguments, ParameterApiDescriptionModel apiParameter) { - var value = methodArguments[apiParameter.NameOnMethod]; + var value = methodArguments.GetOrDefault(apiParameter.NameOnMethod); if (value == null) { return null; diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/IApiDescriptionFinder.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/IApiDescriptionFinder.cs index af34a505c9..ede988b4b9 100644 --- a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/IApiDescriptionFinder.cs +++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/IApiDescriptionFinder.cs @@ -7,6 +7,6 @@ namespace Volo.Abp.Http.Client.DynamicProxying { public interface IApiDescriptionFinder { - Task FindActionAsync(RemoteServiceConfiguration proxyConfig, Type serviceType, MethodInfo invocationMethod); + Task FindActionAsync(string baseUrl, Type serviceType, MethodInfo invocationMethod); } } \ No newline at end of file diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs index f58bda5b9c..8f5fbd0c39 100644 --- a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs +++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs @@ -12,10 +12,9 @@ namespace Volo.Abp.Http.Client.DynamicProxying public static class RequestPayloadBuilder { [CanBeNull] - public static HttpContent BuildContent(ActionApiDescriptionModel action,IReadOnlyDictionary methodArguments, IJsonSerializer jsonSerializer) + public static HttpContent BuildContent(ActionApiDescriptionModel action,IReadOnlyDictionary methodArguments, IJsonSerializer jsonSerializer, ApiVersionInfo apiVersion) { var body = GenerateBody(action, methodArguments, jsonSerializer); - if (body != null) { return new StringContent(body, Encoding.UTF8, "application/json"); //TODO: application/json to a constant diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs index f829c9a7a7..dcb9868b7f 100644 --- a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs +++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs @@ -9,17 +9,17 @@ namespace Volo.Abp.Http.Client.DynamicProxying { internal static class UrlBuilder { - public static string GenerateUrlWithParameters(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments) + public static string GenerateUrlWithParameters(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) { var urlBuilder = new StringBuilder(action.Url); - ReplacePathVariables(urlBuilder, action.Parameters, methodArguments); - AddQueryStringParameters(urlBuilder, action.Parameters, methodArguments); + ReplacePathVariables(urlBuilder, action.Parameters, methodArguments, apiVersion); + AddQueryStringParameters(urlBuilder, action.Parameters, methodArguments, apiVersion); return urlBuilder.ToString(); } - private static void ReplacePathVariables(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments) + private static void ReplacePathVariables(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) { var pathParameters = actionParameters .Where(p => p.BindingSourceId == ParameterBindingSources.Path) @@ -30,9 +30,15 @@ namespace Volo.Abp.Http.Client.DynamicProxying return; } - foreach (var pathParameter in pathParameters) + if (pathParameters.Any(p => p.Name == "apiVersion")) + { + urlBuilder = urlBuilder.Replace("{apiVersion}", apiVersion.Version); + } + + foreach (var pathParameter in pathParameters.Where(p => p.Name != "apiVersion")) //TODO: Constant! { var value = HttpActionParameterHelper.FindParameterValue(methodArguments, pathParameter); + if (value == null) { if (pathParameter.IsOptional) @@ -55,18 +61,14 @@ namespace Volo.Abp.Http.Client.DynamicProxying } } - private static void AddQueryStringParameters(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments) + private static void AddQueryStringParameters(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) { var queryStringParameters = actionParameters .Where(p => p.BindingSourceId.IsIn(ParameterBindingSources.ModelBinding, ParameterBindingSources.Query)) .ToArray(); - if (!queryStringParameters.Any()) - { - return; - } - var isFirstParam = true; + foreach (var queryStringParameter in queryStringParameters) { var value = HttpActionParameterHelper.FindParameterValue(methodArguments, queryStringParameter); @@ -75,11 +77,21 @@ namespace Volo.Abp.Http.Client.DynamicProxying continue; } - urlBuilder.Append(isFirstParam ? "?" : "&"); - urlBuilder.Append(queryStringParameter.Name + "=" + System.Net.WebUtility.UrlEncode(value.ToString())); + AddQueryStringParameter(urlBuilder, isFirstParam, queryStringParameter.Name, value); isFirstParam = false; } + + if (apiVersion.ShouldSendInQueryString()) + { + AddQueryStringParameter(urlBuilder, isFirstParam, "api-version", apiVersion.Version); //TODO: Constant! + } + } + + private static void AddQueryStringParameter(StringBuilder urlBuilder, bool isFirstParam, string name, object value) + { + urlBuilder.Append(isFirstParam ? "?" : "&"); + urlBuilder.Append(name + "=" + System.Net.WebUtility.UrlEncode(value.ToString())); } } } diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfiguration.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfiguration.cs index 666b9b4d5d..200e549c3e 100644 --- a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfiguration.cs +++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfiguration.cs @@ -4,14 +4,17 @@ { public string BaseUrl { get; set; } + public string Version { get; set; } + public RemoteServiceConfiguration() { } - public RemoteServiceConfiguration(string baseUrl) + public RemoteServiceConfiguration(string baseUrl, string version = null) { BaseUrl = baseUrl; + Version = version; } } } \ No newline at end of file diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs index fe91a3ddf2..7e9d375d06 100644 --- a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs +++ b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Reflection; +using JetBrains.Annotations; namespace Volo.Abp.Http.Modeling { @@ -11,12 +12,14 @@ namespace Volo.Abp.Http.Modeling { public string UniqueName { get; set; } - public string NameOnClass { get; set; } + public string Name { get; set; } public string HttpMethod { get; set; } public string Url { get; set; } + public IList SupportedVersions { get; set; } + public IList ParametersOnMethod { get; set; } public IList Parameters { get; set; } @@ -28,12 +31,18 @@ namespace Volo.Abp.Http.Modeling } - public static ActionApiDescriptionModel Create(MethodInfo method, string uniqueName, string url, string httpMethod = null) + public static ActionApiDescriptionModel Create([NotNull] string uniqueName, [NotNull] MethodInfo method, [NotNull] string url, [NotNull] string httpMethod, [NotNull] IList supportedVersions) { + Check.NotNull(uniqueName, nameof(uniqueName)); + Check.NotNull(method, nameof(method)); + Check.NotNull(url, nameof(url)); + Check.NotNull(httpMethod, nameof(httpMethod)); + Check.NotNull(supportedVersions, nameof(supportedVersions)); + return new ActionApiDescriptionModel { UniqueName = uniqueName, - NameOnClass = method.Name, + Name = method.Name, Url = url, HttpMethod = httpMethod, ReturnValue = ReturnValueApiDescriptionModel.Create(method.ReturnType), @@ -41,7 +50,8 @@ namespace Volo.Abp.Http.Modeling ParametersOnMethod = method .GetParameters() .Select(MethodParameterApiDescriptionModel.Create) - .ToList() + .ToList(), + SupportedVersions = supportedVersions }; } @@ -55,5 +65,10 @@ namespace Volo.Abp.Http.Modeling { return HttpMethodHelper.ConvertToHttpMethod(HttpMethod); } + + public override string ToString() + { + return $"[ActionApiDescriptionModel {Name}]"; + } } } \ No newline at end of file diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApiDescriptionModelOptions.cs b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApiDescriptionModelOptions.cs new file mode 100644 index 0000000000..18b335b115 --- /dev/null +++ b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApiDescriptionModelOptions.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Application.Services; +using Volo.Abp.Aspects; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Http.Modeling +{ + public class ApiDescriptionModelOptions + { + public HashSet IgnoredInterfaces { get; } + + public ApiDescriptionModelOptions() + { + IgnoredInterfaces = new HashSet + { + typeof(IApplicationService), + typeof(IRemoteService), + typeof(ITransientDependency), + typeof(ISingletonDependency), + typeof(IDisposable), + typeof(IAvoidDuplicateCrossCuttingConcerns) + }; + } + } +} diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs index 51f03661f4..2bd9a301ab 100644 --- a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs +++ b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; namespace Volo.Abp.Http.Modeling { @@ -20,7 +21,7 @@ namespace Volo.Abp.Http.Modeling } - public static ControllerApiDescriptionModel Create(string controllerName, Type type) + public static ControllerApiDescriptionModel Create(string controllerName, Type type, [CanBeNull] HashSet ignoredInterfaces = null) { return new ControllerApiDescriptionModel { @@ -29,21 +30,22 @@ namespace Volo.Abp.Http.Modeling Actions = new Dictionary(), Interfaces = type .GetInterfaces() + .WhereIf(ignoredInterfaces != null, i => !i.IsGenericType && !ignoredInterfaces.Contains(i)) .Select(ControllerInterfaceApiDescriptionModel.Create) .ToList() }; } - public ActionApiDescriptionModel AddAction(ActionApiDescriptionModel action) + public ActionApiDescriptionModel AddAction(string uniqueName, ActionApiDescriptionModel action) { - if (Actions.ContainsKey(action.UniqueName)) + if (Actions.ContainsKey(uniqueName)) { throw new AbpException( - $"Can not add more than one action with same name to the same controller. Controller: {ControllerName}, Action: {action.UniqueName}." - ); + $"Can not add more than one action with same name to the same controller. Controller: {ControllerName}, Action: {action.Name}." + ); } - return Actions[action.UniqueName] = action; + return Actions[uniqueName] = action; } public ControllerApiDescriptionModel CreateSubModel(string[] actions) @@ -56,11 +58,11 @@ namespace Volo.Abp.Http.Modeling Actions = new Dictionary() }; - foreach (var action in Actions.Values) + foreach (var action in Actions) { - if (actions == null || actions.Contains(action.UniqueName)) + if (actions == null || actions.Contains(action.Key)) { - subModel.AddAction(action); + subModel.AddAction(action.Key, action.Value); } } @@ -71,5 +73,10 @@ namespace Volo.Abp.Http.Modeling { return Interfaces.Any(i => i.TypeAsString == interfaceType.GetFullNameWithAssemblyName()); } + + public override string ToString() + { + return $"[ControllerApiDescriptionModel {ControllerName}]"; + } } } \ No newline at end of file diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ModuleApiDescriptionModel.cs b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ModuleApiDescriptionModel.cs index 5f776d5e4a..44967fa50c 100644 --- a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ModuleApiDescriptionModel.cs +++ b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ModuleApiDescriptionModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; namespace Volo.Abp.Http.Modeling { @@ -40,9 +41,9 @@ namespace Volo.Abp.Http.Modeling return Controllers[controller.ControllerName] = controller; } - public ControllerApiDescriptionModel GetOrAddController(string name, Type type) + public ControllerApiDescriptionModel GetOrAddController(string uniqueName, string name, Type type, [CanBeNull] HashSet ignoredInterfaces = null) { - return Controllers.GetOrAdd(name, () => ControllerApiDescriptionModel.Create(name, type)); + return Controllers.GetOrAdd(uniqueName, () => ControllerApiDescriptionModel.Create(name, type, ignoredInterfaces)); } public ModuleApiDescriptionModel CreateSubModel(string[] controllers, string[] actions) diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs index 609fdd9000..d1de33258f 100644 --- a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs +++ b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs @@ -30,7 +30,7 @@ namespace Volo.Abp.Http.Modeling { Name = name, NameOnMethod = nameOnMethod, - TypeAsString = type.GetFullNameWithAssemblyName(), + TypeAsString = type?.GetFullNameWithAssemblyName(), IsOptional = isOptional, DefaultValue = defaultValue, ConstraintTypes = constraintTypes, diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator.cs b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator.cs index 87dbba1bad..5c399ab83a 100644 --- a/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator.cs +++ b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Text; +using Volo.Abp.Application.Services; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Modeling; @@ -20,8 +23,6 @@ namespace Volo.Abp.Http.ProxyScripting.Generators.JQuery script.AppendLine("/* This file is automatically generated by ABP framework to use MVC Controllers from javascript. */"); script.AppendLine(); - script.AppendLine("var abp = abp || {};"); - script.AppendLine("abp.services = abp.services || {};"); foreach (var module in model.Modules.Values) { @@ -37,53 +38,85 @@ namespace Volo.Abp.Http.ProxyScripting.Generators.JQuery //TODO: Eleminate repeating module.RootPath.Replace("/", ".").ToCamelCase() ! //TODO: Remove illegal chars (like '-') from module/controller names! - script.AppendLine($"// module '{module.RootPath.ToCamelCase()}'"); + script.AppendLine($"// module {module.RootPath.ToCamelCase()}"); + script.AppendLine(); script.AppendLine("(function(){"); - script.AppendLine($"abp.utils.createNamespace(abp, 'services.{module.RootPath.Replace("/", ".").ToCamelCase()}');"); foreach (var controller in module.Controllers.Values) { script.AppendLine(); - AddControllerScript(script, module, controller); + AddControllerScript(script, controller); } script.AppendLine(); script.AppendLine("})();"); } - private static void AddControllerScript(StringBuilder script, ModuleApiDescriptionModel module, ControllerApiDescriptionModel controller) + private static void AddControllerScript(StringBuilder script, ControllerApiDescriptionModel controller) { - script.AppendLine($" // controller '{controller.ControllerName.ToCamelCase()}'"); + var controllerName = GetNormalizedTypeName(controller.TypeAsString); + + script.AppendLine($" // controller {controllerName}"); + script.AppendLine(); script.AppendLine(" (function(){"); script.AppendLine(); - script.AppendLine($" abp.services.{module.RootPath.Replace("/", ".").ToCamelCase()}.{controller.ControllerName.ToCamelCase()} = abp.services.{module.RootPath.Replace("/", ".").ToCamelCase()}.{controller.ControllerName.ToCamelCase()} || {{}};"); + script.AppendLine($" abp.utils.createNamespace(window, '{controllerName}');"); + + var normalizedActionNames = CalculateNormalizedActionNames(controller.Actions); foreach (var action in controller.Actions.Values) { script.AppendLine(); - AddActionScript(script, module, controller, action); + AddActionScript(script, controllerName, action, normalizedActionNames[action]); } script.AppendLine(); script.AppendLine(" })();"); } - private static void AddActionScript(StringBuilder script, ModuleApiDescriptionModel module, ControllerApiDescriptionModel controller, ActionApiDescriptionModel action) + private static void AddActionScript(StringBuilder script, string controllerName, ActionApiDescriptionModel action, string normalizedActionName) { var parameterList = ProxyScriptingJsFuncHelper.GenerateJsFuncParameterList(action, "ajaxParams"); - script.AppendLine($" // action '{action.NameOnClass.ToCamelCase()}'"); - script.AppendLine($" abp.services.{module.RootPath.Replace("/", ".").ToCamelCase()}.{controller.ControllerName.ToCamelCase()}{ProxyScriptingJsFuncHelper.WrapWithBracketsOrWithDotPrefix(action.NameOnClass.ToCamelCase())} = function({parameterList}) {{"); + script.AppendLine($" {controllerName}{ProxyScriptingJsFuncHelper.WrapWithBracketsOrWithDotPrefix(normalizedActionName.RemovePostFix("Async").ToCamelCase())} = function({parameterList}) {{"); + + var versionParam = action.Parameters.FirstOrDefault(p => p.Name == "apiVersion" && p.BindingSourceId == ParameterBindingSources.Path) ?? + action.Parameters.FirstOrDefault(p => p.Name == "api-version" && p.BindingSourceId == ParameterBindingSources.Query); + + if (versionParam != null) + { + var version = FindBestApiVersion(action); + script.AppendLine($" var {ProxyScriptingJsFuncHelper.NormalizeJsVariableName(versionParam.Name)} = '{version}';"); + } + script.AppendLine(" return abp.ajax($.extend(true, {"); - AddAjaxCallParameters(script, controller, action); + AddAjaxCallParameters(script, action); script.AppendLine(" }, ajaxParams));;"); script.AppendLine(" };"); } - private static void AddAjaxCallParameters(StringBuilder script, ControllerApiDescriptionModel controller, ActionApiDescriptionModel action) + private static string FindBestApiVersion(ActionApiDescriptionModel action) + { + //var configuredVersion = GetConfiguredApiVersion(); //TODO: Implement + string configuredVersion = null; + + if (action.SupportedVersions.IsNullOrEmpty()) + { + return configuredVersion ?? "1.0"; + } + + if (action.SupportedVersions.Contains(configuredVersion)) + { + return configuredVersion; + } + + return action.SupportedVersions.Last(); //TODO: Ensure to get the latest version! + } + + private static void AddAjaxCallParameters(StringBuilder script, ActionApiDescriptionModel action) { var httpMethod = action.HttpMethod?.ToUpperInvariant() ?? "POST"; @@ -121,5 +154,49 @@ namespace Volo.Abp.Http.ProxyScripting.Generators.JQuery script.AppendLine(); } + + private static Dictionary CalculateNormalizedActionNames(Dictionary actions) + { + var result = new Dictionary(); + + var actionsByName = new Dictionary>(); + + foreach (var action in actions.Values) + { + var actionName = action.Name.RemovePostFix("Async").ToCamelCase(); + result[action] = actionName; + actionsByName.GetOrAdd(actionName, () => new List()).Add(action); + } + + foreach (var actionByName in actionsByName) + { + if (actionByName.Value.Count <= 1) + { + continue; + } + + foreach (var action in actionByName.Value) + { + result[action] = action.UniqueName; + } + } + + return result; + } + + private static string GetNormalizedTypeName(string typeWithAssemblyName) + { + return CamelCaseWithNamespace( + typeWithAssemblyName.Split(",")[0] + .Trim() + .RemovePostFix(ApplicationService.CommonPostfixes) + .RemovePostFix("Controller") + ); + } + + private static string CamelCaseWithNamespace(string name) + { + return name.Split('.').Select(n => n.ToCamelCase()).JoinAsString("."); + } } } \ No newline at end of file diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingHelper.cs b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingHelper.cs index c962263bd8..64d1515574 100644 --- a/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingHelper.cs +++ b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingHelper.cs @@ -47,7 +47,7 @@ namespace Volo.Abp.Http.ProxyScripting.Generators if (parameters.Length > 1) { throw new AbpException( - $"Only one complex type allowed as argument to a controller action that's binding source is 'Body'. But {action.UniqueName} ({action.Url}) contains more than one!" + $"Only one complex type allowed as argument to a controller action that's binding source is 'Body'. But {action.Name} ({action.Url}) contains more than one!" ); } diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs index a92bbe1710..baa52f3155 100644 --- a/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs +++ b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs @@ -133,7 +133,7 @@ namespace Volo.Abp.Http.ProxyScripting.Generators public static string GenerateJsFuncParameterList(ActionApiDescriptionModel action, string ajaxParametersName) { - var methodParamNames = action.Parameters.Select(p => p.NameOnMethod).Distinct().ToList(); + var methodParamNames = action.ParametersOnMethod.Select(p => p.Name).Distinct().ToList(); methodParamNames.Add(ajaxParametersName); return methodParamNames.Select(prmName => NormalizeJsVariableName(prmName.ToCamelCase())).JoinAsString(", "); } diff --git a/src/Volo.Abp.Identity.HttpApi.Host/AbpIdentityHttpApiHostModule.cs b/src/Volo.Abp.Identity.HttpApi.Host/AbpIdentityHttpApiHostModule.cs index aca4f8ca56..db1f278d6b 100644 --- a/src/Volo.Abp.Identity.HttpApi.Host/AbpIdentityHttpApiHostModule.cs +++ b/src/Volo.Abp.Identity.HttpApi.Host/AbpIdentityHttpApiHostModule.cs @@ -1,15 +1,19 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Swashbuckle.AspNetCore.Swagger; using Volo.Abp.AspNetCore.Modularity; +using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.Autofac; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.Identity.EntityFrameworkCore; +using Volo.Abp.Identity.HttpApi.Host.VersioningTests.V1; using Volo.Abp.Modularity; namespace Volo.Abp.Identity.HttpApi.Host @@ -19,7 +23,7 @@ namespace Volo.Abp.Identity.HttpApi.Host { public override void ConfigureServices(IServiceCollection services) { - var hostingEnvironment = services.GetSingletonInstance(); + var hostingEnvironment = services.GetSingletonInstance(); //TOD: Move to BuildConfiguration method var configuration = BuildConfiguration(hostingEnvironment); services.Configure(configuration); @@ -33,12 +37,68 @@ namespace Volo.Abp.Identity.HttpApi.Host }); }); - services.AddMvc(); + // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service + // note: the specified format code will format the version as "'v'major[.minor][-status]" + services.AddMvcCore().AddVersionedApiExplorer(o => o.GroupNameFormat = "'v'VVV"); - services.AddSwaggerGen(options => + services.AddMvc(options => { - options.SwaggerDoc("v1", new Info { Title = "Volo.Abp.Identity API", Version = "v1" }); - options.DocInclusionPredicate((docName, description) => true); + + }); + + services.AddApiVersioning(o => + { + o.ReportApiVersions = true; + //o.Conventions.Controller().Action((MethodInfo)null).MapToApiVersion(new ApiVersion(1,1),).IsApiVersionNeutral(); + //o.Conventions.Controller().HasApiVersion(new ApiVersion(3, 0)); //We can do that based on controller's AbpApiVersion attribute! + o.AssumeDefaultVersionWhenUnspecified = true; + o.DefaultApiVersion = new ApiVersion(3, 0); //Default: 1.0 //We can not rely on that, application should do. + + //o.ErrorResponses //TOD: We can override error response generator (to solve https://github.com/Microsoft/aspnet-api-versioning/issues/195) + + //o.Conventions.Controller().HasApiVersion(2, 0); + //o.Conventions.Controller().IsApiVersionNeutral(); + + o.ConfigureAbp(services); + }); + + services.AddSwaggerGen( + options => + { + //options.SwaggerDoc("v1", new Info { Title = "Volo.Abp.Identity API", Version = "v1" }); + //options.DocInclusionPredicate((docName, description) => true); + + + // resolve the IApiVersionDescriptionProvider service + // note: that we have to build a temporary service provider here because one has not been created yet + var provider = services.BuildServiceProvider().GetRequiredService(); + + // add a swagger document for each discovered API version + // note: you might choose to skip or document deprecated API versions differently + foreach (var description in provider.ApiVersionDescriptions) + { + options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description)); + } + + // add a custom operation filter which sets default values + options.OperationFilter(); + + // integrate xml comments + //options.IncludeXmlComments(XmlCommentsFilePath); //TODO: Add XML comments! + }); + + services.Configure(options => + { + options.ConventionalControllers.Create(typeof(AbpIdentityHttpApiHostModule).Assembly, o => + { + o.TypePredicate = t => t == typeof(CallsController); + }); + + options.ConventionalControllers.Create(typeof(AbpIdentityHttpApiHostModule).Assembly, o => + { + o.TypePredicate = t => t == typeof(Host.VersioningTests.V2.CallsController); + o.RootPath = "app/compat"; + }); }); services.AddAssemblyOf(); @@ -57,10 +117,20 @@ namespace Volo.Abp.Identity.HttpApi.Host app.UseStaticFiles(); + var provider = context.ServiceProvider.GetRequiredService(); + app.UseSwagger(); - app.UseSwaggerUI(c => + app.UseSwaggerUI(options => { - c.SwaggerEndpoint("/swagger/v1/swagger.json", "Volo.Abp.Identity API"); + //options.SwaggerEndpoint("/swagger/v1/swagger.json", "Volo.Abp.Identity API"); + + + + // build a swagger endpoint for each discovered API version + foreach (var description in provider.ApiVersionDescriptions) + { + options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant()); + } }); app.UseMvcWithDefaultRoute(); @@ -75,5 +145,25 @@ namespace Volo.Abp.Identity.HttpApi.Host return builder.Build(); } + + private static Info CreateInfoForApiVersion(ApiVersionDescription description) + { + var info = new Info() + { + Title = $"Sample API {description.ApiVersion}", + Version = description.ApiVersion.ToString(), + Description = "A sample application with Swagger, Swashbuckle, and API versioning.", + Contact = new Contact() { Name = "Bill Mei", Email = "bill.mei@somewhere.com" }, + TermsOfService = "Shareware", + License = new License() { Name = "MIT", Url = "https://opensource.org/licenses/MIT" } + }; + + if (description.IsDeprecated) + { + info.Description += " This API version has been deprecated."; + } + + return info; + } } } diff --git a/src/Volo.Abp.Identity.HttpApi.Host/SwaggerDefaultValues.cs b/src/Volo.Abp.Identity.HttpApi.Host/SwaggerDefaultValues.cs new file mode 100644 index 0000000000..f273dced70 --- /dev/null +++ b/src/Volo.Abp.Identity.HttpApi.Host/SwaggerDefaultValues.cs @@ -0,0 +1,41 @@ +using System.Linq; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Volo.Abp.Identity.HttpApi.Host +{ + /// + /// Represents the Swagger/Swashbuckle operation filter used to document the implicit API version parameter. + /// + /// This is only required due to bugs in the . + /// Once they are fixed and published, this class can be removed. + public class SwaggerDefaultValues : IOperationFilter + { + /// + /// Applies the filter to the specified operation using the given context. + /// + /// The operation to apply the filter to. + /// The current operation filter context. + public void Apply(Operation operation, OperationFilterContext context) + { + // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412 + // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413 + foreach (var parameter in operation.Parameters.OfType()) + { + var description = context.ApiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name); + + if (parameter.Description == null) + { + parameter.Description = description.ModelMetadata.Description; + } + + if (parameter.Default == null) + { + parameter.Default = description.RouteInfo?.DefaultValue; + } + + parameter.Required |= description.RouteInfo?.IsOptional == true; + } + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/CallsController.cs b/src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/CallsController.cs deleted file mode 100644 index cf590e964a..0000000000 --- a/src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/CallsController.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections.Generic; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.Application.Dtos; -using Volo.Abp.AspNetCore.Mvc; - -namespace Volo.Abp.Identity.HttpApi.Host.VersioningTests -{ - [Route("api/calls")] - public class CallsController : AbpController - { - private static List _calls = new List - { - new CallDto {Id = 1, Number = "123456"}, - new CallDto { Id = 2, Number = "123457" } - }; - - [HttpGet] - public List GetList() - { - return _calls; - } - } - - public class CallDto : EntityDto - { - public string Number { get; set; } - } -} diff --git a/src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/V1/CallDto.cs b/src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/V1/CallDto.cs new file mode 100644 index 0000000000..b8aa84f150 --- /dev/null +++ b/src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/V1/CallDto.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Application.Dtos; + +namespace Volo.Abp.Identity.HttpApi.Host.VersioningTests.V1 +{ + public class CallDto : EntityDto + { + public string Number { get; set; } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/V1/CallsController.cs b/src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/V1/CallsController.cs new file mode 100644 index 0000000000..a61d3ca4a2 --- /dev/null +++ b/src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/V1/CallsController.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Application.Services; +using Volo.Abp.AspNetCore.Mvc; + +namespace Volo.Abp.Identity.HttpApi.Host.VersioningTests.V1 +{ + [ApiVersion("1.0")] + [Route("api/v{apiVersion:apiVersion}/calls")] + public class CallsController : AbpController, IRemoteService + { + private static readonly List Calls = new List + { + new CallDto {Id = 1, Number = "123456"}, + new CallDto { Id = 2, Number = "123457" } + }; + + [HttpGet] + public List GetList() + { + return Calls; + } + } +} diff --git a/src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/V2/Calls2Controller.cs b/src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/V2/Calls2Controller.cs new file mode 100644 index 0000000000..808d177c00 --- /dev/null +++ b/src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/V2/Calls2Controller.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Application.Services; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Identity.HttpApi.Host.VersioningTests.V1; + +namespace Volo.Abp.Identity.HttpApi.Host.VersioningTests.V2 +{ + [ApiVersion("2.0")] + [Route("api/v{apiVersion:apiVersion}/calls")] + public class CallsController : AbpController, IRemoteService + { + private static List _calls = new List + { + new CallDto {Id = 1, Number = "123456000"}, + new CallDto { Id = 2, Number = "123457000" } + }; + + [HttpGet] + public List GetList() + { + return _calls; + } + + [HttpGet] + [Route("by-filter")] + public List GetList(string num) + { + return _calls.Where(c => c.Number.Contains(num)).ToList(); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Identity.HttpApi.Host/Volo.Abp.Identity.HttpApi.Host.csproj b/src/Volo.Abp.Identity.HttpApi.Host/Volo.Abp.Identity.HttpApi.Host.csproj index 581749f373..18f3574de5 100644 --- a/src/Volo.Abp.Identity.HttpApi.Host/Volo.Abp.Identity.HttpApi.Host.csproj +++ b/src/Volo.Abp.Identity.HttpApi.Host/Volo.Abp.Identity.HttpApi.Host.csproj @@ -26,6 +26,7 @@ + diff --git a/src/Volo.Abp.Identity.HttpApi/Volo.Abp.Identity.HttpApi.csproj b/src/Volo.Abp.Identity.HttpApi/Volo.Abp.Identity.HttpApi.csproj index 73ab6aa798..8763a68a14 100644 --- a/src/Volo.Abp.Identity.HttpApi/Volo.Abp.Identity.HttpApi.csproj +++ b/src/Volo.Abp.Identity.HttpApi/Volo.Abp.Identity.HttpApi.csproj @@ -13,6 +13,10 @@ + + + + diff --git a/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/AbpIdentityHttpApiModule.cs b/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/AbpIdentityHttpApiModule.cs index 30e3996bc1..5f9c285c9f 100644 --- a/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/AbpIdentityHttpApiModule.cs +++ b/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/AbpIdentityHttpApiModule.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.Modularity; @@ -14,10 +15,14 @@ namespace Volo.Abp.Identity services.Configure(options => { - options - .AppServiceControllers - .CreateFor(typeof(AbpIdentityApplicationModule).Assembly, "identity") - .NormalizeControllerNameInUrl(context => context.ControllerName.RemovePreFix("Identity")); + options.ConventionalControllers.Create(typeof(AbpIdentityApplicationModule).Assembly, opts => + { + opts.RootPath = "identity"; + opts.UrlControllerNameNormalizer = context => context.ControllerName.RemovePreFix("Identity"); + opts.ApiVersions.Add(new ApiVersion(2, 0)); + }); + + options.ConventionalControllers.Create(typeof(AbpIdentityHttpApiModule).Assembly); }); } } diff --git a/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/FixtureController.cs b/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/FixtureController.cs new file mode 100644 index 0000000000..9321177b8a --- /dev/null +++ b/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/FixtureController.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.ApiVersioning; +using Volo.Abp.Application.Services; +using Volo.Abp.AspNetCore.Mvc; + +namespace Volo.Abp.Identity +{ + //TODO: This is just a test controller and will be removed later + [ApiVersion("3.0")] + [ApiVersion("2.0", Deprecated = true)] + [Route("api/v{apiVersion:apiVersion}/identity/fixture")] + public class FixtureController : AbpController, IRemoteService + { + private readonly IRequestedApiVersion _requestedApiVersion; + + public FixtureController(IRequestedApiVersion requestedApiVersion) + { + _requestedApiVersion = requestedApiVersion; + } + + [HttpGet, MapToApiVersion("2.0")] + public string Get() + { + return 41 + " - " + _requestedApiVersion.Current; + } + + [HttpGet, MapToApiVersion("3.0")] + public string Get3() + { + return 42 + " - " + _requestedApiVersion.Current; + } + + [HttpPost] + public int Post() + { + return 43; + } + + [HttpPost] + [Route("{id}")] + public int Post(int id) + { + return id; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/V4/FixtureV4Controller.cs b/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/V4/FixtureV4Controller.cs new file mode 100644 index 0000000000..dc271bdbba --- /dev/null +++ b/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/V4/FixtureV4Controller.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.ApiVersioning; +using Volo.Abp.Application.Services; +using Volo.Abp.AspNetCore.Mvc; + +namespace Volo.Abp.Identity.V4 +{ + //TODO: This is just a test controller and will be removed later + [ApiVersion("4.0")] + [Route("api/v{apiVersion:apiVersion}/identity/fixture")] + public class FixtureController : AbpController, IRemoteService + { + private readonly IRequestedApiVersion _requestedApiVersion; + + public FixtureController(IRequestedApiVersion requestedApiVersion) + { + _requestedApiVersion = requestedApiVersion; + } + + [HttpGet] + public string Get() + { + return 41 + " - " + _requestedApiVersion.Current; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp/System/Collections/Generic/AbpDictionaryExtensions.cs b/src/Volo.Abp/System/Collections/Generic/AbpDictionaryExtensions.cs index 47ceca79f2..bac4b58217 100644 --- a/src/Volo.Abp/System/Collections/Generic/AbpDictionaryExtensions.cs +++ b/src/Volo.Abp/System/Collections/Generic/AbpDictionaryExtensions.cs @@ -26,6 +26,20 @@ namespace System.Collections.Generic return false; } + /// + /// Gets a value from the dictionary with given key. Returns default value if can not find. + /// + /// Dictionary to check and get + /// Key to find the value + /// Type of the key + /// Type of the value + /// Value if found, default if can not found. + public static TValue GetOrDefault(this Dictionary dictionary, TKey key) + { + TValue obj; + return dictionary.TryGetValue(key, out obj) ? obj : default(TValue); + } + /// /// Gets a value from the dictionary with given key. Returns default value if can not find. /// @@ -40,6 +54,20 @@ namespace System.Collections.Generic return dictionary.TryGetValue(key, out obj) ? obj : default(TValue); } + /// + /// Gets a value from the dictionary with given key. Returns default value if can not find. + /// + /// Dictionary to check and get + /// Key to find the value + /// Type of the key + /// Type of the value + /// Value if found, default if can not found. + public static TValue GetOrDefault(this IReadOnlyDictionary dictionary, TKey key) + { + TValue obj; + return dictionary.TryGetValue(key, out obj) ? obj : default(TValue); + } + /// /// Gets a value from the dictionary with given key. Returns default value if can not find. /// diff --git a/src/Volo.Abp/Volo/Abp/AbpKernelModule.cs b/src/Volo.Abp/Volo/Abp/AbpKernelModule.cs index 49966bad54..ad65a649b1 100644 --- a/src/Volo.Abp/Volo/Abp/AbpKernelModule.cs +++ b/src/Volo.Abp/Volo/Abp/AbpKernelModule.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.ApiVersioning; using Volo.Abp.Modularity; using Volo.Abp.ObjectMapping; using Volo.Abp.Reflection; @@ -33,6 +34,8 @@ namespace Volo.Abp services.AddLogging(); services.AddAssemblyOf(); + + services.AddSingleton(NullRequestedApiVersion.Instance); services.Configure(options => { diff --git a/src/Volo.Abp/Volo/Abp/ApiVersioning/IRequestedApiVersion.cs b/src/Volo.Abp/Volo/Abp/ApiVersioning/IRequestedApiVersion.cs new file mode 100644 index 0000000000..3d8a31c694 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/ApiVersioning/IRequestedApiVersion.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.ApiVersioning +{ + public interface IRequestedApiVersion + { + string Current { get; } + } +} diff --git a/src/Volo.Abp/Volo/Abp/ApiVersioning/NullRequestedApiVersion.cs b/src/Volo.Abp/Volo/Abp/ApiVersioning/NullRequestedApiVersion.cs new file mode 100644 index 0000000000..721fa91815 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/ApiVersioning/NullRequestedApiVersion.cs @@ -0,0 +1,14 @@ +namespace Volo.Abp.ApiVersioning +{ + public class NullRequestedApiVersion : IRequestedApiVersion + { + public static NullRequestedApiVersion Instance = new NullRequestedApiVersion(); + + public string Current => null; + + private NullRequestedApiVersion() + { + + } + } +} \ No newline at end of file diff --git a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AbpAspNetCoreMvcTestModule.cs b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs similarity index 67% rename from test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AbpAspNetCoreMvcTestModule.cs rename to test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs index a3d999671e..5e2e8e2986 100644 --- a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AbpAspNetCoreMvcTestModule.cs +++ b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs @@ -2,14 +2,13 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Modularity; -using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.TestBase; using Volo.Abp.Autofac; using Volo.Abp.MemoryDb; using Volo.Abp.Modularity; using Volo.Abp.TestApp; -namespace Volo.Abp.AspNetCore.App +namespace Volo.Abp.AspNetCore.Mvc { [DependsOn( typeof(AbpAspNetCoreTestBaseModule), @@ -25,15 +24,13 @@ namespace Volo.Abp.AspNetCore.App services.Configure(options => { - options - .AppServiceControllers - .CreateFor(typeof(TestAppModule).Assembly) - .NormalizeActionNameInUrl( - context => - string.Equals(context.ActionNameInUrl, "phone", StringComparison.OrdinalIgnoreCase) - ? "phones" - : context.ActionNameInUrl - ); + options.ConventionalControllers.Create(typeof(TestAppModule).Assembly, opts => + { + opts.UrlActionNameNormalizer = context => + string.Equals(context.ActionNameInUrl, "phone", StringComparison.OrdinalIgnoreCase) + ? "phones" + : context.ActionNameInUrl; + }); }); services.AddAssemblyOf(); diff --git a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/Startup.cs b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Startup.cs similarity index 95% rename from test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/Startup.cs rename to test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Startup.cs index 59bff4b91c..453ae00295 100644 --- a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/Startup.cs +++ b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Startup.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Volo.Abp.AspNetCore.App +namespace Volo.Abp.AspNetCore.Mvc { public class Startup { diff --git a/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo.Abp.AspNetCore.Mvc.Versioning.Tests.csproj b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo.Abp.AspNetCore.Mvc.Versioning.Tests.csproj new file mode 100644 index 0000000000..3db24955e1 --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo.Abp.AspNetCore.Mvc.Versioning.Tests.csproj @@ -0,0 +1,48 @@ + + + + netcoreapp2.0 + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + Volo.Abp.AspNetCore.Mvc.Versioning.Tests + Volo.Abp.AspNetCore.Mvc.Versioning.Tests + true + true + false + false + false + true + true + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + + + + + + + + + + diff --git a/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/AbpAspNetCoreMvcVersioningTestModule.cs b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/AbpAspNetCoreMvcVersioningTestModule.cs new file mode 100644 index 0000000000..ad035e8edd --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/AbpAspNetCoreMvcVersioningTestModule.cs @@ -0,0 +1,69 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Versioning; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Modularity; +using Volo.Abp.AspNetCore.TestBase; +using Volo.Abp.Autofac; +using Volo.Abp.Http.Client; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Mvc.Versioning +{ + [DependsOn( + typeof(AbpAspNetCoreTestBaseModule), + typeof(AbpAspNetCoreMvcModule), + typeof(AbpAutofacModule), + typeof(AbpHttpClientModule) + )] + public class AbpAspNetCoreMvcVersioningTestModule : AbpModule + { + public override void ConfigureServices(IServiceCollection services) + { + services.AddMvc(); + + services.Configure(options => + { + //2.0 Version + options.ConventionalControllers.Create(typeof(AbpAspNetCoreMvcVersioningTestModule).Assembly, opts => + { + opts.TypePredicate = t => t.Namespace == typeof(Volo.Abp.AspNetCore.Mvc.Versioning.App.TodoAppService).Namespace; + opts.ApiVersions.Add(new ApiVersion(2, 0)); + }); + + //1.0 Compatability version + options.ConventionalControllers.Create(typeof(AbpAspNetCoreMvcVersioningTestModule).Assembly, opts => + { + opts.TypePredicate = t => t.Namespace == typeof(Volo.Abp.AspNetCore.Mvc.Versioning.App.Compat.TodoAppService).Namespace; + opts.ApiVersions.Add(new ApiVersion(1, 0)); + }); + }); + + services.AddApiVersioning(options => + { + options.ReportApiVersions = true; + options.AssumeDefaultVersionWhenUnspecified = true; + + //options.ApiVersionReader = new HeaderApiVersionReader("api-version"); //Supports header too + //options.ApiVersionReader = new MediaTypeApiVersionReader(); //Supports accept header too + + options.ConfigureAbp(services); + }); + + services.AddAssemblyOf(); + + services.AddHttpClientProxies(typeof(AbpAspNetCoreMvcVersioningTestModule).Assembly); + + services.Configure(options => + { + options.RemoteServices.Default = new RemoteServiceConfiguration("/"); + }); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var app = context.GetApplicationBuilder(); + app.UseMvcWithDefaultRoute(); + } + } +} diff --git a/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/Compat/ITodoAppService.cs b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/Compat/ITodoAppService.cs new file mode 100644 index 0000000000..0a8ed290ce --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/Compat/ITodoAppService.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Application.Services; + +namespace Volo.Abp.AspNetCore.Mvc.Versioning.App.Compat +{ + public interface ITodoAppService : IApplicationService + { + string Get(int id); + } +} \ No newline at end of file diff --git a/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/Compat/TodoAppService.cs b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/Compat/TodoAppService.cs new file mode 100644 index 0000000000..83835fa9b0 --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/Compat/TodoAppService.cs @@ -0,0 +1,24 @@ +using Volo.Abp.ApiVersioning; + +namespace Volo.Abp.AspNetCore.Mvc.Versioning.App.Compat +{ + public class TodoAppService : ITodoAppService + { + private readonly IRequestedApiVersion _requestedApiVersion; + + public TodoAppService(IRequestedApiVersion requestedApiVersion) + { + _requestedApiVersion = requestedApiVersion; + } + + public string Get(int id) + { + return "Compat-" + id + "-" + GetVersionOrNone(); + } + + private string GetVersionOrNone() + { + return _requestedApiVersion.Current ?? "NONE"; + } + } +} diff --git a/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/HelloController.cs b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/HelloController.cs new file mode 100644 index 0000000000..fb39ae53ed --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/HelloController.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace Volo.Abp.AspNetCore.Mvc.Versioning.App +{ + [ApiVersion("1.0")] + [ApiVersion("2.0")] + [Route("api/v{apiVersion:apiVersion}/[controller]")] + public class HelloController : AbpController, IHelloController + { + [HttpPost] + public Task PostAsync() + { + return Task.FromResult($"42-{HttpContext.GetRequestedApiVersion().ToString()}"); + } + } +} \ No newline at end of file diff --git a/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/IHelloController.cs b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/IHelloController.cs new file mode 100644 index 0000000000..5f43027c8e --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/IHelloController.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace Volo.Abp.AspNetCore.Mvc.Versioning.App +{ + public interface IHelloController : IRemoteService + { + Task PostAsync(); + } +} diff --git a/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/ITodoAppService.cs b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/ITodoAppService.cs new file mode 100644 index 0000000000..f4de87629d --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/ITodoAppService.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Application.Services; + +namespace Volo.Abp.AspNetCore.Mvc.Versioning.App +{ + public interface ITodoAppService : IApplicationService + { + string Get(int id); + } +} \ No newline at end of file diff --git a/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/TodoAppService.cs b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/TodoAppService.cs new file mode 100644 index 0000000000..259ea767bd --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/TodoAppService.cs @@ -0,0 +1,24 @@ +using Volo.Abp.ApiVersioning; + +namespace Volo.Abp.AspNetCore.Mvc.Versioning.App +{ + public class TodoAppService : ITodoAppService + { + private readonly IRequestedApiVersion _requestedApiVersion; + + public TodoAppService(IRequestedApiVersion requestedApiVersion) + { + _requestedApiVersion = requestedApiVersion; + } + + public string Get(int id) + { + return id + "-" + GetVersionOrNone(); + } + + private string GetVersionOrNone() + { + return _requestedApiVersion.Current ?? "NONE"; + } + } +} diff --git a/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/AspNetCoreMvcVersioningTestBase.cs b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/AspNetCoreMvcVersioningTestBase.cs new file mode 100644 index 0000000000..48a926cbf0 --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/AspNetCoreMvcVersioningTestBase.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.AspNetCore.Mvc.Versioning +{ + public abstract class AspNetCoreMvcVersioningTestBase : AbpAspNetCoreTestBase + { + } +} \ No newline at end of file diff --git a/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Startup.cs b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Startup.cs new file mode 100644 index 0000000000..4d9f831670 --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Startup.cs @@ -0,0 +1,27 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Volo.Abp.AspNetCore.Mvc.Versioning +{ + public class Startup + { + public IServiceProvider ConfigureServices(IServiceCollection services) + { + services.AddApplication(options => + { + options.UseAutofac(); + }); + + //TODO: This is needed because ASP.NET Core does not use IServiceProviderFactory! + return services.BuildServiceProviderFromFactory(); + } + + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + app.InitializeApplication(); + } + } +} diff --git a/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/Compat/TodoAppService_Tests.cs b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/Compat/TodoAppService_Tests.cs new file mode 100644 index 0000000000..0ce095c470 --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/Compat/TodoAppService_Tests.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.AspNetCore.Mvc.Versioning.App.Compat; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Versioning.Test.Compat +{ + public class TodoAppService_Tests : AspNetCoreMvcVersioningTestBase + { + private readonly ITodoAppService _todoAppService; + + public TodoAppService_Tests() + { + _todoAppService = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void Get() + { + _todoAppService.Get(42).ShouldBe("Compat-42-1.0"); + } + } +} diff --git a/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/HelloController_Tests.cs b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/HelloController_Tests.cs new file mode 100644 index 0000000000..519bfffd11 --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/HelloController_Tests.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.AspNetCore.Mvc.Versioning.App; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Versioning.Test +{ + public class HelloController_Tests: AspNetCoreMvcVersioningTestBase + { + private readonly IHelloController _todoAppService; + + public HelloController_Tests() + { + _todoAppService = ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task PostAsync() + { + (await _todoAppService.PostAsync()).ShouldBe("42-2.0"); + } + } +} diff --git a/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/TodoAppService_Tests.cs b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/TodoAppService_Tests.cs new file mode 100644 index 0000000000..90ea5970b0 --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/TodoAppService_Tests.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.AspNetCore.Mvc.Versioning.App; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Versioning.Test +{ + public class TodoAppService_Tests : AspNetCoreMvcVersioningTestBase + { + private readonly ITodoAppService _todoAppService; + + public TodoAppService_Tests() + { + _todoAppService = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void Get() + { + _todoAppService.Get(42).ShouldBe("42-2.0"); + } + } +} diff --git a/test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AppTestBase.cs b/test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AbpAspNetCoreTestBase.cs similarity index 100% rename from test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AppTestBase.cs rename to test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AbpAspNetCoreTestBase.cs diff --git a/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpTestBase.cs b/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpTestBase.cs index f950193109..d2db4dc3ac 100644 --- a/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpTestBase.cs +++ b/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpTestBase.cs @@ -1,5 +1,4 @@ using Volo.Abp.AspNetCore; -using Volo.Abp.AspNetCore.App; namespace Volo.Abp.Http { diff --git a/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpTestModule.cs b/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpTestModule.cs index a1645af33a..abe8065b75 100644 --- a/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpTestModule.cs +++ b/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpTestModule.cs @@ -1,5 +1,5 @@ using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.AspNetCore.App; +using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.Http.Client; using Volo.Abp.Http.DynamicProxying; using Volo.Abp.Modularity;