OpenApi import in Azure Api Management Gateway: Support for array of $ref in parameters

We are running a Dotnet Core 2.2 service using ServiceStack 5.7, and need to throttle it. So we want to put it behind a Azure Api Management Gateway (apim) - it runs in a Azure App Service.

We have enabled OpenApi feature using

self.Plugins.Add(new OpenApiFeature());

When we export our OpenApi definition we get the following:

"paths": {
 ...
        "/api/search": {
            "post": {
                "tags": [
                    "api"
                ],
                "operationId": "SearchRequestsearch_Post",
                "consumes": [
                    "application/x-www-form-urlencoded"
                ],
                "produces": [
                    "application/json"
                ],
                "parameters": [
                    {
                        "name": "Filters",
                        "in": "formData",
                        "type": "array",
                        "items": {
                            "$ref": "#/definitions/FilterDto"
                        },
                        "collectionFormat": "multi",
                        "required": false
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success",
                        "schema": {
                            "$ref": "#/definitions/SearchResponse"
                        }
                    }
                },
                "deprecated": false,
                "security": [
                    {
                        "Bearer": []
                    }
                ]
            },

            "parameters": [
                {
                    "$ref": "#/parameters/Accept"
                }
            ]
        }
    }
 ...
"definitions": {
  "FilterDto": {
            "title": "FilterDto",
            "properties": {
                "Field": {
                    "description": "The field to filter on",
                    "type": "string",
                    "enum": [
                        "None",
                        "DestinationName",
                        "DocumentId"
                    ]
                },
                "Values": {
                    type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "Type": {
                    "type": "string",
                    "enum": [
                        "Equals",
                        "NotEquals",
                        "RangeNumeric",
                        "RangeDate"
                    ]
                }
            },
            "description": "FilterDto",
            "type": "object"
        }
        ...
}

The problem is that it is not supported to have a parameter with an array of a type (defined in #/definitions/FilterDto). And it fails with: Parsing error(s): JSON is valid against no schemas from ‘oneOf’. Path ‘paths[’/api/search’].post.parameters[1]’, line 1, position 666. Parsing error(s): The input OpenAPI file is not valid for the OpenAPI specificate https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md (schema https://github.com/OAI/OpenAPI-Specification/blob/master/schemas/v2.0/schema.json).

In the Azure portal.

In c# (ServiceStack) we have defined the following:

public class SearchRequest : SearchRequestBase, IReturn<SearchResponse>
    {
        public SearchRequest()
        {
            Filters = new List<FilterDto>();
        }

        [ApiMember(Name = "Filters"]
        public List<FilterDto> Filters { get; set; }
    }

public class FilterDto
    {
        [ApiMember(Name = "Field"]
        [ApiAllowableValues("Field", typeof(FilterAndFacetField))] 
        public FilterAndFacetField Field { get; set; }

        [ApiMember(Name = "Values")]
        public List<string> Values { get; set; }

        [ApiMember(Name = "Type")]
        [ApiAllowableValues("Type", typeof(FilterType))]
        public FilterType Type { get; set; }

        public FilterDto()
        {
            Values = new List<string>();
        }
    }

Have anyone successfully managed to import a OpenApi using array of $ref in the parameters from ServiceStack into a Api Management?

(I posted this in https://stackoverflow.com/questions/60509749/servicestack-openapi-import-in-azure-api-management-gateway as well)

There’s not really a good solution for this, your Request DTO does include a collection of complex types (i.e. POCOs) but there’s no HTTP standard for sending complex types in form-urlencoded or query string key/value pairs, but ServiceStack does support it using the JSV Format.

To workaround avoiding sending complex type arrays you could change your Request DTO to send a list of strings instead, e.g:

public class SearchRequest : SearchRequestBase, IReturn<SearchResponse>
{
    public List<string> Filters { get; set; }
}

Then your clients could serialize your Filter DTO into a string using any text serialization format (e.g. like JSV) which would then need to be deserialized in the Service implementation.

If you want to tweak the Open API specification that’s generated by default you can use the Operation Filters.

1 Like

Thanks for your reply @mythz. Is it possible if the complex type is defined as type: body? We are currently exposing the endpoint as a GET and POST - but could avoid the GET endpoint.

Have you tried making it a POST? i.e.

[Route("/api/route", "POST")]
public class Search : IReturn<SearchResponse>, IPost
{
    public List<SearchFilter> Filters { get; set; }
}

Using POST only: It still failes since in: formdata and array of $ref.

My workaround is to remove the filterfield from the exported openapi definition - then it will not be defined in the OpenApi generated content in the Azure APIM Developer Portal - but it is possible to pass through the gateway to the underlying ServiceStack dotnet core service.

Not optimal: But better that not having any openapi definitions

I’m trying this Service (in latest ServiceStack):

[Route("/swagger/search", "POST")]
public class SwaggerSearch : IReturn<EmptyResponse>, IPost
{
    public List<SearchFilter> Filters { get; set; }
}

public class SearchFilter
{
    [ApiMember(Name = "Field")]
    public string Field { get; set; }

    [ApiMember(Name = "Values")]
    public List<string> Values { get; set; }

    [ApiMember(Name = "Type")]
    public string Type { get; set; }
}

public class MyServices : Service
{
    public object Any(SwaggerSearch request) => new EmptyResponse();
}

And it generating a POST JSON body request:

Good idea to remove the [ApiMember] annotation on the request DTO - however: when I try to import the generetated OpenApi definition in Azure APIM - I get error:
One or more fields contain incorrect values:

  • Parsing error(s): JSON is valid against no schemas from ‘oneOf’. Path ‘paths[’/api/search’].post.parameters[1]’, line 1, position 3455.
  • Parsing error(s): The input OpenAPI file is not valid for the OpenAPI specificate URL).

on

“/api/search”: {
“post”: {
“operationId”: “SearchRequest2search_Post”,
“consumes”: [
“application/json”
],
“produces”: [
“application/json”
],
“parameters”: [
{
“name”: “Query”,
“in”: “query”,
“type”: “string”,
“required”: false
},
{
“name”: “Filters”,
“in”: “query”,
“description”: “filters”,
“type”: “array”,
“items”: {
“$ref”: “#/definitions/FilterDto”
},
“collectionFormat”: “multi”,
“required”: false
},
{
“name”: “body”,
“in”: “body”,
“schema”: {
“$ref”: “#/definitions/SearchRequest2”
}
}
],
“responses”: {
“200”: {
“description”: “Success”,
“schema”: {
“$ref”: “#/definitions/SearchResponse”
}
}
},
“deprecated”: false
},
“parameters”: [
{
“$ref”: “#/parameters/Accept”
}
]
}

So it seems the problem is how Azure imports a OpenApi definition - so we will just manually remove the definition of the Filters in the OpenApi json - and let it live “secretly” in the POST body :frowning:

Anyways: Thanks for your insights & help @mythz !

1 Like