Open Api (swagger) extensions to make your WebApi generate an AutoRest rich and compliant swagger documents.
Перейти к файлу
Amir Sasson d1e4ed5796
Update BasicExampleIntegrationTests.cs
2022-01-06 16:36:52 +02:00
.github/workflows Update publish-package.yml 2021-12-28 12:46:15 +02:00
.tours Remove ADO Dependency (#3) 2021-12-28 12:32:51 +02:00
.vscode Add Tests + Fix Reaedme (#5) 2022-01-06 12:23:57 +02:00
samples Add Tests + Fix Reaedme (#5) 2022-01-06 12:23:57 +02:00
src/OpenApiExtensions Add Tests + Fix Reaedme (#5) 2022-01-06 12:23:57 +02:00
tests/OpenApiExtensions.Test Update BasicExampleIntegrationTests.cs 2022-01-06 16:36:52 +02:00
.dockerignore first commit 2021-12-01 21:22:31 +02:00
.editorconfig Add Tests + Fix Reaedme (#5) 2022-01-06 12:23:57 +02:00
.gitignore Add Tests + Fix Reaedme (#5) 2022-01-06 12:23:57 +02:00
CODE_OF_CONDUCT.md CODE_OF_CONDUCT.md committed 2021-12-01 05:26:48 -08:00
Directory.Build.props Add Tests + Fix Reaedme (#5) 2022-01-06 12:23:57 +02:00
LICENSE LICENSE committed 2021-12-01 05:26:48 -08:00
OpenApiExtensions.sln Add Tests + Fix Reaedme (#5) 2022-01-06 12:23:57 +02:00
README.md Add Tests + Fix Reaedme (#5) 2022-01-06 12:23:57 +02:00
SECURITY.md SECURITY.md committed 2021-12-01 05:26:49 -08:00
SUPPORT.md SUPPORT.md committed 2021-12-01 05:26:50 -08:00
common.ruleset Add Tests + Fix Reaedme (#5) 2022-01-06 12:23:57 +02:00
global.json Add Tests + Fix Reaedme (#5) 2022-01-06 12:23:57 +02:00
stylecop.json Add Tests + Fix Reaedme (#5) 2022-01-06 12:23:57 +02:00

README.md

OpenApiExtensions

OpenApiExtensions Nuget Package is providing out of the box, generating rich Swagger files out of your Aspnet Api code. The Generated Swagger file is enriched by decorating your code with Attributes and comments, and therefore is able to generated a fully autorest compliant Swagger file (and request/response Example files). the generation process is relying on Swashbuckle

Getting Started

Target Project Prerequisites

  • "Aspnet core 3.1 or higher" for your WebApi

How To use

add this code on your startup ConfigureServices() method see usage

            var config = new SwaggerConfig
            {
                PolymorphicSchemaModels = new List<Type> { typeof(WeatherForecast) },
                ModelEnumsAsString = true,
                ReusableParameters = new Dictionary<string, Microsoft.OpenApi.Models.OpenApiParameter>()
                {
                    { "SomeReusableParam", ReusableParameters.GetSomeReusableParameter() },                    
                    { "ApiVersionParameter", ReusableParameters.GetApiVersionParameter() }                    
                },               
                EnableSwaggerSecurityTokenSupport = true                
            };

            services.AddAutorestCompliantSwagger(config);

and on Configure() method

add this at the beginning of the pipeline:

  app.UseAutorestCompliantSwagger(_swaggerConfig);

having these minimal Prerequisites are enough to generate a swagger file.
when running your ArmApi on "localmode" you can now browse to
http://127.0.0.1:<<YOUR PORT>>/swagger/<<YOUR API VERSION>>/swagger.json
and get the file on your browser
or just http://127.0.0.1:<<YOUR PORT>>/swagger to get the swagger ui.

Enrichment

Code Enrichment

to be able to be fully compliant with Autorest Swagger standards, your code should be decorated with Comments and attributes.

Enrich by Comments:

In order to enrich your swagger file from comments, you need to set msbuild Xml documentation file.
upon build, the complier will read all your triple slash comments and save them into a XML file, which the swagger generation process will automatically read to enrich your Classes, Operations. see example

example:

    /// <summary>
    /// Some Descriptive Summary Description (Reflected on your XML Comment -> and Swashbuckle read the        XmlDocumentation file and enrich the schemas , see https://github.com/domaindrivendev/Swashbuckle.AspNetCore#include-descriptions-from-xml-comments)
    /// </summary>
    abstract public class WeatherForecast
    {

    }

Enrich by Attributes:

As the Swagger Generation is based on Swashbuckle, you may use any of the swashbuckle annotations.
in addition to that you may refer to full list of provided annotation (attributes) by this repo

you might also want to run the code tour of this repo by opening this folder with VSCode, and start the code tour.

Enriching your Controllers

ArmApi

    [ApiVersion("2022-01-01-preview")]
    [Route("MyArmApiController")]
    [ApiController]
    [SwaggerApiVersionRange(fromVersion: "2022-01-01-preview")] // this tell the swagger generating to include this controller in any "2022-01-01-preview" and above (not mandatory)
    [SwaggerTag("ReadSomeResource")] // this would set up the tag for your controller
    public class MyArmApiController : ControllerBase
    {
        ....
    }

Internal Api

to avoid Internal Apis to be reflected in External Swagger docs use [HideInDocs] attribute to decorate either your Controllers or Actions

    [ApiVersion("2022-01-01-preview")]
    [Route("InternalApi")]
    [HideInDocs]
    public class MyInternalApiController : ControllerBase
    {
        ....
    }

Enriching your Actions

in this sample we provide the [SwaggerOperation] attribute that enrich the swagger model, and Request+Response + Example Providers.


        [SwaggerOperation(
           Summary = "Get MyArmResources records",
           Description = "Fetches My Arm Resources",
           OperationId = "Get",
           Tags = new[] { "MyArmResources" })]
        [SwaggerResponse(200, "MyArmResources records fetched", typeof(SomeResourceList))]
        [ResponseExample(200, typeof(GetAllMyArmResourcesResponseExample))]
        [RequestExample(typeof(GetAllMyArmResourcesAsyncRequestExample))]
        [Pageable]        
        [HttpGet]
        public async Task<SomeResourceList> GetListOfResources(
            Guid subscriptionId,
            string resourceGroupName,
            string workspaceName,            
            [SwaggerHideParameter][FromHeader(Name = ArmHeaderConstants.InternalWorkspaceIdHeaderName)] Guid workspaceId,
            [LogIgnore] CancellationToken cancellationToken)
        {
          ...
        }
        /// <param name="resourceId">Some Documentation to be shown on swagger</param>
        [SwaggerOperation(
           Summary = "Get MyArmResource record",
           Description = "Fetches My Arm Resource",
           OperationId = "Get",
           Tags = new[] { "MyArmResource" })]
        [SwaggerResponse(200, "MyArmResource fetched", typeof(MyArmResource))]
        [ResponseExample(200, typeof(GetMyArmResourceResponseExample))]
        [RequestExample(typeof(GetAllSomeResourceAsyncRequestExample))]          
        [HttpGet("{resourceId}")]
        public async Task<MyArmResource> GetSingleResource(
            Guid subscriptionId,
            string resourceGroupName,
            string workspaceName,            
            string resourceId,
            [SwaggerHideParameter][FromHeader(Name = ArmHeaderConstants.InternalWorkspaceIdHeaderName)] Guid workspaceId,
            [LogIgnore] CancellationToken cancellationToken)
        {
          ...
        }        

Examples

by specifying above your action these attributes : [ResponseExample] and [RequestExample], examples will be auto generated for you. see sample

Enriching your WebModels

See [samples](./samples/] projects for more info
for Virtual inheritance in your swagger file refer this sample
(virtual inheritance is when you have polymorphism on your APIs, and the classes in your swagger doesnt exist on your code)

Renaming your WebModels

sometimes you want to call your models differently on the swagger doc than your code.
you have a few options to deal with this.

  1. Renaming your class in code to match swagger, as WebModels class renaming is not a breaking change. you can do so if you can.

  2. If you are not able to control your WebModels, in such cases when returning a generic Object, MyObjectGenericsWrapper<T>, you still can :

    1. Not using MyObjectGenericsWrapper<T> and use your own class.
    2. inherit from MyObjectGenericsWrapper<T> and return this inheritance from your controllers actions.
  3. Use provided Swagger Attributes to control the naming:
    here are some examples:

    • Basic constant renaming:

       // in this example MyBoringClassResource would be named MySwaggerAwesomeName
        [SwaggerSchemaNameStrategy("MySwaggerAwesomeName")]
        public class MyBoringClassResource
        {
        }
      
      
    • Naming a generic wrapper:

        [SwaggerSchemaNameStrategy(NamingStrategy.ApplyToParentWrapper, "MySwaggerAwesomeNameModel")]
        public class MyResourceProperties
        {
        }
    
    
        public class MyObjectGenericsWrapper<T> // this class when used as MyObjectGenericsWrapper<MyResourceProperties> will be named in swagger as MySwaggerAwesomeNameModel
        {
        }
    
    // in this example a Generic Wrapper that holds ConcreteModel would be named according to the logic of SwaggerAwesomeClassNameProvider
    [SwaggerSchemaNameStrategy(NamingStrategy.ApplyToParentWrapper, typeof(SwaggerAwesomeClassNameProvider) )]
    public class ConcreteModel
    {               
    }
    
    public class SwaggerAwesomeClassNameProvider : ICustomSchemaNameProvider
    {
        public string GetCustomName(Type type)
        {
            if (typeof(MyObjectGenericsWrapper<ConcreteModel>) == type)
            {
                return "ConcreteClass";
            }
    
            if (typeof(MyObjectsCollectionGenericsWrapper<ConcreteModel>) == type)
            {
                return "ConcreteClassesList";
            }
    
            throw new InvalidOperationException("Add more here ..");
        }
    }      
    

Open Api Extension also provide out of the box Custom name providers such as ArmResourceWrapperNameProvider

Models hierarchy

You might need to create a model hierarch in swagger that doesn't reflect your code. to do so, you can use the CustomSwaggerSchemaInheritance to create an object Hierarchy that doesn't reflect your code.
see this example:

    /// <summary>
    /// The Some Resource Properties model.
    /// </summary>    
    [ClientFlatten]    
    [CustomSwaggerSchemaInheritance(externalSchemaName: "MySwaggerOnlyBaseCLass", notInheritedPropertiesName: new[] { nameof(Properties) }, CommonObjectType.ResourceProviderCommonDefinition )]
    public class SomeResourceModel
    {
        /// <summary>
        /// Gets or sets nested level of properties which contains the resource content
        /// </summary>
        [JsonProperty("properties", Required = Required.Always)]
        public MyResourceProperties Properties { get; set; }
    }

will result this Definition in swagger:

 "definitions": {
    "SomeResourceModel": {
      "description": "The Some Resource Properties model.",
      "required": [
        "properties"
      ],
      "type": "object",
      "allOf": [
        {
          "$ref": "../../../common/2.0/types.json#/definitions/MySwaggerOnlyBaseClass"
        }
      ],
      "properties": {
        "properties": {
          "description": "Gets or sets nested level of properties which contains the resource content",
          "type": "object",
          "allOf": [
            {
              "$ref": "#/definitions/MyResourceProperties"
            }
          ],
          "x-ms-client-flatten": true,          
        }
      }
    }
 }

Inheritance and Polymorphism

Polymorphic in this context means, that your request or response is/contains a base (or abstract) class.
this can lead to some challenges. in order to overcome it, OpenApiExtension has some out of the box solutions.
for starters you need to register your polymorphic class in the SwaggerConfig:

  • in case of "Virtual Inheritance" - means the inherited classes are not in your code, please refer (to sample)ArmResourceProviderDemo.
  • in other case you need to provide the Discriminator see reference here

Api version

Api version is supported out of box by reading the [ApiVersion] attributes on your Controllers.
to support also the "Api Version Fallback" you may Provide the [SwaggerApiVersionRangeAttribute] as well, to specify ranges version of that your api supports.

Creating Swagger files for your entire Repo by Script

intro

Since your code now contains everything that is needed to generate a well defined Swagger file, you can leverage an automation script that will:

  1. generate your swagger file
  2. "prettify" it so it would be valid for linters
  3. Generate Autorest csharp SDK so you can observe it.
  4. Validate that the swagger file is compliant

Implement

In order to support automate the process of generating and validating your swagger outcome, you need to set up 2 things:

  1. Setup the script configuration section.
  2. a WebHost function that hosts the app, and the cli tool can call it to run the swagger pipeline. see this example for more info.