Skip to content

Commit 2432b60

Browse files
committed
#1397 Add polymorphism support to generated OpenAPI json and UI:
- Added IPolymorphismRegistrar to register polymorphic types and their discriminator fields for the modules. AbstractTypeFactory is used for getting registered descendant types;
1 parent 6e72faa commit 2432b60

9 files changed

+144
-66
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace VirtoCommerce.Platform.Core.Common
5+
{
6+
public interface IPolymorphicBaseTypeInfo
7+
{
8+
Type Type { get; }
9+
string DiscriminatorName { get; }
10+
IEnumerable<Type> DerivedTypes { get; }
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace VirtoCommerce.Platform.Core.Common
2+
{
3+
public interface IPolymorphismRegistrar
4+
{
5+
IPolymorphicBaseTypeInfo[] GetPolymorphicBaseTypes(string moduleName);
6+
void RegisterPolymorphicBaseType(string moduleName, IPolymorphicBaseTypeInfo polymorphismBaseTypeInfo);
7+
}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace VirtoCommerce.Platform.Core.Common
6+
{
7+
public class PolymorphicBaseTypeInfo<T> : IPolymorphicBaseTypeInfo
8+
{
9+
public PolymorphicBaseTypeInfo(string discriminatorName)
10+
{
11+
if (discriminatorName.IsNullOrEmpty())
12+
{
13+
throw new ArgumentException($"\"{ nameof(discriminatorName) }\" cannot be null or empty");
14+
}
15+
16+
DiscriminatorName = discriminatorName;
17+
}
18+
19+
public Type Type { get => typeof(T); }
20+
public string DiscriminatorName { get; private set; }
21+
public IEnumerable<Type> DerivedTypes { get => AbstractTypeFactory<T>.AllTypeInfos.SelectMany(x => x.AllSubclasses); }
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace VirtoCommerce.Platform.Core.Common
6+
{
7+
public class PolymorphismRegistrar : IPolymorphismRegistrar
8+
{
9+
private readonly Dictionary<string, HashSet<IPolymorphicBaseTypeInfo>> _modulePolymorphicTypes = new Dictionary<string, HashSet<IPolymorphicBaseTypeInfo>>(StringComparer.InvariantCultureIgnoreCase);
10+
11+
public void RegisterPolymorphicBaseType(string moduleName, IPolymorphicBaseTypeInfo polymorphismBaseTypeInfo)
12+
{
13+
if (moduleName == null)
14+
{
15+
throw new ArgumentNullException(nameof(moduleName));
16+
}
17+
18+
if (polymorphismBaseTypeInfo == null)
19+
{
20+
throw new ArgumentNullException(nameof(polymorphismBaseTypeInfo));
21+
}
22+
23+
if (!_modulePolymorphicTypes.ContainsKey(moduleName))
24+
{
25+
_modulePolymorphicTypes.Add(moduleName, new HashSet<IPolymorphicBaseTypeInfo>(AnonymousComparer.Create<IPolymorphicBaseTypeInfo, string>(x => x.Type.FullName)));
26+
}
27+
28+
if (_modulePolymorphicTypes[moduleName].Contains(polymorphismBaseTypeInfo))
29+
{
30+
throw new ArgumentException($"\"{polymorphismBaseTypeInfo.Type.FullName}\" polymorphic base type already registered for \"{moduleName}\" module.");
31+
}
32+
33+
_modulePolymorphicTypes[moduleName].Add(polymorphismBaseTypeInfo);
34+
}
35+
36+
public IPolymorphicBaseTypeInfo[] GetPolymorphicBaseTypes(string moduleName)
37+
{
38+
var result = Array.Empty<IPolymorphicBaseTypeInfo>();
39+
40+
var needAllTypes = string.IsNullOrEmpty(moduleName);
41+
42+
if (needAllTypes)
43+
{
44+
result = _modulePolymorphicTypes.Values.SelectMany(x => x).Distinct().ToArray();
45+
}
46+
47+
if (!needAllTypes && _modulePolymorphicTypes.ContainsKey(moduleName))
48+
{
49+
result = _modulePolymorphicTypes[moduleName].ToArray();
50+
}
51+
52+
return result;
53+
}
54+
}
55+
}

VirtoCommerce.Platform.Core/VirtoCommerce.Platform.Core.csproj

+4
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,13 @@
7575
<Compile Include="Common\ICancellationToken.cs" />
7676
<Compile Include="Common\IEntity.cs" />
7777
<Compile Include="Common\IFactory.cs" />
78+
<Compile Include="Common\IPolymorphicBaseTypeInfo.cs" />
79+
<Compile Include="Common\IPolymorphismRegistrar.cs" />
7880
<Compile Include="Common\IValueObject.cs" />
7981
<Compile Include="Common\ObservableChangeTracker.cs" />
8082
<Compile Include="Common\PlatformVersion.cs" />
83+
<Compile Include="Common\PolymorphicBaseTypeInfo.cs" />
84+
<Compile Include="Common\PolymorphismRegistrar.cs" />
8185
<Compile Include="Common\PredicateBuilder.cs" />
8286
<Compile Include="Common\PrimaryKeyResolvingMap.cs" />
8387
<Compile Include="Common\SemanticVersion.cs" />

VirtoCommerce.Platform.Web/Startup.cs

+6
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,12 @@ private static void InitializePlatform(
863863
inProcessBus.RegisterHandler<UserPasswordChangedEvent>(async (message, token) => await container.Resolve<LogChangesUserChangedEventHandler>().Handle(message));
864864
inProcessBus.RegisterHandler<UserResetPasswordEvent>(async (message, token) => await container.Resolve<LogChangesUserChangedEventHandler>().Handle(message));
865865
#endregion
866+
867+
#region Polymorphism registrar
868+
869+
container.RegisterInstance<IPolymorphismRegistrar>(new PolymorphismRegistrar());
870+
871+
#endregion
866872
}
867873

868874
private static string NormalizePath(IPathMapper pathMapper, string path)

VirtoCommerce.Platform.Web/Swagger/PolymorphismDocumentFilter.cs

+15-12
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,41 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Linq;
43
using Swashbuckle.Swagger;
4+
using VirtoCommerce.Platform.Core.Common;
55

66
namespace VirtoCommerce.Platform.Web.Swagger
77
{
88
public class PolymorphismDocumentFilter : IDocumentFilter
99
{
10-
private readonly Type[] _types;
10+
private readonly string _moduleName;
11+
private readonly IPolymorphismRegistrar _polymorphismRegistrar;
12+
private readonly bool _useFullTypeNames;
1113

12-
public PolymorphismDocumentFilter(Type[] types)
14+
public PolymorphismDocumentFilter(IPolymorphismRegistrar polymorphismRegistrar, string moduleName, bool useFullTypeNames)
1315
{
14-
_types = types;
16+
_polymorphismRegistrar = polymorphismRegistrar ?? throw new ArgumentNullException(nameof(polymorphismRegistrar));
17+
_moduleName = moduleName;
18+
_useFullTypeNames = useFullTypeNames;
1519
}
1620

1721
[CLSCompliant(false)]
1822
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, System.Web.Http.Description.IApiExplorer apiExplorer)
1923
{
20-
foreach (var type in _types)
24+
foreach (var polymorphicBaseTypeInfo in _polymorphismRegistrar.GetPolymorphicBaseTypes(_moduleName))
2125
{
22-
RegisterSubClasses(schemaRegistry, type);
26+
RegisterSubClasses(schemaRegistry, polymorphicBaseTypeInfo);
2327
}
2428
}
2529

26-
private static void RegisterSubClasses(SchemaRegistry schemaRegistry, Type abstractType)
30+
private void RegisterSubClasses(SchemaRegistry schemaRegistry, IPolymorphicBaseTypeInfo polymorphicBaseTypeInfo)
2731
{
28-
var discriminatorName = "type";
32+
var abstractType = polymorphicBaseTypeInfo.Type;
33+
var discriminatorName = polymorphicBaseTypeInfo.DiscriminatorName;
2934

3035
// Need to make first property character lower to avoid properties duplication because of case, as all properties in OpenApi spec are in camelCase
3136
discriminatorName = char.ToLowerInvariant(discriminatorName[0]) + discriminatorName.Substring(1);
3237

33-
var typeName = schemaRegistry.Definitions.ContainsKey(abstractType.FullName) ? abstractType.FullName : abstractType.FriendlyId();
38+
var typeName = _useFullTypeNames ? abstractType.FullName : abstractType.FriendlyId();
3439
var parentSchema = schemaRegistry.Definitions[typeName];
3540

3641
//set up a discriminator property (it must be required)
@@ -43,9 +48,7 @@ private static void RegisterSubClasses(SchemaRegistry schemaRegistry, Type abstr
4348
}
4449

4550
//register all subclasses
46-
var derivedTypes = abstractType.Assembly
47-
.GetTypes()
48-
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
51+
var derivedTypes = polymorphicBaseTypeInfo.DerivedTypes;
4952

5053
foreach (var item in derivedTypes)
5154
{

VirtoCommerce.Platform.Web/Swagger/PolymorphismSchemaFilter.cs

+10-14
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,30 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using Swashbuckle.Swagger;
5+
using VirtoCommerce.Platform.Core.Common;
56

67
namespace VirtoCommerce.Platform.Web.Swagger
78
{
8-
99
public class PolymorphismSchemaFilter : ISchemaFilter
1010
{
11-
private readonly Type[] _types;
11+
private readonly string _moduleName;
12+
private readonly IPolymorphismRegistrar _polymorphismRegistrar;
1213
private readonly Lazy<HashSet<Type>> _derivedTypes;
14+
private readonly bool _useFullTypeNames;
1315

14-
public PolymorphismSchemaFilter(Type[] types)
16+
public PolymorphismSchemaFilter(IPolymorphismRegistrar polymorphismRegistrar, string moduleName, bool useFullTypeNames)
1517
{
16-
_types = types;
18+
_polymorphismRegistrar = polymorphismRegistrar ?? throw new ArgumentNullException(nameof(polymorphismRegistrar));
19+
_moduleName = moduleName;
1720
_derivedTypes = new Lazy<HashSet<Type>>(Init);
21+
_useFullTypeNames = useFullTypeNames;
1822
}
1923

2024
private HashSet<Type> Init()
2125
{
2226
var result = new HashSet<Type>();
2327

24-
var derivedTypes = _types.SelectMany(baseType =>
25-
baseType.Assembly
26-
.GetTypes()
27-
.Where(derivedType => baseType != derivedType && baseType.IsAssignableFrom(derivedType)));
28-
29-
foreach (var item in derivedTypes)
30-
{
31-
result.Add(item);
32-
}
28+
result.AddRange(_polymorphismRegistrar.GetPolymorphicBaseTypes(_moduleName).SelectMany(x => x.DerivedTypes).Distinct());
3329

3430
return result;
3531
}
@@ -47,7 +43,7 @@ public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
4743
};
4844

4945
var baseType = type.BaseType;
50-
var baseTypeName = schemaRegistry.Definitions.ContainsKey(baseType.FullName) ? baseType.FullName : baseType.FriendlyId();
46+
var baseTypeName = _useFullTypeNames ? baseType.FullName : baseType.FriendlyId();
5147

5248
var parentSchema = new Schema { @ref = "#/definitions/" + baseTypeName };
5349

VirtoCommerce.Platform.Web/Swagger/SwaggerConfig.cs

+11-40
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
32
using System.IO;
43
using System.Linq;
54
using System.Net.Http;
@@ -33,21 +32,12 @@ public static void RegisterRoutes(IUnityContainer container)
3332
var xmlCommentsFilePaths = xmlCommentsDirectoryPaths.SelectMany(GetXmlFilesPaths).ToArray();
3433

3534
// Add separate swagger generator for platform
36-
EnableSwagger("VirtoCommerce.Platform", httpConfiguration, container, routePrefix, xmlCommentsFilePaths, false, Assembly.GetExecutingAssembly(), null);
35+
EnableSwagger("VirtoCommerce.Platform", httpConfiguration, container, routePrefix, xmlCommentsFilePaths, false, Assembly.GetExecutingAssembly());
3736

3837
// Add separate swagger generator for each installed module
39-
var allmodules = container.Resolve<IModuleCatalog>().Modules.OfType<ManifestModuleInfo>().Where(m => m.ModuleInstance != null);
40-
41-
ICollection<Type> allPolymorphicTypes = new List<Type>();
42-
43-
// Add separate swagger generator for each installed module
44-
foreach (var module in allmodules)
38+
foreach (var module in container.Resolve<IModuleCatalog>().Modules.OfType<ManifestModuleInfo>().Where(m => m.ModuleInstance != null))
4539
{
46-
var polimorphicTypes = GetPolymorphicTypes(module);
47-
48-
allPolymorphicTypes.AddRange(polimorphicTypes);
49-
50-
EnableSwagger(module.ModuleName, httpConfiguration, container, routePrefix, xmlCommentsFilePaths, module.UseFullTypeNameInSwagger, module.ModuleInstance.GetType().Assembly, polimorphicTypes);
40+
EnableSwagger(module.ModuleName, httpConfiguration, container, routePrefix, xmlCommentsFilePaths, module.UseFullTypeNameInSwagger, module.ModuleInstance.GetType().Assembly);
5141
}
5242

5343
// Add full swagger generator
@@ -72,7 +62,7 @@ public static void RegisterRoutes(IUnityContainer container)
7262
.Replace(',', '-')
7363
);
7464

75-
AddProlymorphicFilters(c, allPolymorphicTypes.ToArray());
65+
AddProlymorphicFilters(container, c, null, true);
7666

7767
ApplyCommonSwaggerConfiguration(c, container, string.Empty, xmlCommentsFilePaths);
7868
})
@@ -91,32 +81,14 @@ public static void RegisterRoutes(IUnityContainer container)
9181
});
9282
}
9383

94-
private static Type[] GetPolymorphicTypes(ManifestModuleInfo module)
95-
{
96-
//TODO: Could move type loading to module loading mechanism with its error handling
97-
Type[] polimorphicTypes = null;
98-
try
99-
{
100-
polimorphicTypes = module.OpenAPIPolymorphicTypes
101-
.Select(x => Type.GetType(x, false))
102-
.Where(x => x != null)
103-
.ToArray();
104-
}
105-
// Need to add catch as even with GetType throwOnError = false it could throw
106-
catch { }
107-
108-
return polimorphicTypes;
109-
}
110-
11184
private static void EnableSwagger(
11285
string moduleName,
11386
HttpConfiguration httpConfiguration,
11487
IUnityContainer container,
11588
string routePrefix,
11689
string[] xmlCommentsFilePaths,
11790
bool useFullTypeNameInSchemaIds,
118-
Assembly apiAssembly,
119-
Type[] polymorphicTypes)
91+
Assembly apiAssembly)
12092
{
12193
var routeName = string.Concat("swagger_", moduleName);
12294
var routeTemplate = string.Concat(routePrefix, "docs/", moduleName, "/{apiVersion}");
@@ -136,17 +108,16 @@ private static void EnableSwagger(
136108
ApplyCommonSwaggerConfiguration(c, container, moduleName, xmlCommentsFilePaths);
137109
c.OperationFilter(() => new ModuleTagsFilter(moduleName));
138110

139-
AddProlymorphicFilters(c, polymorphicTypes);
111+
AddProlymorphicFilters(container, c, moduleName, false);
140112
});
141113
}
142114

143-
private static void AddProlymorphicFilters(SwaggerDocsConfig swaggerDocsConfig, Type[] polymorphicTypes)
115+
private static void AddProlymorphicFilters(IUnityContainer container, SwaggerDocsConfig swaggerDocsConfig, string moduleName, bool useFullTypeName)
144116
{
145-
if (polymorphicTypes?.Any() == true)
146-
{
147-
swaggerDocsConfig.DocumentFilter(() => new PolymorphismDocumentFilter(polymorphicTypes));
148-
swaggerDocsConfig.SchemaFilter(() => new PolymorphismSchemaFilter(polymorphicTypes));
149-
}
117+
var polymorphismRegistrar = container.Resolve<IPolymorphismRegistrar>();
118+
119+
swaggerDocsConfig.DocumentFilter(() => new PolymorphismDocumentFilter(polymorphismRegistrar, moduleName, useFullTypeName));
120+
swaggerDocsConfig.SchemaFilter(() => new PolymorphismSchemaFilter(polymorphismRegistrar, moduleName, useFullTypeName));
150121
}
151122

152123
private static Uri ComputeHostAsSeenByOriginalClient(HttpRequestMessage message)

0 commit comments

Comments
 (0)