Browse Source

Merge pull request #116 from aspnetzero/api-versioning

Controller/Action name in url normalization system implemented.
pull/129/head
Halil İbrahim Kalkan 9 years ago
committed by GitHub
parent
commit
6ac2d596e8
  1. BIN
      src/AbpDesk/Web_PlugIns/AbpDesk.MongoBlog.dll
  2. 2
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceControllerFeatureProvider.cs
  3. 61
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConvention.cs
  4. 18
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySetting.cs
  5. 12
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySettingBuilder.cs
  6. 7
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AppServiceControllerOptions.cs
  7. 20
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DefaultUrlActionNameNormalizer.cs
  8. 7
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IUrlActionNameNormalizer.cs
  9. 9
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/UrlActionNameNormalizerContext.cs
  10. 24
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/UrlControllerNameNormalizerContext.cs
  11. 1
      src/Volo.Abp.Identity.HttpApi.Host/Volo.Abp.Identity.HttpApi.Host.csproj
  12. 8
      src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/AbpIdentityHttpApiModule.cs
  13. 14
      test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AbpAspNetCoreMvcTestModule.cs
  14. 17
      test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/PhoneBookUrlActionNameNormalizer.cs

BIN
src/AbpDesk/Web_PlugIns/AbpDesk.MongoBlog.dll

Binary file not shown.

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

@ -35,7 +35,7 @@ namespace Volo.Abp.AspNetCore.Mvc
//TODO: Move this to a lazy loaded field for efficiency. //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(type); return configuration != null && (configuration.TypePredicate == null || configuration.TypePredicate(type));
} }
} }
} }

61
src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConvention.cs

