Skip to content

Commit 80944a8

Browse files
committed
#1397 Add polymorphism support to generated OpenAPI json and UI:
- Reworked Polymorphism registration to use only AbstractTypeFactory and one Document filter; - Added Discriminator field to TypeInfo<BaseType> which should be filled after RegisterType() for proper polymorphism work;
1 parent c648cb7 commit 80944a8

11 files changed

+106
-216
lines changed

VirtoCommerce.Platform.Core/Common/AbstractTypeFactory.cs

+7-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4-
using System.Text;
5-
using System.Threading.Tasks;
64

75
namespace VirtoCommerce.Platform.Core.Common
86
{
@@ -52,7 +50,7 @@ public static TypeInfo<BaseType> OverrideType<OldType, NewType>() where NewType
5250
var existTypeInfo = _typeInfos.FirstOrDefault(x => x.Type == oldType);
5351
var newTypeInfo = new TypeInfo<BaseType>(newType);
5452
if (existTypeInfo != null)
55-
{
53+
{
5654
_typeInfos.Remove(existTypeInfo);
5755
}
5856

@@ -85,9 +83,9 @@ public static BaseType TryCreateInstance(string typeName)
8583
var typeInfo = _typeInfos.FirstOrDefault(x => x.Type.Name.EqualsInvariant(typeName));
8684
//Then need to find in inheritance chain from registered types
8785
if (typeInfo == null)
88-
{
86+
{
8987
typeInfo = _typeInfos.Where(x => x.IsAssignableTo(typeName)).FirstOrDefault();
90-
}
88+
}
9189
if (typeInfo != null)
9290
{
9391
if (typeInfo.Factory != null)
@@ -104,7 +102,7 @@ public static BaseType TryCreateInstance(string typeName)
104102
retVal = (BaseType)Activator.CreateInstance(typeof(BaseType));
105103
}
106104
return retVal;
107-
}
105+
}
108106
}
109107

110108
/// <summary>
@@ -122,10 +120,11 @@ public TypeInfo(Type type)
122120
public Type Type { get; private set; }
123121
public Type MappedType { get; set; }
124122
public ICollection<object> Services { get; set; }
123+
public string Discriminator { get; set; }
125124

126125
public T GetService<T>()
127126
{
128-
return Services.OfType<T>().FirstOrDefault(); ;
127+
return Services.OfType<T>().FirstOrDefault();
129128
}
130129

131130
public TypeInfo<BaseType> WithService<T>(T service)

VirtoCommerce.Platform.Core/Common/IPolymorphicBaseTypeInfo.cs

-12
This file was deleted.

VirtoCommerce.Platform.Core/Common/IPolymorphismRegistrar.cs

-8
This file was deleted.

VirtoCommerce.Platform.Core/Common/PolymorphicBaseTypeInfo.cs

-23
This file was deleted.

VirtoCommerce.Platform.Core/Common/PolymorphismRegistrar.cs

-55
This file was deleted.

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

-4
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,9 @@
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" />
8078
<Compile Include="Common\IValueObject.cs" />
8179
<Compile Include="Common\ObservableChangeTracker.cs" />
8280
<Compile Include="Common\PlatformVersion.cs" />
83-
<Compile Include="Common\PolymorphicBaseTypeInfo.cs" />
84-
<Compile Include="Common\PolymorphismRegistrar.cs" />
8581
<Compile Include="Common\PredicateBuilder.cs" />
8682
<Compile Include="Common\PrimaryKeyResolvingMap.cs" />
8783
<Compile Include="Common\SemanticVersion.cs" />

VirtoCommerce.Platform.Web/Startup.cs

-6
Original file line numberDiff line numberDiff line change
@@ -863,12 +863,6 @@ 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
872866
}
873867

874868
private static string NormalizePath(IPathMapper pathMapper, string path)
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,127 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using System.Web.Http.Description;
36
using Swashbuckle.Swagger;
47
using VirtoCommerce.Platform.Core.Common;
58

