Browse Source

Merge pull request #122 from aspnetzero/api-versioning

Api versioning
pull/129/head
Halil İbrahim Kalkan 9 years ago
committed by GitHub
parent
commit
063df99e0f
  1. 11
      Volo.Abp.sln
  2. 2
      src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs
  3. 73
      src/Volo.Abp.AspNetCore.Mvc/Microsoft/Extensions/DependencyInjection/AbpApiVersioningOptionsExtensions.cs
  4. 1
      src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj
  5. 41
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceControllerFeatureProvider.cs
  6. 24
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs
  7. 8
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs
  8. 36
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySetting.cs
  9. 39
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySettingBuilder.cs
  10. 3
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs
  11. 6
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs
  12. 32
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AppServiceControllerOptions.cs
  13. 62
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs
  14. 16
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ControllerAssemblySettingList.cs
  15. 29
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalControllerFeatureProvider.cs
  16. 44
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs
  17. 10
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConventionWrapper.cs
  18. 35
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerOptions.cs
  19. 91
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSetting.cs
  20. 16
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSettingList.cs
  21. 8
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/IAbpServiceConvention.cs
  22. 2
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/UrlActionNameNormalizerContext.cs
  23. 15
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/UrlControllerNameNormalizerContext.cs
  24. 8
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IAbpAppServiceConvention.cs
  25. 12
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IAbpControllerAssemblySettingBuilder.cs
  26. 24
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/UrlControllerNameNormalizerContext.cs
  27. 18
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Versioning/HttpContextRequestedApiVersion.cs
  28. 1
      src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs
  29. 2
      src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionCache.cs
  30. 6
      src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs
  31. 22
      src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiVersionInfo.cs
  32. 90
      src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs
  33. 2
      src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/HttpActionParameterHelper.cs
  34. 2
      src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/IApiDescriptionFinder.cs
  35. 3
      src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs
  36. 38
      src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs
  37. 5
      src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfiguration.cs
  38. 23
      src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs
  39. 26
      src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApiDescriptionModelOptions.cs
  40. 25
      src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs
  41. 5
      src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ModuleApiDescriptionModel.cs
  42. 2
      src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs
  43. 105
      src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator.cs
  44. 2
      src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingHelper.cs
  45. 2
      src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs
  46. 104
      src/Volo.Abp.Identity.HttpApi.Host/AbpIdentityHttpApiHostModule.cs
  47. 41
      src/Volo.Abp.Identity.HttpApi.Host/SwaggerDefaultValues.cs
  48. 28
      src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/CallsController.cs
  49. 9
      src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/V1/CallDto.cs
  50. 24
      src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/V1/CallsController.cs
  51. 33
      src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/V2/Calls2Controller.cs
  52. 1
      src/Volo.Abp.Identity.HttpApi.Host/Volo.Abp.Identity.HttpApi.Host.csproj
  53. 4
      src/Volo.Abp.Identity.HttpApi/Volo.Abp.Identity.HttpApi.csproj
  54. 13
      src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/AbpIdentityHttpApiModule.cs
  55. 46
      src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/FixtureController.cs
  56. 26
      src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/V4/FixtureV4Controller.cs
  57. 28
      src/Volo.Abp/System/Collections/Generic/AbpDictionaryExtensions.cs
  58. 3
      src/Volo.Abp/Volo/Abp/AbpKernelModule.cs
  59. 7
      src/Volo.Abp/Volo/Abp/ApiVersioning/IRequestedApiVersion.cs
  60. 14
      src/Volo.Abp/Volo/Abp/ApiVersioning/NullRequestedApiVersion.cs
  61. 19
      test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs
  62. 2
      test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Startup.cs
  63. 48
      test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo.Abp.AspNetCore.Mvc.Versioning.Tests.csproj
  64. 69
      test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/AbpAspNetCoreMvcVersioningTestModule.cs
  65. 9
      test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/Compat/ITodoAppService.cs
  66. 24
      test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/Compat/TodoAppService.cs
  67. 17
      test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/HelloController.cs
  68. 10
      test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/IHelloController.cs
  69. 9
      test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/ITodoAppService.cs
  70. 24
      test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/TodoAppService.cs
  71. 6
      test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/AspNetCoreMvcVersioningTestBase.cs
  72. 27
      test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Startup.cs
  73. 23
      test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/Compat/TodoAppService_Tests.cs
  74. 24
      test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/HelloController_Tests.cs
  75. 23
      test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/TodoAppService_Tests.cs
  76. 0
      test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AbpAspNetCoreTestBase.cs
  77. 1
      test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpTestBase.cs
  78. 2
      test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpTestModule.cs