@ -40,7 +40,7 @@ namespace Volo.Abp.AspNetCore.Mvc
if (IsRemoteService(controllerType)) if (IsRemoteService(controllerType))
{ {
controller.ControllerName = controller.ControllerName.RemovePostFix(ApplicationService.CommonPostfixes); controller.ControllerName = controller.ControllerName.RemovePostFix(ApplicationService.CommonPostfixes);
configuration?.ControllerModelConfigurer(controller); configuration?.ControllerModelConfigurer?.Invoke(controller);
//ConfigureArea(controller, configuration); //ConfigureArea(controller, configuration);
ConfigureRemoteService(controller, configuration); ConfigureRemoteService(controller, configuration);
} }
@ -192,7 +192,7 @@ namespace Volo.Abp.AspNetCore.Mvc
} }
else else
{ {
NormalizeSelectorRoutes(rootPath, controllerName, action); NormalizeSelectorRoutes(rootPath, controllerName, action, configuration);
} }
} }
@ -202,7 +202,7 @@ namespace Volo.Abp.AspNetCore.Mvc
var abpServiceSelectorModel = new SelectorModel var abpServiceSelectorModel = new SelectorModel
{ {
AttributeRouteModel = CreateAbpServiceAttributeRouteModel(rootPath, controllerName, action, httpMethod), AttributeRouteModel = CreateAbpServiceAttributeRouteModel(rootPath, controllerName, action, httpMethod, configuration),
ActionConstraints = { new HttpMethodActionConstraint(new[] { httpMethod }) } ActionConstraints = { new HttpMethodActionConstraint(new[] { httpMethod }) }
}; };
@ -214,14 +214,14 @@ namespace Volo.Abp.AspNetCore.Mvc
return HttpMethodHelper.GetConventionalVerbForMethodName(action.ActionName); return HttpMethodHelper.GetConventionalVerbForMethodName(action.ActionName);
} }
protected virtual void NormalizeSelectorRoutes(string rootPath, string controllerName, ActionModel action) protected virtual void NormalizeSelectorRoutes(string rootPath, string controllerName, ActionModel action, [CanBeNull] AbpControllerAssemblySetting configuration)
{ {
foreach (var selector in action.Selectors) foreach (var selector in action.Selectors)
{ {
var httpMethod = selector.ActionConstraints.OfType<HttpMethodActionConstraint>().FirstOrDefault()?.HttpMethods?.FirstOrDefault(); var httpMethod = selector.ActionConstraints.OfType<HttpMethodActionConstraint>().FirstOrDefault()?.HttpMethods?.FirstOrDefault();
if (selector.AttributeRouteModel == null) if (selector.AttributeRouteModel == null)
{ {
selector.AttributeRouteModel = CreateAbpServiceAttributeRouteModel(rootPath, controllerName, action, httpMethod); selector.AttributeRouteModel = CreateAbpServiceAttributeRouteModel(rootPath, controllerName, action, httpMethod, configuration);
} }
} }
} }
@ -238,18 +238,20 @@ namespace Volo.Abp.AspNetCore.Mvc
return _options.AppServiceControllers.ControllerAssemblySettings.GetSettingOrNull(controllerType); return _options.AppServiceControllers.ControllerAssemblySettings.GetSettingOrNull(controllerType);
} }
protected virtual AttributeRouteModel CreateAbpServiceAttributeRouteModel(string rootPath, string controllerName, ActionModel action, string httpMethod) protected virtual AttributeRouteModel CreateAbpServiceAttributeRouteModel(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] AbpControllerAssemblySetting configuration)
{ {
return new AttributeRouteModel( return new AttributeRouteModel(
new RouteAttribute( new RouteAttribute(
CalculateRouteTemplate(rootPath, controllerName, action, httpMethod) CalculateRouteTemplate(rootPath, controllerName, action, httpMethod, configuration)
) )
); );
} }
protected virtual string CalculateRouteTemplate(string rootPath, string controllerName, ActionModel action, string httpMethod) protected virtual string CalculateRouteTemplate(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] AbpControllerAssemblySetting configuration)
{ {
var url = $"api/{rootPath}/{controllerName.ToCamelCase()}"; var controllerNameInUrl = NormalizeUrlControllerName(rootPath, controllerName, action, httpMethod, configuration);
var url = $"api/{rootPath}/{controllerNameInUrl.ToCamelCase()}";
//Add {id} path if needed //Add {id} path if needed
if (action.Parameters.Any(p => p.ParameterName == "id")) if (action.Parameters.Any(p => p.ParameterName == "id"))
@ -258,10 +260,10 @@ namespace Volo.Abp.AspNetCore.Mvc
} }
//Add action name if needed //Add action name if needed
var actionNameInUrl = NormalizeUrlActionName(rootPath, controllerName, action, httpMethod); var actionNameInUrl = NormalizeUrlActionName(rootPath, controllerName, action, httpMethod, configuration);
if (!actionNameInUrl.IsNullOrEmpty()) if (!actionNameInUrl.IsNullOrEmpty())
{ {
url += $"/{actionNameInUrl}"; url += $"/{actionNameInUrl.ToCamelCase()}";
//Add secondary Id //Add secondary Id
var secondaryIds = action.Parameters.Where(p => p.ParameterName.EndsWith("Id", StringComparison.Ordinal)).ToList(); var secondaryIds = action.Parameters.Where(p => p.ParameterName.EndsWith("Id", StringComparison.Ordinal)).ToList();
@ -274,16 +276,43 @@ namespace Volo.Abp.AspNetCore.Mvc
return url; return url;
} }
protected virtual string NormalizeUrlActionName(string rootPath, string controllerName, ActionModel action, string httpMethod) protected virtual string NormalizeUrlActionName(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] AbpControllerAssemblySetting configuration)
{ {
var context = new UrlActionNameNormalizerContext(rootPath, controllerName, action, httpMethod, action.ActionName); var actionNameInUrl = HttpMethodHelper
.RemoveHttpMethodPrefix(action.ActionName, httpMethod)
.RemovePostFix("Async");
foreach (var normalizer in _options.AppServiceControllers.UrlActionNameNormalizers) if (configuration?.UrlActionNameNormalizer == null)
{ {
normalizer.Normalize(context); return actionNameInUrl;
} }
return context.ActionNameInUrl; return configuration.UrlActionNameNormalizer(
new UrlActionNameNormalizerContext(
rootPath,
controllerName,
action,
actionNameInUrl,
httpMethod
)
);
}
protected virtual string NormalizeUrlControllerName(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] AbpControllerAssemblySetting configuration)
{
if(configuration?.UrlControllerNameNormalizer == null)
{
return controllerName;
}
return configuration.UrlControllerNameNormalizer(
new UrlControllerNameNormalizerContext(
rootPath,
controllerName,
action,
httpMethod
)
);
} }
protected virtual void RemoveEmptySelectors(IList<SelectorModel> selectors) protected virtual void RemoveEmptySelectors(IList<SelectorModel> selectors)

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

@ -1,26 +1,36 @@
using System; using System;
using System.Reflection; using System.Reflection;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace Volo.Abp.AspNetCore.Mvc namespace Volo.Abp.AspNetCore.Mvc
{ {
public class AbpControllerAssemblySetting public class AbpControllerAssemblySetting
{ {
[NotNull]
public Assembly Assembly { get; } public Assembly Assembly { get; }
[NotNull]
public string RootPath { get; } public string RootPath { get; }
[CanBeNull]
public Func<Type, bool> TypePredicate { get; set; } public Func<Type, bool> TypePredicate { get; set; }
[CanBeNull]
public Action<ControllerModel> ControllerModelConfigurer { get; set; } public Action<ControllerModel> ControllerModelConfigurer { get; set; }
public AbpControllerAssemblySetting(Assembly assembly, string rootPath) [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; Assembly = assembly;
RootPath = rootPath; RootPath = rootPath;
TypePredicate = type => true;
ControllerModelConfigurer = controller => { };
} }
} }
} }

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

@ -23,5 +23,17 @@ namespace Volo.Abp.AspNetCore.Mvc
_setting.ControllerModelConfigurer = configurer; _setting.ControllerModelConfigurer = configurer;
return this; 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;
}
} }
} }

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

