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