0

I am implementing IApiDescriptionGroupCollectionProvider to get the API description.

I have implemented in this way


private readonly IApiDescriptionGroupCollectionProvider _apiExplorer;

public RouteController(
    IApiDescriptionGroupCollectionProvider apiExplorer)
{
    _apiExplorer = apiExplorer;
}

[HttpGet("all")]
public IActionResult GetRoute()
{
    var paths = GetApiDescriptionsFor("v1");

    return Ok();
}

I want to bind all the details of the controller to an ApiRouteDocument custom model with a description of the action too. But the interface I have implemented doesn't give a summary of the action, and the controller. Is there any built-in interface to extract the summary from the actions?

I wanted to avoid the Reflection.


[ApiController]
[Route("api/contact")]
[ApiExplorerSettings(GroupName = "Contact")]
public class ContactController : ControllerBase
{

    /// <summary>  
    /// Get by Name Contact  
    /// </summary> 
    [HttpGet("getbyname/{name}")]
    public async Task<IActionResult> GetByName(string name)
    {
        return Ok();
    }

}
public class ApiRouteDocument
{
    //controllername tag
    public string ControllerName { get; set; }
    public string ControllerDescription { get; set; }
    public IList<RoutePath> Paths;
}
public class RoutePath
{
    //get
    public string Method { get; set; }

    //operationid
    public string Name { get; set; }

    //summary
    public string Description { get; set; }

    //path
    public string Path { get; set; }

}
Rasik
  • 1,961
  • 3
  • 35
  • 72
  • See: https://stackoverflow.com/questions/28435734/how-to-get-a-list-of-all-routes-in-asp-net-core/66086633#66086633 – abdusco Jul 28 '21 at 09:38
  • I didn't see the summary and descriptions as I have decorated in the action and controller name. – Rasik Jul 28 '21 at 09:40
  • You can get it from `ControllerTypeInfo` – abdusco Jul 28 '21 at 09:41
  • Oh you mean the comments? That's a bit difficult. They're not included in the assembly. You have no way of getting it at runtime. You can export the docs then read the XML, though. – abdusco Jul 28 '21 at 09:41
  • not the comments but the description about action and controller for example if it is a create actions, I want to add `Create a new contact.`. Or it could be like decorating action and controller with attributes. – Rasik Jul 28 '21 at 09:43
  • You can set it with an attribute `[Summary("Create a new contact")]`, which you can read with reflection – abdusco Jul 28 '21 at 09:43
  • Yeah something like that but without the reflection? – Rasik Jul 28 '21 at 09:44
  • Reflection isn't a bad thing as long as you're not doing it too often / on a hot path. Microsoft's own `ApiExplorer` abstractions work via reflection too. The whole framework is built upon the dynamism that's only possible with reflection – abdusco Jul 28 '21 at 09:46
  • The link to the solution you have posted is a nice example, how to add the attribute description on that? any example you can provide? – Rasik Jul 28 '21 at 09:48
  • Sure, give me a minute. – abdusco Jul 28 '21 at 09:49

1 Answers1

0

You have to run this code after the middleware pipeline has been established (and app.UseEndpoints() has been executed), so it should run inside a controller, or an endpoint, for example.

Because documentation comments aren't included in the assembly, you need to use attributes to annotate your classes & actions. You have the option to use the ones provided by Microsoft, such as [DisplayName], [Description], etc. under System.ComponentModel.Primitives namespace.

Or you can create your own attribute:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
internal class SummaryAttribute : Attribute
{
    public string Summary { get; }

    public SummaryAttribute(string summary)
    {
        Summary = summary;
    }
}

Then inject IEnumerable<EndpointDataSource> in a controller, which will give you the list of discovered endpoints.

You can get reflected runtime type info from ControllerActionDescriptor, then extract the attributes you've added to controllers & actions.

[Description("Provides info")]
[Route("info")]
public class ThingsController : ControllerBase
{
    private IEnumerable<EndpointDataSource> _endpointDataSources;

    public ThingsController(IEnumerable<EndpointDataSource> endpointDataSources)
    {
        _endpointDataSources = endpointDataSources;
    }

    [Description("Returns a list of endpoints")]
    [HttpGet("endpoints")]
    public ActionResult Endpoints()
    {
        var actions = _endpointDataSources.SelectMany(it => it.Endpoints)
            .OfType<RouteEndpoint>()
            .Where(
                it => it.Metadata
                    .OfType<ControllerActionDescriptor>()
                    .Any()
            )
            .Select(
                e => {
                    var actionDescriptor = e.Metadata
                        .OfType<ControllerActionDescriptor>()
                        .First();

                    var isControllerIgnored = actionDescriptor.ControllerTypeInfo.GetCustomAttribute<ApiExplorerSettingsAttribute>()?.IgnoreApi ?? false;
                    var isActionIgnored = actionDescriptor.MethodInfo                       .GetCustomAttribute<ApiExplorerSettingsAttribute>()?.IgnoreApi ?? false;

                    return new
                    {
                        ControllerName = actionDescriptor.ControllerName,
                        ControllerType = actionDescriptor.ControllerTypeInfo.FullName,
                        ActionDescription = actionDescriptor.MethodInfo.GetCustomAttribute<DescriptionAttribute>()?.Description,
                        ControllerDescription = actionDescriptor.ControllerTypeInfo
                            .GetCustomAttribute<DescriptionAttribute>()
                            ?.Description,
                        Method = actionDescriptor.EndpointMetadata.OfType<HttpMethodMetadata>()
                            .FirstOrDefault()
                            ?.HttpMethods?[0],
                        Path = $"/{e.RoutePattern!.RawText!.TrimStart('/')}",
                    };
                }
            )
            .ToList();

        return Ok(actions);
    }
}

when you visit /info/endpoints, you'll get a list of endpoints:

[
  {
    "controllerName": "Things",
    "controllerType": "ApiPlayground.ThingsController",
    "actionDescription": "Returns a list of endpoints",
    "controllerDescription": "Provides info",
    "method": "GET",
    "path": "/info/endpoints"
  }
]
abdusco
  • 9,700
  • 2
  • 27
  • 44