5

I have a .Net Core 2.2 OData API, for which I'm tying to implement Swagger documentation.

I've been following this example: https://github.com/Microsoft/aspnet-api-versioning/tree/master/samples/aspnetcore/SwaggerODataSample

I've got it working up to a point. Now I'm facing an issue with my models referencing each other in a circular manner, let me explain:

Note: I used EFCore Code first approach for handling my DB.

I have these models (as an example): Project, ProjectLocation, ProjectRegion. Lets call them A, B & C to keep things short.

A has references to B & C like so:

public virtual ICollection<X> X{ get; set; }

And both B & C reference A directly like so:

public A A{ get; set; }

This is all pretty standard relational DB model stuff, but it seems SwaggerUI can't handle this.

The error I'm getting is as follows:

Failed to load API definition. Errors: Fetch error: Internal Server Error /swagger/v1/swagger.json

An unhandled exception occurred while processing the request. TypeLoadException: Could not load type 'NCCRD.Services.DataV2.Database.Models.ProjectLocation' from assembly 'Tc2fc56a7babe40419a678a075439246c.DynamicModels, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.

System.Signature.GetSignature(Void* pCorSig, int cCorSig, RuntimeFieldHandleInternal fieldHandle, IRuntimeMethodInfo methodHandle, RuntimeType declaringType)

A stack-trace is also available if needed.

I've read these two posts and all of the references links as far as I could go, but did not find a solution that works:

https://github.com/swagger-api/swagger-codegen/issues/741

https://github.com/swagger-api/swagger-codegen/issues/728

Once solution that seemed good was this: https://systemout.net/2017/04/07/swagger-asp-net-core-fixing-circular-self-references/, but this too had no effect. (I contacted to author for help too)

The moment I remove the reference to A from either B or C, then SwaggerUI loads up fine.

If anyone could shed some light on this I'd really appreciate it. Thanks in advance to any help offered.

  • Do not return or accept entities on the WebApi endpoints. – Tseng Dec 10 '18 at 12:44
  • I already do, and have to. This is an existing API used by clients, I'm just trying to add Swagger to the mix. It used to be so easy with Swashbuckle, but because I'm now using Odata and .Net Core together, Swashbuckle is no longer an option, afaik. – André Engelbrecht Dec 11 '18 at 07:14
  • This _might_ be a bug in the OData API Explorer. Most, if not all, Swagger generators perform their work using Reflection whereas OData uses the EDM to determine what a model looks like over the wire. As a result, the Swagger documentation generated from the corresponding CLR type might not be same as the EDM type, which would be _wrong_. The OData API Explorer _substitutes_ the CLR type with a surrogate that matches the EDM type when required. The cause of this issue is being investigated. In the meantime, the workaround _should_ be to use response types that match the EDM exactly. – Chris Martinez Jan 03 '19 at 18:36

3 Answers3

2

After two days of intense research and experimentation, I was unable to find any solutions that work out of the box. Swashbuckle used to be my goto for this, but because I'm using Odata in .NetCore that's not an option. Swashbuckle offers options for OData and .NetCore separately, but unfortunately not when used together.

I did however find a solution, that with some effort I was able to convert into something that works for my needs. The following post formed the starting point of my eventual solution (which is still a work-in-progress BTW): https://stackoverflow.com/a/51774147/4261155

My version of "CustomDocumentFilter" is up on GitHub: https://github.com/SAEONData/NCCRD/blob/swagger_odata_netcore/NCCRD_API/NCCRD.Services.DataV2/Database/Contexts/CustomDocumentFilter.cs

As mentioned, this is still a work in progress so it'll be changing over the next few days, but together with the original version, I'm hoping this might help someone else in this same situation. Also keep in mind, this class was adapted to suit my specific needs, and is not intended as an "out of the box" solution for anyone else.

0

This appears to have been a bug related to the Model Substitution feature. The fix has been published in version 3.1.1 of the Microsoft.AspNetCore.OData.Versioning.ApiExplorer package. If this problem persists, please report a new issue in the API Versioning repo.

Chris Martinez
  • 3,185
  • 12
  • 28
0

I serialized the response using linq.

This are my classes:

public sealed class ProductDto
{
    public int ProductId { get; set; }
    public string Description { get; set; }
    public string Type { get; set; }

    public List<PlanDto> Plans { get; set; }
}

public class PlanDto
{
    public int PlanId { get; set; }
    public int ProductId { get; set; }
    public string Description { get; set; }
    public bool Active { get; set; }
    public DateTime Created { get; set; }

    public virtual ProductDto Product { get; set; }
}

Then I removed from the response the circular reference that in this case is Product = null.

My solution when I'm creating ProductDto Api response:

productEntity.ForEach(i => i.Plans.ForEach(j => j.Product = null));
return productEntity;

When swagger fixes this issue this serialization won't be necessary.

Javier Contreras
  • 871
  • 7
  • 16
  • I don't understand this solution. Where do you add productEntity.ForEach(i => i.Plans.ForEach(j => j.Product = null));? – John Bowyer May 19 '20 at 02:12
  • If we see the classes, a Plan has an associated Product. And, a Product has a list of Plans. So, when it's time to return the data to the client. I say: I will return this Product "productEntity" and all its Plans. But, when it's time to return the associated Product to each of the Plans I set them to null. "Plans.ForEach(j => j.Product = null)". That way: Product has list of Plans, and each Plan doesn't have a Product. Try it, and tell me how it goes. – Javier Contreras May 21 '20 at 02:39