11
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}

2
src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs

@ -65,7 +65,7 @@ namespace AbpDesk.Web.Mvc
services.Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.AppServiceControllers.CreateFor(typeof(AbpDeskApplicationModule).Assembly);
options.ConventionalControllers.Create(typeof(AbpDeskApplicationModule).Assembly);
});
}

73
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<IRequestedApiVersion, HttpContextRequestedApiVersion>();
services.Configure<AbpAspNetCoreMvcOptions>(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);
}
}
}
}
}
}

1
src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj

@ -20,6 +20,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="2.0.0" />
</ItemGroup>
</Project>

41
src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceControllerFeatureProvider.cs

@ -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<RemoteServiceAttribute>(typeInfo);
if (remoteServiceAttr != null && !remoteServiceAttr.IsEnabledFor(type))
{
return false;
}
//TODO: Move this to a lazy loaded field for efficiency.
var configuration = _application.ServiceProvider.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>().Value.AppServiceControllers.ControllerAssemblySettings.GetSettingOrNull(type);
return configuration != null && (configuration.TypePredicate == null || configuration.TypePredicate(type));
}
}
}

24
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<ApiDescriptionModelOptions>(options =>
{
options.IgnoredInterfaces.AddIfNotContains(typeof(IAsyncActionFilter));
options.IgnoredInterfaces.AddIfNotContains(typeof(IFilterMetadata));
options.IgnoredInterfaces.AddIfNotContains(typeof(IActionFilter));
});
services.Configure<AbpAspNetCoreMvcOptions>(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<ApplicationPartManager>();
var application = services.GetSingletonInstance<IAbpApplication>();
partManager.FeatureProviders.Add(new AbpAppServiceControllerFeatureProvider(application));
partManager.FeatureProviders.Add(new AbpConventionalControllerFeatureProvider(application));
services.Configure<MvcOptions>(mvcOptions =>
{
@ -97,8 +115,8 @@ namespace Volo.Abp.AspNetCore.Mvc
.ServiceProvider
.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>()
.Value
.AppServiceControllers
.ControllerAssemblySettings
.ConventionalControllers
.ConventionalControllerSettings
.Select(s => s.Assembly)
.Distinct();

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

36
src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySetting.cs

@ -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<Type, bool> TypePredicate { get; set; }
[CanBeNull]
public Action<ControllerModel> ControllerModelConfigurer { get; set; }
[CanBeNull]
public Func<UrlControllerNameNormalizerContext, string> UrlControllerNameNormalizer { get; set; }
[CanBeNull]
public Func<UrlActionNameNormalizerContext, string> UrlActionNameNormalizer { get; set; }
public AbpControllerAssemblySetting([NotNull] Assembly assembly, [NotNull] string rootPath)
{
Check.NotNull(assembly, rootPath);
Assembly = assembly;
RootPath = rootPath;
}
}
}

39
src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySettingBuilder.cs

@ -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<Type, bool> predicate)
{
_setting.TypePredicate = predicate;
return this;
}
public AbpControllerAssemblySettingBuilder ConfigureControllerModel(Action<ControllerModel> configurer)
{
_setting.ControllerModelConfigurer = configurer;
return this;
}
public AbpControllerAssemblySettingBuilder NormalizeControllerNameInUrl(Func<UrlControllerNameNormalizerContext, string> normalizer)
{
_setting.UrlControllerNameNormalizer = normalizer;
return this;
}
public AbpControllerAssemblySettingBuilder NormalizeActionNameInUrl(Func<UrlActionNameNormalizerContext, string> normalizer)
{
_setting.UrlActionNameNormalizer = normalizer;
return this;
}
}
}

3
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)

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

32
src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AppServiceControllerOptions.cs

