Browse Source

Add configuration to set version to all app services in a module.

pull/122/head
Halil İbrahim Kalkan 9 years ago
parent
commit
01fead68a5
  1. 2
      src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs
  2. 33
      src/Volo.Abp.AspNetCore.Mvc/Microsoft/Extensions/DependencyInjection/AbpApiVersioningOptionsExtensions.cs
  3. 1
      src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj
  4. 7
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceControllerFeatureProvider.cs
  5. 51
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySetting.cs
  6. 39
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySettingBuilder.cs
  7. 9
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AppServiceControllerOptions.cs
  8. 12
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IAbpControllerAssemblySettingBuilder.cs
  9. 98
      src/Volo.Abp.Identity.HttpApi.Host/AbpIdentityHttpApiHostModule.cs
  10. 2
      src/Volo.Abp.Identity.HttpApi.Host/Controllers/HomeController.cs
  11. 41
      src/Volo.Abp.Identity.HttpApi.Host/SwaggerDefaultValues.cs
  12. 28
      src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/CallsController.cs
  13. 55
      src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/V1/CallsController.cs
  14. 1
      src/Volo.Abp.Identity.HttpApi.Host/Volo.Abp.Identity.HttpApi.Host.csproj
  15. 4
      src/Volo.Abp.Identity.HttpApi/Volo.Abp.Identity.HttpApi.csproj
  16. 11
      src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/AbpIdentityHttpApiModule.cs
  17. 16
      test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AbpAspNetCoreMvcTestModule.cs

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.AppServiceControllers.Create(typeof(AbpDeskApplicationModule).Assembly);
});
}

33
src/Volo.Abp.AspNetCore.Mvc/Microsoft/Extensions/DependencyInjection/AbpApiVersioningOptionsExtensions.cs

@ -0,0 +1,33 @@
using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.AspNetCore.Mvc.Versioning.Conventions;
using Volo.Abp.AspNetCore.Mvc;
namespace Microsoft.Extensions.DependencyInjection
{
public static class AbpApiVersioningOptionsExtensions
{
public static void AddAbpModules(this ApiVersioningOptions options, IServiceCollection services)
{
services.Configure<AbpAspNetCoreMvcOptions>(op =>
{
foreach (var setting in op.AppServiceControllers.ControllerAssemblySettings)
{
foreach (var controllerType in setting.ControllerTypes)
{
var controllerBuilder = typeof(ApiVersionConventionBuilder)
.GetMethod(nameof(ApiVersionConventionBuilder.Controller), BindingFlags.Instance | BindingFlags.Public)
.MakeGenericMethod(controllerType)
.Invoke(options.Conventions, null);
typeof(ControllerApiVersionConventionBuilder<>)
.MakeGenericType(controllerType)
.GetMethod("HasApiVersion")
.Invoke(controllerBuilder, new object[] { setting.ApiVersion });
}
}
});
}
}
}

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>

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

@ -34,7 +34,12 @@ namespace Volo.Abp.AspNetCore.Mvc
}
//TODO: Move this to a lazy loaded field for efficiency.
var configuration = _application.ServiceProvider.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>().Value.AppServiceControllers.ControllerAssemblySettings.GetSettingOrNull(type);
var configuration = _application.ServiceProvider
.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>().Value
.AppServiceControllers
.ControllerAssemblySettings
.GetSettingOrNull(type);
return configuration != null && (configuration.TypePredicate == null || configuration.TypePredicate(type));
}
}

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

