openiddict-ui icon indicating copy to clipboard operation
openiddict-ui copied to clipboard

Update ApiControllerBase.cs

Open Soruk opened this issue 3 years ago • 13 comments

Add Attribute "ApiController" in order to generate Swagger documentation

[ApiController]

Soruk avatar Dec 08 '21 16:12 Soruk

I think it might be usefull also to add grouping of APIby project

 [ApiExplorerSettings(GroupName = "Name-Group")]

Soruk avatar Dec 08 '21 16:12 Soruk

Hi Soruk

Is having the ApiController-attribute a requirement in order to generate Swagger documentation or will it enrich the generated documentation with additional information? Honestly I do not know the details here. I thought it should be already be possible with what is there at the moment.

Regarding the ApiExplorerSettings-attribute, this is already set directly on the specific endpoint-Controllers (see the below picture).

image

And yes, they are hardcoded strings meaning an integrator can not change it to his will. Any suggestions on this?

thomasduft avatar Dec 09 '21 19:12 thomasduft

Hi @thomasduft, the ApiController-attribute is mandatory to generate the Swagger documentation. When I reference your nugets for the use with Swagger (btw the included XML documentation is not complete) I do not have any endpoints exposed by your API. I had the same issue with my main executing assemble, and when I added the given attribute, the Swagger documentation was generated for given endpoint.

Soruk avatar Dec 09 '21 20:12 Soruk

Hi @Soruk

This is weird?! When I use and reference my latest published nuget openiddict-ui packages and setup the Swagger related stuff within the Startup/Program.cs file then I see the endpoints and I can also browse them.

image

I usually develop within devcontainers on a linux based PC with VSCode. So to me the ApiController-attribute doesn't seem to be mandatory.

thomasduft avatar Dec 09 '21 20:12 thomasduft

Hi @thomasduft

Yes it is weird. Can you share your Startup/Program.cs, please ?

Soruk avatar Dec 09 '21 20:12 Soruk

Et voilà...


using System.Reflection;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerUI;

using tomware.Suite.Host.Web;
using tomware.Suite.Persistence.EF;

var configuration = new ConfigurationBuilder()
  .AddJsonFile("appsettings.json")
  .Build();

var builder = WebApplication.CreateBuilder(args);

// Add services to the container => ConfigureServices
builder.Services.Configure<StorageContextOptions>(options =>
{
  options.DbContextOptionsBuilder = builder =>
     builder.UseSqlite(configuration.GetConnectionString("DefaultConnection"),
       sql => sql.MigrationsAssembly(typeof(Program)
                 .GetTypeInfo()
                 .Assembly
                 .GetName()
                 .Name));
});

builder.Services.AddModules();

if (builder.Environment.IsDevelopment())
{
  builder.Services.AddSwaggerGen(c =>
  {
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "App", Version = "v1" });
    c.DocInclusionPredicate((name, api) => true);
    c.TagActionsBy(api =>
    {
      if (api.GroupName != null)
      {
        return new[] { api.GroupName };
      }

      var controllerActionDescriptor = api.ActionDescriptor as ControllerActionDescriptor;
      if (controllerActionDescriptor != null)
      {
        return new[] { controllerActionDescriptor.ControllerName };
      }

      throw new InvalidOperationException("Unable to determine tag for endpoint.");
    });
    c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
  });
}

// Configure
var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
  app.UseCors(builder =>
  {
    builder.WithOrigins("http://localhost:4200");
    builder.AllowAnyHeader();
    builder.AllowAnyMethod();
    builder.AllowCredentials();
  });

  app.UseDeveloperExceptionPage();
  app.UseSwagger();
  app.UseSwaggerUI(c =>
  {
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "App v1");
    c.DocExpansion(DocExpansion.None);
  });
}

app.UseWebAppWithDefaults();

app.Run();

It is part of private stuff and already on .NET 6. But you should see the swagger configuration section....

thomasduft avatar Dec 09 '21 21:12 thomasduft

Thanks.

I will check it tomorrow

Soruk avatar Dec 09 '21 21:12 Soruk

Ok, I have just tested your code, and still I get only the main / executing assembly api endpoints. I do not know why I cannot to get to show the api exposed by referenced assemblies.

Soruk avatar Dec 10 '21 07:12 Soruk

Same as well if you clone this repo and start the sample server? I can run it on a Linux based PC and a Windows based PC and always see the Swagger generated docs!

thomasduft avatar Dec 10 '21 07:12 thomasduft

I have just re-downloaded the code from main branch and I can see the other assemblies documentation. When I was using the code downloaded just after release of 1.3 version, the swagger didn't work. But it works, strange... I will be investigating and keep you posted.

Soruk avatar Dec 10 '21 08:12 Soruk

Hi @thomasduft, I think that I found the source of the problem. When I use

services.AddVersionedApiExplorer();

for versioning API, and the Controller does not have the given attribute [ApiController], the Swagger documentation is not generated.

I will check this solution from StackOverflow

Soruk avatar Dec 10 '21 15:12 Soruk

I will check this solution from StackOverflow

And it seems that UseApiBehavior = false is the solution.

Soruk avatar Dec 10 '21 15:12 Soruk

After more investigation, when I want to filer by version the methods in the following Swagger configration:

   options.DocInclusionPredicate((version, desc) =>
                {
                    if (string.IsNullOrWhiteSpace(desc.GroupName))
                        return false;

                    if (!desc.TryGetMethodInfo(out MethodInfo methodInfo))
                        return false;

                    var versions = methodInfo.DeclaringType
                    .GetCustomAttributes(true)
                    .OfType<ApiVersionAttribute>()
                    .SelectMany(attr => attr.Versions);

                    var maps = methodInfo
                    .GetCustomAttributes(true)
                    .OfType<MapToApiVersionAttribute>()
                    .SelectMany(attr => attr.Versions)
                    .ToArray();

                    return versions.Any(v => $"v{v}" == version)
                    && (!maps.Any() || maps.Any(v => $"v{v}" == version));
                });

without the [ApiVersion], the filter return false.

A little tweak, fix it:

   options.DocInclusionPredicate((version, desc) =>
                {
                    if (string.IsNullOrWhiteSpace(desc.GroupName))
                        return false;

                    if (!desc.TryGetMethodInfo(out MethodInfo methodInfo))
                        return false;

                    var versions = methodInfo.DeclaringType
                    .GetCustomAttributes(true)
                    .OfType<ApiVersionAttribute>()
                    .SelectMany(attr => attr.Versions);

                    var maps = methodInfo
                    .GetCustomAttributes(true)
                    .OfType<MapToApiVersionAttribute>()
                    .SelectMany(attr => attr.Versions)
                    .ToArray();

                       return !versions.Any()  // without version attribute
                        ||
                        (   versions.Any(v => $"v{v}" == version)
                        && (!maps.Any() || maps.Any(v => $"v{v}" == version))
                    );
                });

Soruk avatar Dec 10 '21 16:12 Soruk