@ -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<Type> FormBodyBindingIgnoredTypes { get; }
public AppServiceControllerOptions()
{
ControllerAssemblySettings = new ControllerAssemblySettingList();
FormBodyBindingIgnoredTypes = new List<Type>
{
typeof(IFormFile)
};
}
public AbpControllerAssemblySettingBuilder CreateFor(Assembly assembly, string rootPath = ModuleApiDescriptionModel.DefaultRootPath)
{
var setting = new AbpControllerAssemblySetting(assembly, rootPath);
ControllerAssemblySettings.Add(setting);
return new AbpControllerAssemblySettingBuilder(setting);
}
}
}

62
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<AbpAspNetCoreMvcOptions> options)
IOptions<AbpAspNetCoreMvcOptions> options,
IOptions<ApiDescriptionModelOptions> modelOptions)
{
_descriptionProvider = descriptionProvider;
_options = options.Value;
_modelOptions = modelOptions.Value;
Logger = NullLogger<AspNetCoreApiDescriptionModelProvider>.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<string>() //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<AreaAttribute>().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;
}
}
}

16
src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ControllerAssemblySettingList.cs

@ -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<AbpControllerAssemblySetting>
{
[CanBeNull]
public AbpControllerAssemblySetting GetSettingOrNull(Type controllerType)
{
return this.FirstOrDefault(controllerSetting => controllerSetting.Assembly == controllerType.Assembly);
}
}
}

29
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<IOptions<AbpAspNetCoreMvcOptions>>().Value
.ConventionalControllers
.ConventionalControllerSettings
.GetSettingOrNull(typeInfo.AsType());
return configuration != null;
}
}
}

44
src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConvention.cs → 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<AbpAspNetCoreMvcOptions> options)
public AbpServiceConvention(IOptions<AbpAspNetCoreMvcOptions> 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);
}

10
src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConventionWrapper.cs → 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<IAbpAppServiceConvention> _convention;
private readonly Lazy<IAbpServiceConvention> _convention;
public AbpAppServiceConventionWrapper(IServiceCollection services)
public AbpServiceConventionWrapper(IServiceCollection services)
{
_convention = services.GetRequiredServiceLazy<IAbpAppServiceConvention>();
_convention = services.GetRequiredServiceLazy<IAbpServiceConvention>();
}
public void Apply(ApplicationModel application)

35
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<Type> FormBodyBindingIgnoredTypes { get; }
public ConventionalControllerOptions()
{
ConventionalControllerSettings = new ConventionalControllerSettingList();
FormBodyBindingIgnoredTypes = new List<Type>
{
typeof(IFormFile)
};
}
public ConventionalControllerOptions Create(Assembly assembly, [CanBeNull] Action<ConventionalControllerSetting> optionsAction = null)
{
var setting = new ConventionalControllerSetting(assembly, ModuleApiDescriptionModel.DefaultRootPath);
optionsAction?.Invoke(setting);
setting.Initialize();
ConventionalControllerSettings.Add(setting);
return this;
}
}
}

91
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<Type> ControllerTypes { get; }
[NotNull]
public string RootPath
{
get => _rootPath;
set
{
Check.NotNull(value, nameof(value));
_rootPath = value;
}
}
private string _rootPath;
[CanBeNull]
public Func<Type, bool> TypePredicate { get; set; }
[CanBeNull]
public Action<ControllerModel> ControllerModelConfigurer { get; set; }
[CanBeNull]
public Func<UrlControllerNameNormalizerContext, string> UrlControllerNameNormalizer { get; set; }
[CanBeNull]
public Func<UrlActionNameNormalizerContext, string> UrlActionNameNormalizer { get; set; }
public List<ApiVersion> ApiVersions { get; }
public Action<ApiVersioningOptions> ApiVersionConfigurer { get; set; }
public ConventionalControllerSetting([NotNull] Assembly assembly, [NotNull] string rootPath)
{
Check.NotNull(assembly, rootPath);
Assembly = assembly;
RootPath = rootPath;
ControllerTypes = new HashSet<Type>();
ApiVersions = new List<ApiVersion>();
}
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<RemoteServiceAttribute>(type);
if (remoteServiceAttr != null && !remoteServiceAttr.IsEnabledFor(type))
{
return false;
}
return true;
}
}
}

16
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<ConventionalControllerSetting>
{
[CanBeNull]
public ConventionalControllerSetting GetSettingOrNull(Type controllerType)
{
return this.FirstOrDefault(controllerSetting => controllerSetting.ControllerTypes.Contains(controllerType));
}
}
}