@ -12,8 +12,6 @@ namespace Volo.Abp.AspNetCore.Mvc
public List<Type> FormBodyBindingIgnoredTypes { get; } public List<Type> FormBodyBindingIgnoredTypes { get; }
public List<IUrlActionNameNormalizer> UrlActionNameNormalizers { get; }
public AppServiceControllerOptions() public AppServiceControllerOptions()
{ {
ControllerAssemblySettings = new ControllerAssemblySettingList(); ControllerAssemblySettings = new ControllerAssemblySettingList();
@ -22,11 +20,6 @@ namespace Volo.Abp.AspNetCore.Mvc
{ {
typeof(IFormFile) typeof(IFormFile)
}; };
UrlActionNameNormalizers = new List<IUrlActionNameNormalizer>
{
new DefaultUrlActionNameNormalizer()
};
} }
public AbpControllerAssemblySettingBuilder CreateFor(Assembly assembly, string rootPath = ModuleApiDescriptionModel.DefaultRootPath) public AbpControllerAssemblySettingBuilder CreateFor(Assembly assembly, string rootPath = ModuleApiDescriptionModel.DefaultRootPath)

20
src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DefaultUrlActionNameNormalizer.cs

@ -1,20 +0,0 @@
using System;
using Volo.Abp.Http;
namespace Volo.Abp.AspNetCore.Mvc
{
public class DefaultUrlActionNameNormalizer : IUrlActionNameNormalizer
{
public void Normalize(UrlActionNameNormalizerContext context)
{
if (context.ActionNameInUrl.IsNullOrEmpty())
{
return;
}
context.ActionNameInUrl = HttpMethodHelper
.RemoveHttpMethodPrefix(context.ActionNameInUrl, context.HttpMethod)
.RemovePostFix("Async");
}
}
}

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

@ -1,7 +0,0 @@
namespace Volo.Abp.AspNetCore.Mvc
{
public interface IUrlActionNameNormalizer
{
void Normalize(UrlActionNameNormalizerContext context);
}
}

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

@ -10,18 +10,17 @@ namespace Volo.Abp.AspNetCore.Mvc
public ActionModel Action { get; } public ActionModel Action { get; }
public string HttpMethod { get; } public string ActionNameInUrl { get; }
public string ActionNameInUrl { get; set; } public string HttpMethod { get; }
public UrlActionNameNormalizerContext(string rootPath, string controllerName, ActionModel action, string httpMethod, string actionNameInUrl) public UrlActionNameNormalizerContext(string rootPath, string controllerName, ActionModel action, string actionNameInUrl, string httpMethod)
{ {
RootPath = rootPath; RootPath = rootPath;
ControllerName = controllerName; ControllerName = controllerName;
Action = action; Action = action;
HttpMethod = httpMethod;
ActionNameInUrl = actionNameInUrl; ActionNameInUrl = actionNameInUrl;
HttpMethod = httpMethod;
} }
} }
} }

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

@ -0,0 +1,24 @@
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;
}
}
}

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

@ -39,6 +39,7 @@
<PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" /> <PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Serilog.Sinks.RollingFile" Version="3.3.0" /> <PackageReference Include="Serilog.Sinks.RollingFile" Version="3.3.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="2.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

8
src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/AbpIdentityHttpApiModule.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection; using System;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -13,7 +14,10 @@ namespace Volo.Abp.Identity
services.Configure<AbpAspNetCoreMvcOptions>(options => services.Configure<AbpAspNetCoreMvcOptions>(options =>
{ {
options.AppServiceControllers.CreateFor(typeof(AbpIdentityApplicationModule).Assembly, "identity"); options
.AppServiceControllers
.CreateFor(typeof(AbpIdentityApplicationModule).Assembly, "identity")
.NormalizeControllerNameInUrl(context => context.ControllerName.RemovePreFix("Identity"));
}); });
} }
} }

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

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Builder; using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Modularity; using Volo.Abp.AspNetCore.Modularity;
using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc;
@ -24,8 +25,15 @@ namespace Volo.Abp.AspNetCore.App
services.Configure<AbpAspNetCoreMvcOptions>(options => services.Configure<AbpAspNetCoreMvcOptions>(options =>
{ {
options.AppServiceControllers.CreateFor(typeof(TestAppModule).Assembly); options
options.AppServiceControllers.UrlActionNameNormalizers.Add(new PhoneBookUrlActionNameNormalizer()); .AppServiceControllers
.CreateFor(typeof(TestAppModule).Assembly)
.NormalizeActionNameInUrl(
context =>
string.Equals(context.ActionNameInUrl, "phone", StringComparison.OrdinalIgnoreCase)
? "phones"
: context.ActionNameInUrl
);
}); });
services.AddAssemblyOf<AbpAspNetCoreMvcTestModule>(); services.AddAssemblyOf<AbpAspNetCoreMvcTestModule>();

17
test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/PhoneBookUrlActionNameNormalizer.cs

@ -1,17 +0,0 @@
using System;
using Volo.Abp.AspNetCore.Mvc;
namespace Volo.Abp.AspNetCore.App
{
public class PhoneBookUrlActionNameNormalizer : IUrlActionNameNormalizer
{
public void Normalize(UrlActionNameNormalizerContext context)
{
if (string.Equals(context.ActionNameInUrl, "phone", StringComparison.OrdinalIgnoreCase))
{
context.ActionNameInUrl = "phones";
return;
}
}
}
}
Loading…
Cancel
Save