OpenApi, Swagger, NSwag
Swagger를 써서 api의 상세스팩을(open api) 만들고 이걸 swagger website를 이용하여 웹화면으로 보게 해준다.
openapi 3.0규격에 맞게 문서를 생성해주므로 아주 편하다.
기본 프로젝트 생성
mkdir ~/Desktop/NSwagSample
cd ~/Desktop/NSwagSample
dotnet new web
package install
dotnet add package NSwag.AspNetCore
dotnet add package NSwag.MSBuild
startup.cs
public void ConfigureServices(IServiceCollection services)
{
//jwt token을 사용하여 인증을 통과 후 테스트가 가능하게
services.AddOpenApiDocument(configure =>
{
configure.Title = "My API";
configure.AddSecurity("JWT", Enumerable.Empty<string>(), new OpenApiSecurityScheme
{
Type = OpenApiSecuritySchemeType.ApiKey,
Name = "Authorization",
In = OpenApiSecurityApiKeyLocation.Header,
Description = "Type into the textbox: Bearer {your JWT token}."
});
configure.OperationProcessors.Add(new AspNetCoreOperationSecurityScopeProcessor("JWT"));
});
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseOpenApi();
app.UseSwaggerUi3(options =>
{
options.Path = "/swagger";
options.DocumentPath = "/swagger/v1/swagger.json";
});
app.UseReDoc(options =>
{
options.Path = "/redoc";
options.DocumentPath = "/swagger/v1/swagger.json";
});
...
}
이제 프로젝트를 실행후 웹사이트를 확인해보자.
https://localhost:5001/swagger
https://localhost:5001/recoc
openapi오 redoc스타일 둘다 볼수 있다.
project.csproj 수정
<Target Name="NSwag" AfterTargets="Build" Condition="$(Configuration)== 'Debug'">
<Exec Command="$(NSwagExe_Net50) run nswag.json /variables:Configuration=$(Configuration)" />
</Target>
NSwagExe_Net50 : 이부분은 각자 알아서 바꾼다. github에 가보면 적당한 내용이 다 있다.
debug일때만 코드 자동생성 하려고 Condition="$(Configuration)== 'Debug'
이부분 추가
위 내용은 보면 빌드후 커맨드를 하나 실행하는데 nswag.json 을 참고함을 알수 있다.
csproj파일에 같은 위치에 파일을 하나 만들자.
vi nswag.json
{
"runtime": "Net50",
"defaultVariables": null,
"documentGenerator": {
"aspNetCoreToOpenApi": {
"project": "WebAPI.csproj",
"msBuildProjectExtensionsPath": null,
"configuration": null,
"runtime": null,
"targetFramework": null,
"noBuild": true,
"verbose": false,
"workingDirectory": null,
"requireParametersWithoutDefault": true,
"apiGroupNames": null,
"defaultPropertyNameHandling": "CamelCase",
"defaultReferenceTypeNullHandling": "Null",
"defaultDictionaryValueReferenceTypeNullHandling": "NotNull",
"defaultResponseReferenceTypeNullHandling": "NotNull",
"defaultEnumHandling": "Integer",
"flattenInheritanceHierarchy": false,
"generateKnownTypes": true,
"generateEnumMappingDescription": false,
"generateXmlObjects": false,
"generateAbstractProperties": false,
"generateAbstractSchemas": true,
"ignoreObsoleteProperties": false,
"allowReferencesWithProperties": false,
"excludedTypeNames": [],
"serviceHost": null,
"serviceBasePath": null,
"serviceSchemes": [],
"infoTitle": "WEB API",
"infoDescription": null,
"infoVersion": "1.0.0",
"documentTemplate": null,
"documentProcessorTypes": [],
"operationProcessorTypes": [],
"typeNameGeneratorType": null,
"schemaNameGeneratorType": null,
"contractResolverType": null,
"serializerSettingsType": null,
"useDocumentProvider": true,
"documentName": "v1",
"aspNetCoreEnvironment": null,
"createWebHostBuilderMethod": null,
"startupType": null,
"allowNullableBodyParameters": true,
"output": "wwwroot/api/specification.json",
"outputType": "OpenApi3",
"assemblyPaths": [],
"assemblyConfig": null,
"referencePaths": [],
"useNuGetCache": false
}
},
"codeGenerators": {
"openApiToTypeScriptClient": {
"className": "{controller}Client",
"moduleName": "",
"namespace": "",
"typeScriptVersion": 2.7,
"template": "Angular",
"promiseType": "Promise",
"httpClass": "HttpClient",
"withCredentials": false,
"useSingletonProvider": true,
"injectionTokenType": "InjectionToken",
"rxJsVersion": 6.0,
"dateTimeType": "Date",
"nullValue": "Undefined",
"generateClientClasses": true,
"generateClientInterfaces": true,
"generateOptionalParameters": false,
"exportTypes": true,
"wrapDtoExceptions": false,
"exceptionClass": "SwaggerException",
"clientBaseClass": null,
"wrapResponses": false,
"wrapResponseMethods": [],
"generateResponseClasses": true,
"responseClass": "SwaggerResponse",
"protectedMethods": [],
"configurationClass": null,
"useTransformOptionsMethod": false,
"useTransformResultMethod": false,
"generateDtoTypes": true,
"operationGenerationMode": "MultipleClientsFromOperationId",
"markOptionalProperties": true,
"generateCloneMethod": false,
"typeStyle": "Class",
"classTypes": [],
"extendedClasses": [],
"extensionCode": null,
"generateDefaultValues": true,
"excludedTypeNames": [],
"excludedParameterNames": [],
"handleReferences": false,
"generateConstructorInterface": true,
"convertConstructorInterfaceData": false,
"importRequiredTypes": true,
"useGetBaseUrlMethod": false,
"baseUrlTokenName": "API_BASE_URL",
"queryNullValue": "",
"inlineNamedDictionaries": false,
"inlineNamedAny": false,
"templateDirectory": null,
"typeNameGeneratorType": null,
"propertyNameGeneratorType": null,
"enumNameGeneratorType": null,
"serviceHost": null,
"serviceSchemes": null,
"output": "../../../rendercore-cloud-www/src/app/shared/web-api-client.ts"
}
}
}
“output”: “wwwroot/api/specification.json” : 이부분이 spec을 만들어줘서 이 경로에 json을 저장한다.
“output”: “../../../frontend/src/app/shared/web-api-client.ts” : 이부분은 위 json을 이용하여 클라이언트 코드를 자동 생성해준 클라이언트 파일이다. 나의 경우는 프론트앤드에 shared폴더에 복사를 자동으로 하게 해두었다.
위 설정 파일은 nswagstudio라는 윈도우 프로그램을 설치하면 만들수 있고 https://github.com/RicoSuter/NSwag/wiki/NSwagStudio 또는 이걸 만들어주는 웹사이트도 있다고한다.
클라이언트코드는 c#용 type script용 등으로 만들수 있다.
swagger웹사이트에서 자기에게 필요한 변환기를 찾아볼수도 있다.
이제 프론트에서는 생성된 클라를 사용하기만 하면된다.
이제 빌드하면 문서도 자동으로 생기고 클라이언트 파일도 생성이 되서 작업하기는 편하게 됬다.
장단점
- 생성된 코드가 편하게 이해할수 없다.
- 매번 api가 바뀔때마다 재 생성되므로 관련 내용을 프론트에서 수정하지 않아도 되니 편하다.
- 커스터마이즈가 잘 안된다.
- 개인프로젝트에 한번 써보려고 햇는데 생성된 코드는 잘 안되는걸 확인했다.
- dotnet 5 부터 connect service라는 기능으로 이 클라이언트를 이용하여 코딩하는 부분이 쉬워졌다.
- 클라이언트를 그냥 쓸수 있을 정도의 프로젝트라면 상당한 속도의 코딩을 해나갈수 있을듯 하다.
- 문서화가 잘되기때문에 프론트 작업자와 커뮤니케이션을 줄일수 있다.