From 82e9803ca8844f87e257b614425287b9046f87a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 20 Nov 2019 14:31:12 +0300 Subject: [PATCH] Add models to the API definition --- .../Tab/AbpTabLinkTagHelperService.cs | 4 +- .../AspNetCoreApiDescriptionModelProvider.cs | 72 ++++++++++++++++++- .../Volo.Abp.Core/System/AbpTypeExtensions.cs | 21 +++--- .../Volo/Abp/Reflection/TypeHelper.cs | 44 ++++++++++++ .../Volo/Abp/Threading/AsyncHelper.cs | 7 +- .../DynamicProxying/ApiDescriptionFinder.cs | 2 +- .../ApplicationApiDescriptionModel.cs | 6 +- .../Modeling/ControllerApiDescriptionModel.cs | 4 +- .../ControllerInterfaceApiDescriptionModel.cs | 2 +- .../MethodParameterApiDescriptionModel.cs | 2 +- .../Modeling/ParameterApiDescriptionModel.cs | 2 +- .../Modeling/PropertyApiDescriptionModel.cs | 23 ++++++ .../ReturnValueApiDescriptionModel.cs | 2 +- .../Http/Modeling/TypeApiDescriptionModel.cs | 54 ++++++++++++++ .../JQuery/JQueryProxyScriptGenerator.cs | 2 +- 15 files changed, 225 insertions(+), 22 deletions(-) create mode 100644 framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs create mode 100644 framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabLinkTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabLinkTagHelperService.cs index ba5e3e811a..54a29bf691 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabLinkTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabLinkTagHelperService.cs @@ -7,7 +7,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab { public class AbpTabLinkTagHelperService : AbpTagHelperService { - public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { SetPlaceholderForNameIfNotProvided(); @@ -18,6 +18,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab tabHeaderItems.Add(new TabItem(tabHeader, "", false, TagHelper.Name, TagHelper.ParentDropdownName, false)); output.SuppressOutput(); + + return Task.CompletedTask; } protected virtual string GetTabHeaderItem(TagHelperContext context, TagHelperOutput output) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs index d9cb936326..696860734f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; +using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -16,6 +17,8 @@ using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.Utils; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Modeling; +using Volo.Abp.Reflection; +using Volo.Abp.Threading; namespace Volo.Abp.AspNetCore.Mvc { @@ -61,12 +64,12 @@ namespace Volo.Abp.AspNetCore.Mvc return model; } - private void AddApiDescriptionToModel(ApiDescription apiDescription, ApplicationApiDescriptionModel model) + private void AddApiDescriptionToModel(ApiDescription apiDescription, ApplicationApiDescriptionModel applicationModel) { var controllerType = apiDescription.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.AsType(); var setting = FindSetting(controllerType); - var moduleModel = model.GetOrAddModule(GetRootPath(controllerType, setting)); + var moduleModel = applicationModel.GetOrAddModule(GetRootPath(controllerType, setting)); var controllerModel = moduleModel.GetOrAddController(controllerType.FullName, CalculateControllerName(controllerType, setting), controllerType, _modelOptions.IgnoredInterfaces); @@ -80,6 +83,7 @@ namespace Volo.Abp.AspNetCore.Mvc } Logger.LogDebug($"ActionApiDescriptionModel.Create: {controllerModel.ControllerName}.{uniqueMethodName}"); + var actionModel = controllerModel.AddAction(uniqueMethodName, ActionApiDescriptionModel.Create( uniqueMethodName, method, @@ -88,6 +92,8 @@ namespace Volo.Abp.AspNetCore.Mvc GetSupportedVersions(controllerType, method, setting) )); + AddCustomTypesToModel(applicationModel, method); + AddParameterDescriptionsToModel(actionModel, method, apiDescription); } @@ -149,6 +155,68 @@ namespace Volo.Abp.AspNetCore.Mvc return supportedVersions.Select(v => v.ToString()).Distinct().ToList(); } + private void AddCustomTypesToModel(ApplicationApiDescriptionModel applicationModel, MethodInfo method) + { + foreach (var parameterInfo in method.GetParameters()) + { + AddCustomTypesToModel(applicationModel, parameterInfo.ParameterType); + } + + AddCustomTypesToModel(applicationModel, method.ReturnType); + } + + private static void AddCustomTypesToModel(ApplicationApiDescriptionModel applicationModel, [CanBeNull] Type type) + { + if (type == null) + { + return; + } + + type = AsyncHelper.UnwrapTask(type); + + if (type == typeof(object) || + type == typeof(void) || + type == typeof(Enum) || + type == typeof(ValueType) || + TypeHelper.IsPrimitiveExtended(type)) + { + return; + } + + if (TypeHelper.IsEnumerable(type, out var itemType)) + { + AddCustomTypesToModel(applicationModel, itemType); + return; + } + + if (TypeHelper.IsDictionary(type, out var keyType, out var valueType)) + { + AddCustomTypesToModel(applicationModel, keyType); + AddCustomTypesToModel(applicationModel, valueType); + return; + } + + /* TODO: Add interfaces + */ + + var typeAsString = type.FullName; + + if (applicationModel.Types.ContainsKey(typeAsString)) + { + return; + } + + var typeModel = TypeApiDescriptionModel.Create(type); + applicationModel.Types[typeAsString] = typeModel; + + AddCustomTypesToModel(applicationModel, type.BaseType); + + foreach (var propertyInfo in type.GetProperties()) + { + AddCustomTypesToModel(applicationModel, propertyInfo.PropertyType); + } + } + private void AddParameterDescriptionsToModel(ActionApiDescriptionModel actionModel, MethodInfo method, ApiDescription apiDescription) { if (!apiDescription.ParameterDescriptions.Any()) diff --git a/framework/src/Volo.Abp.Core/System/AbpTypeExtensions.cs b/framework/src/Volo.Abp.Core/System/AbpTypeExtensions.cs index a930096404..fe23c77530 100644 --- a/framework/src/Volo.Abp.Core/System/AbpTypeExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/AbpTypeExtensions.cs @@ -1,15 +1,11 @@ using System.Collections.Generic; using JetBrains.Annotations; +using Volo.Abp; namespace System { public static class AbpTypeExtensions { - public static string GetFullNameWithAssemblyName(this Type type) - { - return type.FullName + ", " + type.Assembly.GetName().Name; - } - /// /// Determines whether an instance of this type can be assigned to /// an instance of the . @@ -17,8 +13,10 @@ namespace System /// Internally uses . /// /// Target type (as reverse). - public static bool IsAssignableTo(this Type type) + public static bool IsAssignableTo([NotNull] this Type type) { + Check.NotNull(type, nameof(type)); + return type.IsAssignableTo(typeof(TTarget)); } @@ -30,8 +28,11 @@ namespace System /// /// this type /// Target type - public static bool IsAssignableTo(this Type type, Type targetType) + public static bool IsAssignableTo([NotNull] this Type type, [NotNull] Type targetType) { + Check.NotNull(type, nameof(type)); + Check.NotNull(targetType, nameof(targetType)); + return targetType.IsAssignableFrom(type); } @@ -40,8 +41,10 @@ namespace System /// /// The type to get its base classes. /// True, to include the standard type in the returned array. - public static Type[] GetBaseClasses(this Type type, bool includeObject = true) + public static Type[] GetBaseClasses([NotNull] this Type type, bool includeObject = true) { + Check.NotNull(type, nameof(type)); + var types = new List(); AddTypeAndBaseTypesRecursively(types, type.BaseType, includeObject); return types.ToArray(); @@ -52,6 +55,8 @@ namespace System [CanBeNull] Type type, bool includeObject) { + Check.NotNull(types, nameof(types)); + if (type == null) { return; diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs index a708ecc10d..cab8c41b2c 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs @@ -1,4 +1,7 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Reflection; @@ -54,6 +57,47 @@ namespace Volo.Abp.Reflection return t; } + public static bool IsEnumerable(Type type, out Type itemType) + { + var enumerableTypes = ReflectionHelper.GetImplementedGenericTypes(type, typeof(IEnumerable<>)); + if (enumerableTypes.Count == 1) + { + itemType = enumerableTypes[0].GenericTypeArguments[0]; + return true; + } + + if (typeof(IEnumerable).IsAssignableFrom(type)) + { + itemType = typeof(object); + return true; + } + + itemType = null; + return false; + } + + public static bool IsDictionary(Type type, out Type keyType, out Type valueType) + { + var enumerableTypes = ReflectionHelper.GetImplementedGenericTypes(type, typeof(IDictionary<,>)); + if (enumerableTypes.Count == 1) + { + keyType = enumerableTypes[0].GenericTypeArguments[0]; + valueType = enumerableTypes[0].GenericTypeArguments[2]; + return true; + } + + if (typeof(IDictionary).IsAssignableFrom(type)) + { + keyType = typeof(object); + valueType = typeof(object); + return true; + } + + keyType = null; + valueType = null; + return false; + } + private static bool IsPrimitiveExtendedInternal(Type type, bool includeEnums) { if (type.IsPrimitive) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Threading/AsyncHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Threading/AsyncHelper.cs index dc678b8fd4..65e411d038 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Threading/AsyncHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Threading/AsyncHelper.cs @@ -27,6 +27,11 @@ namespace Volo.Abp.Threading return type == typeof(Task) || (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>)); } + public static bool IsTaskOfT([NotNull] this Type type) + { + return type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>); + } + /// /// Returns void if given type is Task. /// Return T, if given type is Task{T}. @@ -41,7 +46,7 @@ namespace Volo.Abp.Threading return typeof(void); } - if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>)) + if (type.IsTaskOfT()) { return type.GenericTypeArguments[0]; } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs index 9b9c8e804c..fe6192fd85 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs @@ -57,7 +57,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying for (int i = 0; i < methodParameters.Length; i++) { - if (action.ParametersOnMethod[i].TypeAsString != methodParameters[i].ParameterType.GetFullNameWithAssemblyName()) + if (action.ParametersOnMethod[i].TypeAsString != methodParameters[i].ParameterType.FullName) { found = false; break; diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApplicationApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApplicationApiDescriptionModel.cs index a7def10625..fad94b3745 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApplicationApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApplicationApiDescriptionModel.cs @@ -10,6 +10,8 @@ namespace Volo.Abp.Http.Modeling { public IDictionary Modules { get; set; } + public IDictionary Types { get; set; } + private ApplicationApiDescriptionModel() { @@ -19,8 +21,8 @@ namespace Volo.Abp.Http.Modeling { return new ApplicationApiDescriptionModel { - //TODO: Why ConcurrentDictionary? - Modules = new ConcurrentDictionary() + Modules = new ConcurrentDictionary(), //TODO: Why ConcurrentDictionary? + Types = new Dictionary() }; } diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs index 2bd9a301ab..dfad32f621 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs @@ -26,7 +26,7 @@ namespace Volo.Abp.Http.Modeling return new ControllerApiDescriptionModel { ControllerName = controllerName, - TypeAsString = type.GetFullNameWithAssemblyName(), + TypeAsString = type.FullName, Actions = new Dictionary(), Interfaces = type .GetInterfaces() @@ -71,7 +71,7 @@ namespace Volo.Abp.Http.Modeling public bool Implements(Type interfaceType) { - return Interfaces.Any(i => i.TypeAsString == interfaceType.GetFullNameWithAssemblyName()); + return Interfaces.Any(i => i.TypeAsString == interfaceType.FullName); } public override string ToString() diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs index 3605cf675b..3868ec94f9 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs @@ -16,7 +16,7 @@ namespace Volo.Abp.Http.Modeling { return new ControllerInterfaceApiDescriptionModel { - TypeAsString = type.GetFullNameWithAssemblyName() + TypeAsString = type.FullName }; } } diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs index 61ffc516f6..fdc50090d2 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs @@ -24,7 +24,7 @@ namespace Volo.Abp.Http.Modeling return new MethodParameterApiDescriptionModel { Name = parameterInfo.Name, - TypeAsString = parameterInfo.ParameterType.GetFullNameWithAssemblyName(), + TypeAsString = parameterInfo.ParameterType.FullName, IsOptional = parameterInfo.IsOptional, DefaultValue = parameterInfo.HasDefaultValue ? parameterInfo.DefaultValue : null }; diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs index d1de33258f..e05e36aec3 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs +++ b/framework/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?.FullName, IsOptional = isOptional, DefaultValue = defaultValue, ConstraintTypes = constraintTypes, diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs new file mode 100644 index 0000000000..ee39f54687 --- /dev/null +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs @@ -0,0 +1,23 @@ +using System; +using System.Reflection; + +namespace Volo.Abp.Http.Modeling +{ + [Serializable] + public class PropertyApiDescriptionModel + { + public string Name { get; set; } + + public string TypeAsString { get; set; } + + //TODO: Validation rules for this property + public static PropertyApiDescriptionModel Create(PropertyInfo propertyInfo) + { + return new PropertyApiDescriptionModel + { + Name = propertyInfo.Name, + TypeAsString = propertyInfo.PropertyType.FullName + }; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs index 001bd1b9c9..78bd5982fe 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs @@ -17,7 +17,7 @@ namespace Volo.Abp.Http.Modeling { return new ReturnValueApiDescriptionModel { - TypeAsString = AsyncHelper.UnwrapTask(type).GetFullNameWithAssemblyName() + TypeAsString = AsyncHelper.UnwrapTask(type).FullName }; } } diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs new file mode 100644 index 0000000000..65f7a61b27 --- /dev/null +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs @@ -0,0 +1,54 @@ +using System; +using System.Linq; + +namespace Volo.Abp.Http.Modeling +{ + [Serializable] + public class TypeApiDescriptionModel + { + public string BaseTypeAsString { get; set; } + + public bool IsEnum { get; set; } + + public string[] EnumNames { get; set; } + + public object[] EnumValues { get; set; } + + public PropertyApiDescriptionModel[] Properties { get; set; } + + private TypeApiDescriptionModel() + { + + } + + public static TypeApiDescriptionModel Create(Type type) + { + var baseType = type.BaseType; + if (baseType == typeof(object)) + { + baseType = null; + } + + var typeModel = new TypeApiDescriptionModel + { + IsEnum = type.IsEnum, + BaseTypeAsString = baseType?.FullName + }; + + if (typeModel.IsEnum) + { + typeModel.EnumNames = type.GetEnumNames(); + typeModel.EnumValues = type.GetEnumValues().Cast().ToArray(); + } + else + { + typeModel.Properties = type + .GetProperties() + .Select(PropertyApiDescriptionModel.Create) + .ToArray(); + } + + return typeModel; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator.cs index 169e9b8764..4cf80f7ed8 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator.cs @@ -125,7 +125,7 @@ namespace Volo.Abp.Http.ProxyScripting.Generators.JQuery script.AppendLine(" url: abp.appPath + '" + ProxyScriptingHelper.GenerateUrlWithParameters(action) + "',"); script.Append(" type: '" + httpMethod + "'"); - if (action.ReturnValue.TypeAsString == typeof(void).GetFullNameWithAssemblyName()) + if (action.ReturnValue.TypeAsString == typeof(void).FullName) { script.AppendLine(","); script.Append(" dataType: null");