Swashbuckle.webapi: SchemaRegistry.GetOrRegister(typeof(Exception)) doesn't generate a useful schema

Created on 12 May 2016  路  2Comments  路  Source: domaindrivendev/Swashbuckle.WebApi

I've written a DefaultResponseOperationFilter to make Swashbuckle Swagger play nicely with AutoRest. Part of that is calling:

 var schema = schemaRegistry.GetOrRegister(typeof(Exception));

The generated schema is empty. If I create a class that mirrors exception

public class ApiError
{
    public string Message { get; set; }
    public string ExceptionMessage { get; set; }
    public string ExceptionType { get; set; }
    public string StackTrace { get; set; }
    public ApiError InnerException { get; set; }
    public string Source { get; set; }
    public string HelpLink { get; set; }
    public int HResult { get; set; }
    public IDictionary Data { get; set; }
}

Then

 var schema = schemaRegistry.GetOrRegister(typeof(ApiException));

Generates a useful schema object successfully.

Why does referring to a System object fail to generate a model?

Most helpful comment

In case it helps anyone, here's my DefaultResponseOperationFilter. This adds a "default" response Model to every returned Status Code you _don't_ specify. AutoRest proxies aren't very useful without this.

Usage: add [SwaggerDefaultResponse(typeof(ApiError))] to your Actions, Controller or Assembly: [assembly: SwaggerDefaultResponse(typeof(ApiError))]

public class ApiError
{
    public string Message { get; set; }
    public string ExceptionMessage { get; set; }
    public string ExceptionType { get; set; }
    public string StackTrace { get; set; }
    public ApiError InnerException { get; set; }
    public string Source { get; set; }
    public string HelpLink { get; set; }
    public int HResult { get; set; }
    public IDictionary Data { get; set; }
}


/// <summary>
/// Adds a Default model for unspecified response types. Usage: [SwaggerDefaultResponse(typeof(HttpResponseException))]
/// </summary>
/// <seealso cref="System.Attribute" />
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class SwaggerDefaultResponseAttribute : Attribute
{ 
    public SwaggerDefaultResponseAttribute(Type type)
    {
        Type = type;
    }

    public SwaggerDefaultResponseAttribute(Type type, string description)
    {
        Type = type;
        Description = description;
    }

    public Type Type { get; set; }

    public string Description { get; set; }
}


    private class DefaultResponseOperationFilter : IOperationFilter
    {
        public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
        {
            // Check action + controller attributes first
            var defaultResponseTypes = apiDescription.GetControllerAndActionAttributes<SwaggerDefaultResponseAttribute>().ToList();

            if (!defaultResponseTypes.Any())
            {
                // look for an Assembly attribute
                defaultResponseTypes = apiDescription.ActionDescriptor.ControllerDescriptor.ControllerType.Assembly.GetCustomAttributes<SwaggerDefaultResponseAttribute>().ToList();
            }

            // Start with Action attributes.
            defaultResponseTypes.Reverse();
            foreach (var defaultResponseType in defaultResponseTypes)
            {
                if (!schemaRegistry.Definitions.ContainsKey(defaultResponseType.Type.Name))
                {
                    schemaRegistry.GetOrRegister(defaultResponseType.Type);
                }

                var response = new Response()
                {
                    description = defaultResponseType.Description,
                    schema = new Schema()
                    {
                        @ref = "#/definitions/" + defaultResponseType.Type.Name
                    }
                };

                operation.responses.Add("default", response);
                break;
            }
        }
    }

And:

        GlobalConfiguration.Configuration 
            .EnableSwagger(c =>
                {
                    c.OperationFilter<DefaultResponseOperationFilter>();

You may want to add that after your other filters including the ones added by IncludeXmlComments.

All 2 comments

In case it helps anyone, here's my DefaultResponseOperationFilter. This adds a "default" response Model to every returned Status Code you _don't_ specify. AutoRest proxies aren't very useful without this.

Usage: add [SwaggerDefaultResponse(typeof(ApiError))] to your Actions, Controller or Assembly: [assembly: SwaggerDefaultResponse(typeof(ApiError))]

public class ApiError
{
    public string Message { get; set; }
    public string ExceptionMessage { get; set; }
    public string ExceptionType { get; set; }
    public string StackTrace { get; set; }
    public ApiError InnerException { get; set; }
    public string Source { get; set; }
    public string HelpLink { get; set; }
    public int HResult { get; set; }
    public IDictionary Data { get; set; }
}


/// <summary>
/// Adds a Default model for unspecified response types. Usage: [SwaggerDefaultResponse(typeof(HttpResponseException))]
/// </summary>
/// <seealso cref="System.Attribute" />
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class SwaggerDefaultResponseAttribute : Attribute
{ 
    public SwaggerDefaultResponseAttribute(Type type)
    {
        Type = type;
    }

    public SwaggerDefaultResponseAttribute(Type type, string description)
    {
        Type = type;
        Description = description;
    }

    public Type Type { get; set; }

    public string Description { get; set; }
}


    private class DefaultResponseOperationFilter : IOperationFilter
    {
        public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
        {
            // Check action + controller attributes first
            var defaultResponseTypes = apiDescription.GetControllerAndActionAttributes<SwaggerDefaultResponseAttribute>().ToList();

            if (!defaultResponseTypes.Any())
            {
                // look for an Assembly attribute
                defaultResponseTypes = apiDescription.ActionDescriptor.ControllerDescriptor.ControllerType.Assembly.GetCustomAttributes<SwaggerDefaultResponseAttribute>().ToList();
            }

            // Start with Action attributes.
            defaultResponseTypes.Reverse();
            foreach (var defaultResponseType in defaultResponseTypes)
            {
                if (!schemaRegistry.Definitions.ContainsKey(defaultResponseType.Type.Name))
                {
                    schemaRegistry.GetOrRegister(defaultResponseType.Type);
                }

                var response = new Response()
                {
                    description = defaultResponseType.Description,
                    schema = new Schema()
                    {
                        @ref = "#/definitions/" + defaultResponseType.Type.Name
                    }
                };

                operation.responses.Add("default", response);
                break;
            }
        }
    }

And:

        GlobalConfiguration.Configuration 
            .EnableSwagger(c =>
                {
                    c.OperationFilter<DefaultResponseOperationFilter>();

You may want to add that after your other filters including the ones added by IncludeXmlComments.

I'm proposing a separate Nuget package that extends Swashbuckle with AutoRest requirements. I think this is also something that could be encapsulated within that project. See #776 for more details.

Was this page helpful?
0 / 5 - 0 ratings