Skip to content

Type inheritance support in Swagger API

Thanks to PR #2063, Virtocommerce platform is able to expose derived types in Swagger API description.

Problem

Consider the following code:

// Models definition in ...Core assembly

public abstract class BaseObject
{
    // ...
}

public class DerivedObject : BaseObject
{
    // ...
}

public class AnotherDerivedObject : BaseObject
{
    // ...
}

// Controller method that returns these models
public ActionResult<IList<BaseObject>> GetObjects()
{
    var result = new[] 
    {
        new DerivedObject(),
        new AnotherDerivedObject()
    };

    return Ok(result);
}

In this example, only the BaseObject will be exposed in Swagger. DerivedObject and AnotherDerivedObject will be missing from JSON with API definition, and the only way to expose them is to add some other API method that will explicitly accept or return any of these derived types.

On the other hand, we don't want to enable polymorphism globally, as this might break existing API (e.g. any action from vc-module-customer that works with a Member class).

Solution

Since the creation of VC platform v3, polymorphism support in Swashbuckle improved significantly. Now we actually can use it in action, and the resulting document will be suitable for AutoRest (e.g. to generate API clients for storefront). An example mentioned above can be reworked like this to expose derived models:

using Swashbuckle.AspNetCore.Annotations;

[SwaggerSubType(typeof(DerivedObject)]
[SwaggerSubType(typeof(AnotherDerivedObject)]
public abstract class BaseObject
{
    // ...
}

public class DerivedObject : BaseObject
{
    // ...
}

public class AnotherDerivedObject : BaseObject
{
    // ...
}

This will expose BaseObject, DerivedObject and AnotherDerivedObject in Swagger API description (despite the fact that GetObjects() method still has only the base type in its signature), and it won't break other API.

More info on used attributes and type descriminator annotations could be found here.

Limitations

This approach works and does not break any existing clients generated by AutoRest. However, it has some limitations: 1. If BaseObject has any abstract or virtual properties that are overridden in derived types, Swashbuckle will include these properties both to BaseObject and to derived types. However, AutoRest does not understand that and produces an error like FATAL: System.InvalidOperationException: Found incompatible property types , for property 'someVirtualProperty' in schema inheritance chain. A solution for this would be to avoid using abstract and virtual properties for such types. 2. This approach only works if all of derived types are located in the same module - it won't allow to extend this list from other modules. To overcome this, we might need to make a custom sub-type selector - its code would be based on the existing implementation in Swashbuckle, but use AbstractTypeFactory instead of custom attributes to find descendant types. However, this might require extending the TypeInfo, so that we could explicitly specify what types can be exposed in Swagger API.


Last update: October 9, 2020