8
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
{
}
}

2
src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/UrlActionNameNormalizerContext.cs → 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
{

15
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;
}
}
}

8
src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IAbpAppServiceConvention.cs

@ -1,8 +0,0 @@
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace Volo.Abp.AspNetCore.Mvc
{
public interface IAbpAppServiceConvention : IApplicationModelConvention
{
}
}

12
src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IAbpControllerAssemblySettingBuilder.cs

@ -1,12 +0,0 @@
using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace Volo.Abp.AspNetCore.Mvc
{
public interface IAbpControllerAssemblySettingBuilder
{
AbpControllerAssemblySettingBuilder Where(Func<Type, bool> predicate);
AbpControllerAssemblySettingBuilder ConfigureControllerModel(Action<ControllerModel> configurer);
}
}

24
src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/UrlControllerNameNormalizerContext.cs

@ -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;
}
}
}

18
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;
}
}
}

1
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 =>

2
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!");

6
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<ActionApiDescriptionModel> FindActionAsync(RemoteServiceConfiguration proxyConfig, Type serviceType, MethodInfo method)
public async Task<ActionApiDescriptionModel> 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;

22
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");
}
}
}

90
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)
{

2
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<string, object> methodArguments, ParameterApiDescriptionModel apiParameter)
{
var value = methodArguments[apiParameter.NameOnMethod];
var value = methodArguments.GetOrDefault(apiParameter.NameOnMethod);
if (value == null)
{
return null;

2
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<ActionApiDescriptionModel> FindActionAsync(RemoteServiceConfiguration proxyConfig, Type serviceType, MethodInfo invocationMethod);
Task<ActionApiDescriptionModel> FindActionAsync(string baseUrl, Type serviceType, MethodInfo invocationMethod);
}
}

3
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<string, object> methodArguments, IJsonSerializer jsonSerializer)
public static HttpContent BuildContent(ActionApiDescriptionModel action,IReadOnlyDictionary<string, object> 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

38
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<string, object> methodArguments)
public static string GenerateUrlWithParameters(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> 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<ParameterApiDescriptionModel> actionParameters, IReadOnlyDictionary<string, object> methodArguments)
private static void ReplacePathVariables(StringBuilder urlBuilder, IList<ParameterApiDescriptionModel> actionParameters, IReadOnlyDictionary<string, object> 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<ParameterApiDescriptionModel> actionParameters, IReadOnlyDictionary<string, object> methodArguments)
private static void AddQueryStringParameters(StringBuilder urlBuilder, IList<ParameterApiDescriptionModel> actionParameters, IReadOnlyDictionary<string, object> 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()));
}
}
}

5
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;
}
}
}

23
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<string> SupportedVersions { get; set; }
public IList<MethodParameterApiDescriptionModel> ParametersOnMethod { get; set; }
public IList<ParameterApiDescriptionModel> 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<string> 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}]";
}
}
}

26
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<Type> IgnoredInterfaces { get; }
public ApiDescriptionModelOptions()
{
IgnoredInterfaces = new HashSet<Type>
{
typeof(IApplicationService),
typeof(IRemoteService),
typeof(ITransientDependency),
typeof(ISingletonDependency),
typeof(IDisposable),
typeof(IAvoidDuplicateCrossCuttingConcerns)
};
}
}
}

25
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<Type> ignoredInterfaces = null)
{
return new ControllerApiDescriptionModel
{
@ -29,21 +30,22 @@ namespace Volo.Abp.Http.Modeling
Actions = new Dictionary<string, ActionApiDescriptionModel>(),
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<string, ActionApiDescriptionModel>()
};
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}]";
}
}
}

5
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<Type> 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)

2
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,

105
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<ActionApiDescriptionModel, string> CalculateNormalizedActionNames(Dictionary<string, ActionApiDescriptionModel> actions)
{
var result = new Dictionary<ActionApiDescriptionModel, string>();
var actionsByName = new Dictionary<string, List<ActionApiDescriptionModel>>();
foreach (var action in actions.Values)
{
var actionName = action.Name.RemovePostFix("Async").ToCamelCase();
result[action] = actionName;
actionsByName.GetOrAdd(actionName, () => new List<ActionApiDescriptionModel>()).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(".");
}
}
}

2
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!"
);
}

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

