mirror of https://github.com/abpframework/abp.git
20 changed files with 510 additions and 23 deletions
@ -0,0 +1,29 @@ |
|||||
|
using System.Reflection; |
||||
|
using Microsoft.AspNetCore.Mvc.Abstractions; |
||||
|
using Microsoft.AspNetCore.Mvc.Controllers; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Mvc |
||||
|
{ |
||||
|
public static class ActionDescriptorExtensions |
||||
|
{ |
||||
|
public static ControllerActionDescriptor AsControllerActionDescriptor(this ActionDescriptor actionDescriptor) |
||||
|
{ |
||||
|
if (!actionDescriptor.IsControllerAction()) |
||||
|
{ |
||||
|
throw new AbpException($"{nameof(actionDescriptor)} should be type of {typeof(ControllerActionDescriptor).AssemblyQualifiedName}"); |
||||
|
} |
||||
|
|
||||
|
return actionDescriptor as ControllerActionDescriptor; |
||||
|
} |
||||
|
|
||||
|
public static MethodInfo GetMethodInfo(this ActionDescriptor actionDescriptor) |
||||
|
{ |
||||
|
return actionDescriptor.AsControllerActionDescriptor().MethodInfo; |
||||
|
} |
||||
|
|
||||
|
public static bool IsControllerAction(this ActionDescriptor actionDescriptor) |
||||
|
{ |
||||
|
return actionDescriptor is ControllerActionDescriptor; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,149 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Reflection; |
||||
|
using Microsoft.AspNetCore.Mvc.ApiExplorer; |
||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
using Volo.Abp.AspNetCore.Mvc.Utils; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Http.Modeling; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Mvc |
||||
|
{ |
||||
|
public class AspNetCoreApiDescriptionModelProvider : IApiDescriptionModelProvider, ISingletonDependency |
||||
|
{ |
||||
|
public ILogger<AspNetCoreApiDescriptionModelProvider> Logger { get; set; } |
||||
|
|
||||
|
private readonly IApiDescriptionGroupCollectionProvider _descriptionProvider; |
||||
|
private readonly AbpAspNetCoreMvcOptions _options; |
||||
|
|
||||
|
public AspNetCoreApiDescriptionModelProvider( |
||||
|
IApiDescriptionGroupCollectionProvider descriptionProvider, |
||||
|
IOptions<AbpAspNetCoreMvcOptions> options) |
||||
|
{ |
||||
|
_descriptionProvider = descriptionProvider; |
||||
|
_options = options.Value; |
||||
|
|
||||
|
Logger = NullLogger<AspNetCoreApiDescriptionModelProvider>.Instance; |
||||
|
} |
||||
|
|
||||
|
public ApplicationApiDescriptionModel CreateModel() |
||||
|
{ |
||||
|
var model = new ApplicationApiDescriptionModel(); |
||||
|
|
||||
|
foreach (var descriptionGroupItem in _descriptionProvider.ApiDescriptionGroups.Items) |
||||
|
{ |
||||
|
foreach (var apiDescription in descriptionGroupItem.Items) |
||||
|
{ |
||||
|
if (!apiDescription.ActionDescriptor.IsControllerAction()) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
AddApiDescriptionToModel(apiDescription, model); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return model; |
||||
|
} |
||||
|
|
||||
|
private void AddApiDescriptionToModel(ApiDescription apiDescription, ApplicationApiDescriptionModel model) |
||||
|
{ |
||||
|
var moduleModel = model.GetOrAddModule(GetModuleName(apiDescription)); |
||||
|
var controllerModel = moduleModel.GetOrAddController(GetControllerName(apiDescription)); |
||||
|
|
||||
|
var method = apiDescription.ActionDescriptor.GetMethodInfo(); |
||||
|
|
||||
|
if (controllerModel.Actions.ContainsKey(method.Name)) |
||||
|
{ |
||||
|
Logger.LogWarning($"Controller '{controllerModel.Name}' contains more than one action with name '{method.Name}' for module '{moduleModel.Name}'. Ignored: " + apiDescription.ActionDescriptor.GetMethodInfo()); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var returnValue = new ReturnValueApiDescriptionModel(method.ReturnType); |
||||
|
|
||||
|
var actionModel = controllerModel.AddAction(new ActionApiDescriptionModel( |
||||
|
method.Name, |
||||
|
returnValue, |
||||
|
apiDescription.RelativePath, |
||||
|
apiDescription.HttpMethod |
||||
|
)); |
||||
|
|
||||
|
AddParameterDescriptionsToModel(actionModel, method, apiDescription); |
||||
|
} |
||||
|
|
||||
|
private static string GetControllerName(ApiDescription apiDescription) |
||||
|
{ |
||||
|
return apiDescription.GroupName?.RemovePostFix(ApplicationService.CommonPostfixes) |
||||
|
?? apiDescription.ActionDescriptor.AsControllerActionDescriptor().ControllerName; |
||||
|
} |
||||
|
|
||||
|
private void AddParameterDescriptionsToModel(ActionApiDescriptionModel actionModel, MethodInfo method, ApiDescription apiDescription) |
||||
|
{ |
||||
|
if (!apiDescription.ParameterDescriptions.Any()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var matchedMethodParamNames = ArrayMatcher.Match( |
||||
|
apiDescription.ParameterDescriptions.Select(p => p.Name).ToArray(), |
||||
|
method.GetParameters().Select(GetMethodParamName).ToArray() |
||||
|
); |
||||
|
|
||||
|
for (var i = 0; i < apiDescription.ParameterDescriptions.Count; i++) |
||||
|
{ |
||||
|
var parameterDescription = apiDescription.ParameterDescriptions[i]; |
||||
|
var matchedMethodParamName = matchedMethodParamNames.Length > i |
||||
|
? matchedMethodParamNames[i] |
||||
|
: parameterDescription.Name; |
||||
|
|
||||
|
actionModel.AddParameter(new ParameterApiDescriptionModel( |
||||
|
parameterDescription.Name, |
||||
|
matchedMethodParamName, |
||||
|
parameterDescription.Type, |
||||
|
parameterDescription.RouteInfo?.IsOptional ?? false, |
||||
|
parameterDescription.RouteInfo?.DefaultValue, |
||||
|
parameterDescription.RouteInfo?.Constraints?.Select(c => c.GetType().Name).ToArray(), |
||||
|
parameterDescription.Source.Id |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public string GetMethodParamName(ParameterInfo parameterInfo) |
||||
|
{ |
||||
|
var modelNameProvider = parameterInfo.GetCustomAttributes() |
||||
|
.OfType<IModelNameProvider>() |
||||
|
.FirstOrDefault(); |
||||
|
|
||||
|
if (modelNameProvider == null) |
||||
|
{ |
||||
|
return parameterInfo.Name; |
||||
|
} |
||||
|
|
||||
|
return modelNameProvider.Name; |
||||
|
} |
||||
|
|
||||
|
private string GetModuleName(ApiDescription apiDescription) |
||||
|
{ |
||||
|
var controllerType = apiDescription.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.AsType(); |
||||
|
if (controllerType == null) |
||||
|
{ |
||||
|
return AbpControllerAssemblySetting.DefaultServiceModuleName; |
||||
|
} |
||||
|
|
||||
|
foreach (var controllerSetting in _options.AppServiceControllers.ControllerAssemblySettings) |
||||
|
{ |
||||
|
if (Equals(controllerType.Assembly, controllerSetting.Assembly)) |
||||
|
{ |
||||
|
return controllerSetting.ModuleName; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return AbpControllerAssemblySetting.DefaultServiceModuleName; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Mvc.Utils |
||||
|
{ |
||||
|
internal static class ArrayMatcher |
||||
|
{ |
||||
|
public static T[] Match<T>(T[] sourceArray, T[] destinationArray) |
||||
|
{ |
||||
|
var result = new List<T>(); |
||||
|
|
||||
|
var currentMethodParamIndex = 0; |
||||
|
var parentItem = default(T); |
||||
|
|
||||
|
foreach (var sourceItem in sourceArray) |
||||
|
{ |
||||
|
if (currentMethodParamIndex < destinationArray.Length) |
||||
|
{ |
||||
|
var destinationItem = destinationArray[currentMethodParamIndex]; |
||||
|
|
||||
|
if (EqualityComparer<T>.Default.Equals(sourceItem, destinationItem)) |
||||
|
{ |
||||
|
parentItem = default(T); |
||||
|
currentMethodParamIndex++; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (parentItem == null) |
||||
|
{ |
||||
|
parentItem = destinationItem; |
||||
|
currentMethodParamIndex++; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var resultItem = EqualityComparer<T>.Default.Equals(parentItem, default(T)) ? sourceItem : parentItem; |
||||
|
result.Add(resultItem); |
||||
|
} |
||||
|
|
||||
|
return result.ToArray(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.Http.Modeling |
||||
|
{ |
||||
|
[Serializable] |
||||
|
public class ActionApiDescriptionModel |
||||
|
{ |
||||
|
public string Name { get; } |
||||
|
|
||||
|
public string HttpMethod { get; } |
||||
|
|
||||
|
public string Url { get; } |
||||
|
|
||||
|
public IList<ParameterApiDescriptionModel> Parameters { get; } |
||||
|
|
||||
|
public ReturnValueApiDescriptionModel ReturnValue { get; } |
||||
|
|
||||
|
private ActionApiDescriptionModel() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public ActionApiDescriptionModel(string name, ReturnValueApiDescriptionModel returnValue, string url, string httpMethod = null) |
||||
|
{ |
||||
|
Name = name; |
||||
|
ReturnValue = returnValue; |
||||
|
Url = url; |
||||
|
HttpMethod = httpMethod; |
||||
|
|
||||
|
Parameters = new List<ParameterApiDescriptionModel>(); |
||||
|
} |
||||
|
|
||||
|
public ParameterApiDescriptionModel AddParameter(ParameterApiDescriptionModel parameter) |
||||
|
{ |
||||
|
Parameters.Add(parameter); |
||||
|
return parameter; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace Volo.Abp.Http.Modeling |
||||
|
{ |
||||
|
[Serializable] |
||||
|
public class ApplicationApiDescriptionModel |
||||
|
{ |
||||
|
public IDictionary<string, ModuleApiDescriptionModel> Modules { get; } |
||||
|
|
||||
|
public ApplicationApiDescriptionModel() |
||||
|
{ |
||||
|
Modules = new Dictionary<string, ModuleApiDescriptionModel>(); |
||||
|
} |
||||
|
|
||||
|
public ModuleApiDescriptionModel AddModule(ModuleApiDescriptionModel module) |
||||
|
{ |
||||
|
if (Modules.ContainsKey(module.Name)) |
||||
|
{ |
||||
|
throw new AbpException("There is already a module with same name: " + module.Name); |
||||
|
} |
||||
|
|
||||
|
return Modules[module.Name] = module; |
||||
|
} |
||||
|
|
||||
|
public ModuleApiDescriptionModel GetOrAddModule(string name) |
||||
|
{ |
||||
|
return Modules.GetOrAdd(name, () => new ModuleApiDescriptionModel(name)); |
||||
|
} |
||||
|
|
||||
|
public ApplicationApiDescriptionModel CreateSubModel(string[] modules = null, string[] controllers = null, string[] actions = null) |
||||
|
{ |
||||
|
var subModel = new ApplicationApiDescriptionModel(); |
||||
|
|
||||
|
foreach (var module in Modules.Values) |
||||
|
{ |
||||
|
if (modules == null || modules.Contains(module.Name)) |
||||
|
{ |
||||
|
subModel.AddModule(module.CreateSubModel(controllers, actions)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return subModel; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace Volo.Abp.Http.Modeling |
||||
|
{ |
||||
|
[Serializable] |
||||
|
public class ControllerApiDescriptionModel |
||||
|
{ |
||||
|
public string Name { get; } |
||||
|
|
||||
|
public IDictionary<string, ActionApiDescriptionModel> Actions { get; } |
||||
|
|
||||
|
private ControllerApiDescriptionModel() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public ControllerApiDescriptionModel(string name) |
||||
|
{ |
||||
|
Name = name; |
||||
|
|
||||
|
Actions = new Dictionary<string, ActionApiDescriptionModel>(); |
||||
|
} |
||||
|
|
||||
|
public ActionApiDescriptionModel AddAction(ActionApiDescriptionModel action) |
||||
|
{ |
||||
|
if (Actions.ContainsKey(action.Name)) |
||||
|
{ |
||||
|
throw new AbpException( |
||||
|
$"Can not add more than one action with same name to the same controller. Controller: {Name}, Action: {action.Name}." |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return Actions[action.Name] = action; |
||||
|
} |
||||
|
|
||||
|
public ControllerApiDescriptionModel CreateSubModel(string[] actions) |
||||
|
{ |
||||
|
var subModel = new ControllerApiDescriptionModel(Name); |
||||
|
|
||||
|
foreach (var action in Actions.Values) |
||||
|
{ |
||||
|
if (actions == null || actions.Contains(action.Name)) |
||||
|
{ |
||||
|
subModel.AddAction(action); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return subModel; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Volo.Abp.Http.Modeling |
||||
|
{ |
||||
|
public interface IApiDescriptionModelProvider |
||||
|
{ |
||||
|
ApplicationApiDescriptionModel CreateModel(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace Volo.Abp.Http.Modeling |
||||
|
{ |
||||
|
[Serializable] |
||||
|
public class ModuleApiDescriptionModel |
||||
|
{ |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public IDictionary<string, ControllerApiDescriptionModel> Controllers { get; } |
||||
|
|
||||
|
private ModuleApiDescriptionModel() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public ModuleApiDescriptionModel(string name) |
||||
|
{ |
||||
|
Name = name; |
||||
|
|
||||
|
Controllers = new Dictionary<string, ControllerApiDescriptionModel>(); |
||||
|
} |
||||
|
|
||||
|
public ControllerApiDescriptionModel AddController(ControllerApiDescriptionModel controller) |
||||
|
{ |
||||
|
if (Controllers.ContainsKey(controller.Name)) |
||||
|
{ |
||||
|
throw new AbpException($"There is already a controller with name: {controller.Name} in module: {Name}"); |
||||
|
} |
||||
|
|
||||
|
return Controllers[controller.Name] = controller; |
||||
|
} |
||||
|
|
||||
|
public ControllerApiDescriptionModel GetOrAddController(string name) |
||||
|
{ |
||||
|
return Controllers.GetOrAdd(name, () => new ControllerApiDescriptionModel(name)); |
||||
|
} |
||||
|
|
||||
|
public ModuleApiDescriptionModel CreateSubModel(string[] controllers, string[] actions) |
||||
|
{ |
||||
|
var subModel = new ModuleApiDescriptionModel(Name); |
||||
|
|
||||
|
foreach (var controller in Controllers.Values) |
||||
|
{ |
||||
|
if (controllers == null || controllers.Contains(controller.Name)) |
||||
|
{ |
||||
|
subModel.AddController(controller.CreateSubModel(actions)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return subModel; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.Http.Modeling |
||||
|
{ |
||||
|
[Serializable] |
||||
|
public class ParameterApiDescriptionModel |
||||
|
{ |
||||
|
public string NameOnMethod { get; } |
||||
|
|
||||
|
public string Name { get; } |
||||
|
|
||||
|
public Type Type { get; } |
||||
|
|
||||
|
public string TypeAsString { get; } |
||||
|
|
||||
|
public bool IsOptional { get; } |
||||
|
|
||||
|
public object DefaultValue { get; } |
||||
|
|
||||
|
public string[] ConstraintTypes { get; } |
||||
|
|
||||
|
public string BindingSourceId { get; } |
||||
|
|
||||
|
private ParameterApiDescriptionModel() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public ParameterApiDescriptionModel(string name, string nameOnMethod, Type type, bool isOptional = false, object defaultValue = null, string[] constraintTypes = null, string bindingSourceId = null) |
||||
|
{ |
||||
|
Name = name; |
||||
|
NameOnMethod = nameOnMethod; |
||||
|
Type = type; |
||||
|
TypeAsString = type.FullName; |
||||
|
IsOptional = isOptional; |
||||
|
DefaultValue = defaultValue; |
||||
|
ConstraintTypes = constraintTypes; |
||||
|
BindingSourceId = bindingSourceId; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.Http.Modeling |
||||
|
{ |
||||
|
[Serializable] |
||||
|
public class ReturnValueApiDescriptionModel |
||||
|
{ |
||||
|
public Type Type { get; } |
||||
|
public string TypeAsString { get; } |
||||
|
|
||||
|
public ReturnValueApiDescriptionModel(Type type) |
||||
|
{ |
||||
|
Type = type; |
||||
|
TypeAsString = type.FullName; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,13 +0,0 @@ |
|||||
using System.Reflection; |
|
||||
|
|
||||
namespace System |
|
||||
{ |
|
||||
public static class AbpTypeExtensions |
|
||||
{ |
|
||||
//TODO: This method can be removed because not needed anymore!
|
|
||||
public static Assembly GetAssembly(this Type type) |
|
||||
{ |
|
||||
return type.GetTypeInfo().Assembly; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,19 @@ |
|||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Mvc.Utils |
||||
|
{ |
||||
|
public class ArrayMacther_Tests |
||||
|
{ |
||||
|
[Theory] |
||||
|
[InlineData( |
||||
|
new[] { "p1", "p2.a1", "p2.a2.b1", "p2.a2.b2", "p3", "p4", "p5.c1", "p5.c2" }, |
||||
|
new[] { "p1", "p2", "p3", "p4", "p5" }, |
||||
|
new[] { "p1", "p2", "p2", "p2", "p3", "p4", "p5", "p5" }) |
||||
|
] |
||||
|
public void Should_Find_Correct_Items(string[] sourceArray, string[] destinationArray, string[] expectedArray) |
||||
|
{ |
||||
|
var result = ArrayMatcher.Match(sourceArray, destinationArray); |
||||
|
Assert.Equal(expectedArray, result); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue