Skip to content

Commit 0779722

Browse files
committed
* #1397 Add polymorphism support to generated OpenAPI json and UI using Swashbuckle:
- Working prototype for polymorphic object passing through API. - Added filters for adding discriminator to the schema; - Each polymorphic type should have property "Type" containig its real type; - Filters added to module sawgger docs and general one; TODO: - Need to add Registrar for registering polymorphic types and use it inside Swagger config; - Possible to add interface ISupportPolymorphicAPI with string Type property which is used as discriminator in OpenAPI doc.
1 parent 02b5099 commit 0779722

File tree

4 files changed

+160
-0
lines changed

4 files changed

+160
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System.Collections.Generic;
2+
using System.Web.Http;
3+
using System.Web.Http.Description;
4+
5+
namespace VirtoCommerce.Platform.Web.Controllers.Api
6+
{
7+
[RoutePrefix("api/platform/poly")]
8+
public class PolyController : ApiController
9+
{
10+
public PolyController()
11+
{
12+
}
13+
14+
[HttpGet]
15+
[Route("")]
16+
[ResponseType(typeof(BaseClass[]))]
17+
public IHttpActionResult GetPolyTypes()
18+
{
19+
//return Ok(new BaseClassGroup
20+
//{
21+
// Name = "GroupObject2",
22+
// AdditionalInfo = "Add info on this object",
23+
// Items = new[]
24+
// {
25+
// new BaseClass { Name = "ChildItem1"},
26+
// new BaseClass { Name = "ChildItem2"},
27+
// }
28+
//});
29+
return Ok(new BaseClass[]
30+
{
31+
new BaseClass { Name = "BaseObject1"},
32+
new BaseClass { Name = "BaseObject2"},
33+
new BaseClassGroup
34+
{
35+
Name = "GroupObject2",
36+
AdditionalInfo = "Add info on this object",
37+
Items = new[]
38+
{
39+
new BaseClass { Name = "ChildItem1"},
40+
new BaseClass { Name = "ChildItem2"},
41+
}
42+
},
43+
});
44+
}
45+
}
46+
47+
public class BaseClass
48+
{
49+
public string Name { get; set; }
50+
public string Type
51+
{
52+
get
53+
{
54+
return GetType().Name;
55+
}
56+
}
57+
}
58+
59+
public class BaseClassGroup : BaseClass
60+
{
61+
public ICollection<BaseClass> Items { get; set; }
62+
public string AdditionalInfo { get; set; }
63+
}
64+
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Swashbuckle.Swagger;
5+
6+
namespace VirtoCommerce.Platform.Web.Swagger
7+
{
8+
9+
public class PolymorphismSchemaFilter<T> : ISchemaFilter
10+
{
11+
private readonly Lazy<HashSet<Type>> derivedTypes = new Lazy<HashSet<Type>>(Init);
12+
13+
private static HashSet<Type> Init()
14+
{
15+
var abstractType = typeof(T);
16+
var dTypes = abstractType.Assembly
17+
.GetTypes()
18+
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
19+
20+
var result = new HashSet<Type>();
21+
22+
foreach (var item in dTypes)
23+
result.Add(item);
24+
25+
return result;
26+
}
27+
28+
[CLSCompliant(false)]
29+
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
30+
{
31+
if (!derivedTypes.Value.Contains(type))
32+
return;
33+
34+
var clonedSchema = new Schema
35+
{
36+
properties = schema.properties,
37+
type = schema.type,
38+
required = schema.required
39+
};
40+
41+
//schemaRegistry.Definitions[typeof(T).Name]; does not work correctly in SwashBuckle
42+
var parentSchema = new Schema { @ref = "#/definitions/" + typeof(T).Name };
43+
44+
schema.allOf = new List<Schema> { parentSchema, clonedSchema };
45+
46+
//reset properties for they are included in allOf, should be null but code does not handle it
47+
schema.properties = new Dictionary<string, Schema>();
48+
}
49+
}
50+
51+
52+
public class PolymorphismDocumentFilter<T> : IDocumentFilter
53+
{
54+
[CLSCompliant(false)]
55+
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, System.Web.Http.Description.IApiExplorer apiExplorer)
56+
{
57+
RegisterSubClasses(schemaRegistry, typeof(T));
58+
}
59+
60+
61+
private static void RegisterSubClasses(SchemaRegistry schemaRegistry, Type abstractType)
62+
{
63+
const string discriminatorName = "type";
64+
65+
var typeName = schemaRegistry.Definitions.ContainsKey(abstractType.FullName) ? abstractType.FullName : abstractType.FriendlyId();
66+
var parentSchema = schemaRegistry.Definitions[typeName];
67+
68+
//set up a discriminator property (it must be required)
69+
parentSchema.discriminator = discriminatorName;
70+
parentSchema.required = new List<string> { discriminatorName };
71+
72+
if (!parentSchema.properties.ContainsKey(discriminatorName))
73+
parentSchema.properties.Add(discriminatorName, new Schema { type = "string" });
74+
75+
//register all subclasses
76+
var derivedTypes = abstractType.Assembly
77+
.GetTypes()
78+
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
79+
80+
foreach (var item in derivedTypes)
81+
schemaRegistry.GetOrRegister(item);
82+
}
83+
}
84+
}