104
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<IHostingEnvironment>();
var hostingEnvironment = services.GetSingletonInstance<IHostingEnvironment>(); //TOD: Move to BuildConfiguration method
var configuration = BuildConfiguration(hostingEnvironment);
services.Configure<DbConnectionOptions>(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<AbpApiDefinitionController>().Action((MethodInfo)null).MapToApiVersion(new ApiVersion(1,1),).IsApiVersionNeutral();
//o.Conventions.Controller<AbpApiDefinitionController>().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<IdentityUserAppService>().HasApiVersion(2, 0);
//o.Conventions.Controller<IdentityRoleAppService>().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<IApiVersionDescriptionProvider>();
// 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<SwaggerDefaultValues>();
// integrate xml comments
//options.IncludeXmlComments(XmlCommentsFilePath); //TODO: Add XML comments!
});
services.Configure<AbpAspNetCoreMvcOptions>(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<AbpIdentityHttpApiHostModule>();
@ -57,10 +117,20 @@ namespace Volo.Abp.Identity.HttpApi.Host
app.UseStaticFiles();
var provider = context.ServiceProvider.GetRequiredService<IApiVersionDescriptionProvider>();
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;
}
}
}

41
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
{
/// <summary>
/// Represents the Swagger/Swashbuckle operation filter used to document the implicit API version parameter.
/// </summary>
/// <remarks>This <see cref="IOperationFilter"/> is only required due to bugs in the <see cref="SwaggerGenerator"/>.
/// Once they are fixed and published, this class can be removed.</remarks>
public class SwaggerDefaultValues : IOperationFilter
{
/// <summary>
/// Applies the filter to the specified operation using the given context.
/// </summary>
/// <param name="operation">The operation to apply the filter to.</param>
/// <param name="context">The current operation filter context.</param>
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<NonBodyParameter>())
{
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;
}
}
}
}

28
src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/CallsController.cs

@ -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<CallDto> _calls = new List<CallDto>
{
new CallDto {Id = 1, Number = "123456"},
new CallDto { Id = 2, Number = "123457" }
};
[HttpGet]
public List<CallDto> GetList()
{
return _calls;
}
}
public class CallDto : EntityDto<int>
{
public string Number { get; set; }
}
}

9
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<int>
{
public string Number { get; set; }
}
}

24
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<CallDto> Calls = new List<CallDto>
{
new CallDto {Id = 1, Number = "123456"},
new CallDto { Id = 2, Number = "123457" }
};
[HttpGet]
public List<CallDto> GetList()
{
return Calls;
}
}
}

33
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<CallDto> _calls = new List<CallDto>
{
new CallDto {Id = 1, Number = "123456000"},
new CallDto { Id = 2, Number = "123457000" }
};
[HttpGet]
public List<CallDto> GetList()
{
return _calls;
}
[HttpGet]
[Route("by-filter")]
public List<CallDto> GetList(string num)
{
return _calls.Where(c => c.Number.Contains(num)).ToList();
}
}
}

1
src/Volo.Abp.Identity.HttpApi.Host/Volo.Abp.Identity.HttpApi.Host.csproj

@ -26,6 +26,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.0.0" />

4
src/Volo.Abp.Identity.HttpApi/Volo.Abp.Identity.HttpApi.csproj

@ -13,6 +13,10 @@
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" />
<ProjectReference Include="..\Volo.Abp.Identity.Application\Volo.Abp.Identity.Application.csproj" />

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

46
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;
}
}
}

26
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;
}
}
}

28
src/Volo.Abp/System/Collections/Generic/AbpDictionaryExtensions.cs