69
namespace VirtoCommerce.Platform.Web.Swagger
710
{
811
public class PolymorphismDocumentFilter : IDocumentFilter
912
{
10-
private readonly string _moduleName;
11-
private readonly IPolymorphismRegistrar _polymorphismRegistrar;
1213
private readonly bool _useFullTypeNames;
1314

14-
public PolymorphismDocumentFilter(IPolymorphismRegistrar polymorphismRegistrar, string moduleName, bool useFullTypeNames)
15+
public PolymorphismDocumentFilter(bool useFullTypeNames)
1516
{
16-
_polymorphismRegistrar = polymorphismRegistrar ?? throw new ArgumentNullException(nameof(polymorphismRegistrar));
17-
_moduleName = moduleName;
1817
_useFullTypeNames = useFullTypeNames;
1918
}
2019

2120
[CLSCompliant(false)]
22-
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, System.Web.Http.Description.IApiExplorer apiExplorer)
21+
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
2322
{
24-
foreach (var polymorphicBaseTypeInfo in _polymorphismRegistrar.GetPolymorphicBaseTypes(_moduleName))
25-
{
26-
RegisterSubClasses(schemaRegistry, polymorphicBaseTypeInfo);
27-
}
23+
RegisterSubClasses(schemaRegistry, apiExplorer);
2824
}
2925

30-
private void RegisterSubClasses(SchemaRegistry schemaRegistry, IPolymorphicBaseTypeInfo polymorphicBaseTypeInfo)
26+
private void RegisterSubClasses(SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
3127
{
32-
var abstractType = polymorphicBaseTypeInfo.Type;
33-
var discriminatorName = polymorphicBaseTypeInfo.DiscriminatorName;
28+
foreach (var type in apiExplorer.ApiDescriptions.Select(x => x.ResponseType()).Where(x => x != null).Distinct())
29+
{
30+
var schema = GetTypeSchema(schemaRegistry, type, false);
31+
32+
// IApiExplorer contains types from all api controllers, so some of them could be not presented in specific module schemaRegistry.
33+
if (schema != null)
34+
{
35+
// Find if type is registered in AbstractTypeFactory with descendants and TypeInfo have Discriminator filled
36+
var polymorphicBaseTypeInfoType = typeof(PolymorphicBaseTypeInfo<>).MakeGenericType(type);
37+
var polymorphicBaseTypeInfoInstance = Activator.CreateInstance(polymorphicBaseTypeInfoType);
38+
var derivedTypesPropertyGetter = polymorphicBaseTypeInfoType.GetProperty("DerivedTypes", BindingFlags.Instance | BindingFlags.Public).GetGetMethod();
39+
var derivedTypes = (derivedTypesPropertyGetter.Invoke(polymorphicBaseTypeInfoInstance, null) as IEnumerable<Type>).ToArray();
40+
var discriminatorPropertyGetter = polymorphicBaseTypeInfoType.GetProperty("Discriminator", BindingFlags.Instance | BindingFlags.Public).GetGetMethod();
41+
var discriminator = discriminatorPropertyGetter.Invoke(polymorphicBaseTypeInfoInstance, null) as string;
42+
43+
// Polymorphism registration required if we have at least one TypeInfo in AbstractTypeFactory and Discriminator is set
44+
if (derivedTypes.Length > 0 && !string.IsNullOrEmpty(discriminator))
45+
{
46+
foreach (var derivedType in derivedTypes)
47+
{
48+
var derivedTypeSchema = GetTypeSchema(schemaRegistry, derivedType, false);
49+
50+
// Make sure all derivedTypes are in schemaRegistry
51+
if (derivedTypeSchema == null)
52+
{
53+
derivedTypeSchema = schemaRegistry.GetOrRegister(derivedType);
54+
}
55+
56+
AddInheritanceToDerivedTypeSchema(derivedTypeSchema, type);
57+
}
3458

35-
// Need to make first property character lower to avoid properties duplication because of case, as all properties in OpenApi spec are in camelCase
36-
discriminatorName = char.ToLowerInvariant(discriminatorName[0]) + discriminatorName.Substring(1);
59+
AddDiscriminatorToBaseType(schemaRegistry, type, discriminator);
60+
}
61+
}
62+
}
63+
}
3764

38-
var typeName = _useFullTypeNames ? abstractType.FullName : abstractType.FriendlyId();
39-
var parentSchema = schemaRegistry.Definitions[typeName];
65+
private Schema GetTypeSchema(SchemaRegistry schemaRegistry, Type type, bool throwOnEmpty)
66+
{
67+
Schema result = null;
68+
var typeName = _useFullTypeNames ? type.FullName : type.Name;
4069

41-
//set up a discriminator property (it must be required)
42-
parentSchema.discriminator = discriminatorName;
43-
parentSchema.required = new List<string> { discriminatorName };
70+
// IApiExplorer contains types from all api controllers, so some of them could be not presented in specific module schemaRegistry.
71+
if (schemaRegistry.Definitions.ContainsKey(typeName))
72+
{
73+
result = schemaRegistry.Definitions[typeName];
74+
}
4475

45-
if (!parentSchema.properties.ContainsKey(discriminatorName))
76+
if (throwOnEmpty && result == null)
4677
{
47-
parentSchema.properties.Add(discriminatorName, new Schema { type = "string" });
78+
throw new KeyNotFoundException($"Derived type \"{type.FullName}\" does not exist in SchemaRegistry.");
4879
}
4980

50-
//register all subclasses
51-
var derivedTypes = polymorphicBaseTypeInfo.DerivedTypes;
81+
return result;
82+
}
83+
84+
private void AddDiscriminatorToBaseType(SchemaRegistry schemaRegistry, Type baseType, string discriminator)
85+
{
86+
// Need to make first discriminator character lower to avoid properties duplication because of case, as all properties in OpenApi spec are in camelCase
87+
discriminator = char.ToLowerInvariant(discriminator[0]) + discriminator.Substring(1);
88+
89+
var baseTypeSchema = GetTypeSchema(schemaRegistry, baseType, true);
5290

53-
foreach (var item in derivedTypes)
91+
// Set up a discriminator property (it must be required)
92+
baseTypeSchema.discriminator = discriminator;
93+
baseTypeSchema.required = new List<string> { discriminator };
94+
95+
if (!baseTypeSchema.properties.ContainsKey(discriminator))
5496
{
55-
schemaRegistry.GetOrRegister(item);
97+
baseTypeSchema.properties.Add(discriminator, new Schema { type = "string" });
5698
}
5799
}
100+
101+
private void AddInheritanceToDerivedTypeSchema(Schema derivedTypeSchema, Type baseType)
102+
{
103+
var clonedSchema = new Schema
104+
{
105+
properties = derivedTypeSchema.properties,
106+
type = derivedTypeSchema.type,
107+
required = derivedTypeSchema.required
108+
};
109+
110+
var baseTypeName = _useFullTypeNames ? baseType.FullName : baseType.FriendlyId();
111+
112+
var parentSchema = new Schema { @ref = "#/definitions/" + baseTypeName };
113+
114+
derivedTypeSchema.allOf = new List<Schema> { parentSchema, clonedSchema };
115+
116+
//reset properties for they are included in allOf, should be null but code does not handle it
117+
derivedTypeSchema.properties = new Dictionary<string, Schema>();
118+
}
119+
120+
// This private class is used to simplify querying from AbstractTypeFactory<T>.AllTypeInfos properties (no need to implement Linq queries using reflection)
121+
private class PolymorphicBaseTypeInfo<T>
122+
{
123+
public string Discriminator { get => AbstractTypeFactory<T>.AllTypeInfos.FirstOrDefault()?.Discriminator; }
124+
public IEnumerable<Type> DerivedTypes { get => AbstractTypeFactory<T>.AllTypeInfos.Select(x => x.Type) ?? Enumerable.Empty<Type>(); }
125+
}
58126
}
59127
}

VirtoCommerce.Platform.Web/Swagger/PolymorphismSchemaFilter.cs

-57
This file was deleted.

0 commit comments

Comments
 (0)