VirtoCommerce.Platform.Web/Swagger/SwaggerConfig.cs

+9
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Swashbuckle.Swagger;
1313
using VirtoCommerce.Platform.Core.Modularity;
1414
using VirtoCommerce.Platform.Core.Settings;
15+
using VirtoCommerce.Platform.Web.Controllers.Api;
1516

1617
namespace VirtoCommerce.Platform.Web.Swagger
1718
{
@@ -61,6 +62,9 @@ public static void RegisterRoutes(IUnityContainer container)
6162
.Replace(',', '-')
6263
);
6364

65+
c.DocumentFilter<PolymorphismDocumentFilter<BaseClass>>();
66+
c.SchemaFilter<PolymorphismSchemaFilter<BaseClass>>();
67+
6468
ApplyCommonSwaggerConfiguration(c, container, string.Empty, xmlCommentsFilePaths);
6569
})
6670
.EnableSwaggerUi(routePrefix + "docs/ui/{*assetPath}", c =>
@@ -97,6 +101,11 @@ private static void EnableSwagger(string moduleName, HttpConfiguration httpConfi
97101

98102
ApplyCommonSwaggerConfiguration(c, container, moduleName, xmlCommentsFilePaths);
99103
c.OperationFilter(() => new ModuleTagsFilter(moduleName));
104+
if (moduleName.Equals("VirtoCommerce.Platform", StringComparison.InvariantCultureIgnoreCase))
105+
{
106+
c.DocumentFilter<PolymorphismDocumentFilter<BaseClass>>();
107+
c.SchemaFilter<PolymorphismSchemaFilter<BaseClass>>();
108+
}
100109
});
101110
}
102111

VirtoCommerce.Platform.Web/VirtoCommerce.Platform.Web.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@
337337
<Compile Include="Controllers\AiHandleErrorAttribute.cs" />
338338
<Compile Include="Controllers\Api\ChangeLogController.cs" />
339339
<Compile Include="Controllers\Api\LicensingController.cs" />
340+
<Compile Include="Controllers\Api\PolyController.cs" />
340341
<Compile Include="Controllers\Api\ProfilesController.cs" />
341342
<Compile Include="Controllers\Api\AssetEntryController.cs" />
342343
<Compile Include="Controllers\ExternalLoginController.cs" />
@@ -455,6 +456,7 @@
455456
<Compile Include="Swagger\ModuleTagsFilter.cs" />
456457
<Compile Include="Swagger\AssignOAuth2SecurityOperationFilter.cs" />
457458
<Compile Include="Swagger\OptionalParametersFilter.cs" />
459+
<Compile Include="Swagger\PolymorphismSchemaFilter.cs" />
458460
<Compile Include="Swagger\SwaggerConfig.cs" />
459461
<Compile Include="Swagger\TagsFilter.cs" />
460462
<Compile Include="Swagger\UploadFileAttribute.cs" />

0 commit comments

Comments
 (0)