@ -26,6 +26,20 @@ namespace System.Collections.Generic
return false;
}
/// <summary>
/// Gets a value from the dictionary with given key. Returns default value if can not find.
/// </summary>
/// <param name="dictionary">Dictionary to check and get</param>
/// <param name="key">Key to find the value</param>
/// <typeparam name="TKey">Type of the key</typeparam>
/// <typeparam name="TValue">Type of the value</typeparam>
/// <returns>Value if found, default if can not found.</returns>
public static TValue GetOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key)
{
TValue obj;
return dictionary.TryGetValue(key, out obj) ? obj : default(TValue);
}
/// <summary>
/// Gets a value from the dictionary with given key. Returns default value if can not find.
/// </summary>
@ -40,6 +54,20 @@ namespace System.Collections.Generic
return dictionary.TryGetValue(key, out obj) ? obj : default(TValue);
}
/// <summary>
/// Gets a value from the dictionary with given key. Returns default value if can not find.
/// </summary>
/// <param name="dictionary">Dictionary to check and get</param>
/// <param name="key">Key to find the value</param>
/// <typeparam name="TKey">Type of the key</typeparam>
/// <typeparam name="TValue">Type of the value</typeparam>
/// <returns>Value if found, default if can not found.</returns>
public static TValue GetOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, TKey key)
{
TValue obj;
return dictionary.TryGetValue(key, out obj) ? obj : default(TValue);
}
/// <summary>
/// Gets a value from the dictionary with given key. Returns default value if can not find.
/// </summary>

3
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<AbpKernelModule>();
services.AddSingleton<IRequestedApiVersion>(NullRequestedApiVersion.Instance);
services.Configure<ModuleLifecycleOptions>(options =>
{

7
src/Volo.Abp/Volo/Abp/ApiVersioning/IRequestedApiVersion.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.ApiVersioning
{
public interface IRequestedApiVersion
{
string Current { get; }
}
}

14
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()
{
}
}
}

19
test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AbpAspNetCoreMvcTestModule.cs → 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<AbpAspNetCoreMvcOptions>(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<AbpAspNetCoreMvcTestModule>();

2
test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/Startup.cs → 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
{

48
test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo.Abp.AspNetCore.Mvc.Versioning.Tests.csproj

@ -0,0 +1,48 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<AssemblyName>Volo.Abp.AspNetCore.Mvc.Versioning.Tests</AssemblyName>
<PackageId>Volo.Abp.AspNetCore.Mvc.Versioning.Tests</PackageId>
<PreserveCompilationContext>true</PreserveCompilationContext>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\Volo.Abp.AspNetCore.Tests\Volo.Abp.AspNetCore.Tests.csproj" />
</ItemGroup>
<!-- Below ItemGroup and Target tags are added according to https://github.com/aspnet/Hosting/issues/959#issuecomment-286351703 -->
<!-- Solves Problem#2 (404 when executing service calls hosted in other assemblies) -->
<!-- https://github.com/Microsoft/vstest/issues/196.-->
<ItemGroup>
<None Update="xunit.runner.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<!-- https://github.com/NuGet/Home/issues/4412. -->
<Target Name="CopyDepsFiles" AfterTargets="Build" Condition="'$(TargetFramework)'!=''">
<ItemGroup>
<DepsFilePaths Include="$([System.IO.Path]::ChangeExtension('%(_ResolvedProjectReferencePaths.FullPath)', '.deps.json'))" />
</ItemGroup>
<Copy SourceFiles="%(DepsFilePaths.FullPath)" DestinationFolder="$(OutputPath)" Condition="Exists('%(DepsFilePaths.FullPath)')" />
</Target>
</Project>

69
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<AbpAspNetCoreMvcOptions>(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<AbpAspNetCoreMvcVersioningTestModule>();
services.AddHttpClientProxies(typeof(AbpAspNetCoreMvcVersioningTestModule).Assembly);
services.Configure<RemoteServiceOptions>(options =>
{
options.RemoteServices.Default = new RemoteServiceConfiguration("/");
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
app.UseMvcWithDefaultRoute();
}
}
}

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

24
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";
}
}
}

17
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<string> PostAsync()
{
return Task.FromResult($"42-{HttpContext.GetRequestedApiVersion().ToString()}");
}
}
}

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

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

24
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";
}
}
}

6
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<Startup>
{
}
}

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

23
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<ITodoAppService>();
}
[Fact]
public void Get()
{
_todoAppService.Get(42).ShouldBe("Compat-42-1.0");
}
}
}

24
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<IHelloController>();
}
[Fact]
public async Task PostAsync()
{
(await _todoAppService.PostAsync()).ShouldBe("42-2.0");
}
}
}

23
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<ITodoAppService>();
}
[Fact]
public void Get()
{
_todoAppService.Get(42).ShouldBe("42-2.0");
}
}
}

0
test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AppTestBase.cs → test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AbpAspNetCoreTestBase.cs

1
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
{

2
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;

Loading…
Cancel
Save