Custom ApiResponseAttribute, missing response codes

We have a strange issue with reflexion in ServiceStack.Api.Swagger (using .NET framework 4.6, nugget package 4.5.4).

Before commit 92646b3aa0e1e9d66911163b3057c39db83eb083, we were implementing IApiResponseDescription for custom api response attributes.
Now we have to override ApiResponseAttribute and there is a strange behaviour: only first custom attribute is retrieved, so only one status code is displayed in Swagger.

Step to reproduce:
Create new custom response attribute

/// <summary>
/// Custom API response attribute
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class CustomApiResponseAttribute : ApiResponseAttribute
{
    private static int errCode = 402;

    public CustomApiResponseAttribute()
        : base(++errCode, Guid.NewGuid().ToString())
    {}
}

Sample request:

/// <summary>
/// Sample request
/// </summary>
[ApiResponse(400, "Code 1")]
[CustomApiResponse()]
[ApiResponse(402, "Code 2")]
[CustomApiResponse()]
[CustomApiResponse()]
[ApiResponse(401, "Code 3")]
[Route("/sample", Verbs = "POST", Summary = "Sample request")]
public sealed class SampleRequest : IReturnVoid
{}

Issue is located here:

ServiceStack.Api.Swagger
=> SwaggerApi FormatMethodDescription(RestPath restPath, Dictionary<string, SwaggerModel> models)
=> ErrorResponses = GetMethodResponseCodes(requestType)

Debugging method content:

var requestType = typeof(SampleRequest);

requestType
    .AllAttributes<ApiResponseAttribute>()
    .Select(x => new ErrorResponseStatus
    {
        StatusCode = x.StatusCode,
        Reason = x.Description
    })
    .OrderBy(x => x.StatusCode)
    .ToList()
    .ForEach(x => Debug.WriteLine("{0}:{1}", x.StatusCode, x.Reason));

Result:
400:Code 1
401:Code 3
402:Code 2
403:5009a341-4c64-4c0d-896a-09c144d52d54

Replacing .AllAttributes() with method content

requestType
    .GetCustomAttributes(typeof(ApiResponseAttribute), true)
    .OfType<ApiResponseAttribute>()
    //.Union(requestType.GetRuntimeAttributes<ApiResponseAttribute>())
    .ToArray()
    .Select(x => new ErrorResponseStatus
    {
        StatusCode = x.StatusCode,
        Reason = x.Description
    })
    .OrderBy(x => x.StatusCode)
    .ToList()
    .ForEach(x => Debug.WriteLine("{0}:{1}", x.StatusCode, x.Reason));

Result:
400:Code 1
401:Code 3
402:Code 2
406:d4bbdc55-d743-4b58-ad95-b1a43d0e2148
407:0e8d45ab-0727-49e8-9736-fb8fa75aceaa
408:ab2d8882-82bb-4042-91bb-f277d910623a

Do you have any idea?

The issue is because Attributes being a compile-time construct have an identity issue when using collection methods which required overriding TypeId with a unique value in order for them to be considered as different instances. To get around this we originally had to add a AttributeBase class that overrided TypeId, e.g:

public class AttributeBase : Attribute
{

#if !(NETFX_CORE || WP || SL5 || PCL)
    public AttributeBase()
    {
        this.typeId = Guid.NewGuid();
    }

    /// <summary>
    /// Required when using a TypeDescriptor to make it unique
    /// </summary>
    protected readonly Guid typeId;
    public override object TypeId
    {
        get { return this.typeId; }
    }
#endif
    
}

Unfortunately we had to remove this hack once ServiceStack.Interfaces became a PCL library since TypeId isn’t available in PCL APIs. But you can get around this by implementing this in your Custom Attribute, e.g:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class CustomApiResponseAttribute : ApiResponseAttribute
{
    private static int errCode = 402;

    public CustomApiResponseAttribute()
        : base(++errCode, Guid.NewGuid().ToString())
    {
        this.typeId = Guid.NewGuid();
    }

    protected readonly Guid typeId;
    public override object TypeId => this.typeId;
}

Although after spending a while experimenting it looks like it’s enough to just have the AttributeBase class to have a unique Guid property in order for the .NET Runtime to treat them as different instances, e.g:

public class AttributeBase : Attribute
{
    public AttributeBase()
    {
        this.typeId = Guid.NewGuid();
    }

    protected readonly Guid typeId;
}

Which I’ve added in this commit that’s available from v4.5.5 that’s now on MyGet.

Thank you for your investigation, didn’t know about this identity issue.

Having a unique Guid property to solve this is very weird, but glad to see it is working.