@ -1,7 +1,12 @@
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 Volo.Abp.Application.Services;
using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc
{
@ -11,7 +16,19 @@ namespace Volo.Abp.AspNetCore.Mvc
public Assembly Assembly { get; }
[NotNull]
public string RootPath { get; }
public List<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; }
@ -25,12 +42,44 @@ namespace Volo.Abp.AspNetCore.Mvc
[CanBeNull]
public Func<UrlActionNameNormalizerContext, string> UrlActionNameNormalizer { get; set; }
public ApiVersion ApiVersion { get; set; }
public AbpControllerAssemblySetting([NotNull] Assembly assembly, [NotNull] string rootPath)
{
Check.NotNull(assembly, rootPath);
Assembly = assembly;
RootPath = rootPath;
ControllerTypes = new List<Type>();
ApiVersion = new ApiVersion(1, 0);
}
public void Initialize()
{
ControllerTypes.AddRange(
Assembly.GetTypes()
.Where(IsRemoteService)
.WhereIf(TypePredicate != null, TypePredicate)
);
}
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;
}
}
}

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

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

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Http;
using Volo.Abp.Http.Modeling;
@ -22,11 +23,13 @@ namespace Volo.Abp.AspNetCore.Mvc
};
}
public AbpControllerAssemblySettingBuilder CreateFor(Assembly assembly, string rootPath = ModuleApiDescriptionModel.DefaultRootPath)
public AppServiceControllerOptions Create(Assembly assembly, [CanBeNull] Action<AbpControllerAssemblySetting> optionsAction = null)
{
var setting = new AbpControllerAssemblySetting(assembly, rootPath);
var setting = new AbpControllerAssemblySetting(assembly, ModuleApiDescriptionModel.DefaultRootPath);
optionsAction?.Invoke(setting);
setting.Initialize();
ControllerAssemblySettings.Add(setting);
return new AbpControllerAssemblySettingBuilder(setting);
return this;
}
}
}

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

98
src/Volo.Abp.Identity.HttpApi.Host/AbpIdentityHttpApiHostModule.cs

@ -1,11 +1,21 @@
using Microsoft.AspNetCore.Builder;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.AspNetCore.Mvc.Versioning.Conventions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Examples;
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.ApiExploring;
using Volo.Abp.Autofac;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
@ -19,7 +29,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,14 +43,56 @@ 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>().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.AddAbpModules(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.AddAssemblyOf<AbpIdentityHttpApiHostModule>();
}
@ -57,10 +109,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 +137,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;
}
}
}

2
src/Volo.Abp.Identity.HttpApi.Host/Controllers/HomeController.cs

@ -5,6 +5,8 @@ namespace Volo.Abp.Identity.HttpApi.Host.Controllers
{
public class HomeController : AbpController
{
[HttpPost]
[Route("/api/v1/users/create")]
public IActionResult Index()
{
return Redirect("/swagger");

41
src/Volo.Abp.Identity.HttpApi.Host/SwaggerDefaultValues.cs

@ -0,0 +1,41 @@
namespace Microsoft.Examples
{
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Linq;
/// <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; }
}
}

55
src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/V1/CallsController.cs

@ -0,0 +1,55 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Application.Dtos;
using Volo.Abp.AspNetCore.Mvc;
namespace Volo.Abp.Identity.HttpApi.Host.VersioningTests.V1
{
[ApiVersion("1.0")]
[Route("api/v{api-version:apiVersion}/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; }
}
[ApiVersion("2.0")]
[Route("api/v{api-version:apiVersion}/calls")]
[ControllerName("Calls")]
public class Calls2Controller : AbpController
{
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" />

11
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,12 @@ namespace Volo.Abp.Identity
services.Configure<AbpAspNetCoreMvcOptions>(options =>
{
options
.AppServiceControllers
.CreateFor(typeof(AbpIdentityApplicationModule).Assembly, "identity")
.NormalizeControllerNameInUrl(context => context.ControllerName.RemovePreFix("Identity"));
options.AppServiceControllers.Create(typeof(AbpIdentityApplicationModule).Assembly, opts =>
{
opts.RootPath = "identity";
opts.UrlControllerNameNormalizer = context => context.ControllerName.RemovePreFix("Identity");
opts.ApiVersion = new ApiVersion(2, 0, "beta");
});
});
}
}

16
test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AbpAspNetCoreMvcTestModule.cs

@ -25,15 +25,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.AppServiceControllers.Create(typeof(TestAppModule).Assembly, opts =>
{
opts.UrlActionNameNormalizer = context =>
string.Equals(context.ActionNameInUrl, "phone", StringComparison.OrdinalIgnoreCase)
? "phones"
: context.ActionNameInUrl;
});
});
services.AddAssemblyOf<AbpAspNetCoreMvcTestModule>();

Loading…
Cancel
Save