소스 빌더 및 템플릿 코드

Introduction


소스 코드 생성기는 2020년 봄에 C#9.0에 처음 도입됐으며, 새로운 컴파일러 기능으로 개발자가 컴파일러에 추가할 수 있는 새 C# 소스 코드를 생성할 수 있도록 했다.이를 사용하면 컴파일러가 컴파일하는 동안 구축한 모든 풍부한 메타데이터 검사 코드를 사용하여 C# 코드를 분석한 데이터와 같은 컴파일러로 전송할 수 있습니다.만약 Roslyn 분석기에 익숙하다면, 원본 코드 생성기를 C# 원본 코드를 보낼 수 있는 분석기로 상상할 수 있다.이것은 기능이 강한 개발자 도구로 사용자 정의 서열화 생성부터 빠른 의존항 주입 용기 생성까지 코드를 확장할 수 있다.
코드 재구성은 개발자가 응용 프로그램을 유지하여 기술적 부담을 줄이고 사용하는 라이브러리를 갱신하거나 코드의 가독성을 유지하는 과정 중의 하나이다.샘플 코드의 주요 단점은 업무 논리를 차단하는 것이다. 프로그래머는 분석 기능을 필요로 하지 않고 전체 코드에서 그것을 찾아야 한다.
SourceMapper, SourceConfig, SourceApi 등 원본 코드 생성기 특성을 바탕으로 하는 세 개의 라이브러리를 소개하고 싶습니다. 해결 방안의 템플릿 코드를 줄이기 위해서입니다.모든 가방의 생각은 코드를 자동으로 생성하는 것이다. 코드에 사용할 수 있는 기능들이 포함되어 있다.

소스 매퍼


각종 기술 창고를 사용하여 서로 다른 클라우드 본체 응용 프로그램을 개발하는 동안 저는 자바가 광범위하게 사용하는 맵 라이브러리 MapStruct을 주목했습니다. 이 라이브러리에서 개발자는 자바 주석(C#attributes)을 사용하여 맵을 정의했습니다.SourceMapper 패키지는 Source Generators을 사용하고 인코딩 과정에서 C# 속성에 따라 객체 매핑을 생성합니다.물론 광범위하게 응용된다.NET Mapper 라이브러리 AutoMapper이지만 tham 간의 주요 차이점은 개발자가 생성된 코드에서 맵을 볼 수 있고 제어할 수 있다는 것이다.
Nuget 패키지 관리자를 사용하여 패키지를 설치할 수 있습니다.
Install-Package Compentio.SourceMapper
예를 들어 UserDaoUserInfo에 비치는 대상의 정의는 인터페이스 ClassName에서 정의할 수 있습니다 — 생성된 대상 맵 클래스 이름을 정의합니다.
[Mapper(ClassName = "UserMapper")]
public interface IUserMapper
{
    [Mapping(Source = nameof(UserDao.FirstName), Target = nameof(UserInfo.Name))]
    UserInfo MapToDomainModel(UserDao userDao);
}

이 패키지는 솔루션에 사용할 수 있는 매핑 코드를 생성합니다.
// <mapper-source-generated />
// <generated-at '01.10.2021 08:35:50' />
using System;

namespace Compentio.SourceMapper.Tests.Mappings
{
  public class UserMapper : IUserMapper
  {
      public static UserMapper Create() => new();
      public virtual Compentio.SourceMapper.Tests.Entities.UserInfo MapToDomainModel(Compentio.SourceMapper.Tests.Entities.UserDao userDao)
      {
          var target = new Compentio.SourceMapper.Tests.Entities.UserInfo();
          target.Name = userDao.FirstName;
          target.BirthDate = userDao.BirthDate;
          return target;
      }
   }
 }
각종 응용 프로그램의 의존항에 확장 코드를 주입합니다.NET 컨테이너도 프로젝트에서 사용하는 종속성 주입 컨테이너에 따라 생성됩니다.이렇게 하면 서비스, 컨트롤러 또는 저장소에 맵을 삽입하고 솔루션에서 도메인, DTO 및 DAO 변환을 깨끗하게 유지할 수 있습니다.
코드, 예제 및 기여에 대한 자세한 내용은 SourceMapper GitHub을 참조하십시오.

SourceConfig


SourceConfig 패키지는 Source Generators Additional File Transaformation 기능을 사용하여 추가 C# 코드를 생성합니다.이 가방의 생각은 매우 간단합니다. 프로젝트의 설정을 위한 POCO 클래스를 만드는 것이 아니라 주변에 대상을 생성하는 것입니다.먼저
Install-Package Compentio.SourceConfig
이후에 서로 다른 환경에 대한 설정이 적더라도 하나의 클래스에 통합됩니다.만약 개발과 생산에 사용할 두 개의 프로필이 있다면, 예를 들어 appsettings입니다.json:
{
  "$NoteEmailAddresses": [
    "[email protected]",
    "[email protected]",
    "[email protected]"
  ],
  "ConnectionTimeout": "30",
  "ConnectionHost": "https://test.com",
  "DefaultNote": {
    "Title": "DefaultTitle",
    "Description":  "DefaultDescription"
  }
}
환경 응용 프로그램 설정을 개발합니다.발전하다.json:
{
  "ConnectionTimeout": "300",
  "DatabaseSize": "200"
}
Visual Studio에서 속성을 C#analyzer 첨부 파일로 설정하기만 하면 됩니다.

또는 *.cproj 파일에서 다음을 수행합니다.
<ItemGroup>
    <AdditionalFiles Include="Appsettings.Development.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </AdditionalFiles>
    <AdditionalFiles Include="Appsettings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </AdditionalFiles>
</ItemGroup>
이렇게 하면 구성에 대한 객체가 생성되므로 새 구성 속성이 이 파일에 추가될 때 동기화를 유지할 필요가 없습니다. 이러한 객체는 클래스에 자동으로 적용됩니다!:
// <mapper-source-generated />
// <generated-at '01.12.2021 15:34:34' />
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace Compentio.SourceConfig.App
{
    [ExcludeFromCodeCoverage]
    public class AppSettings
    {
        public string ConnectionTimeout { get; set; }

        public string DatabaseSize { get; set; }

        public IEnumerable<string> _NoteEmailAddresses { get; set; }

        public string ConnectionHost { get; set; }

        public DefaultNote DefaultNote { get; set; }
    }

    [ExcludeFromCodeCoverage]
    public class DefaultNote
    {
        public string Title { get; set; }

        public string Description { get; set; }
    }
}
물론 SourceConfig GitHub에 소스 코드와 더 많은 내용이 있습니다.

SourceApi


웹 API를 실현할 때 두 가지 방법이 있다. 코드가 우선이다. 이것은 대부분의 개발자들이 좋아하는 것이다. 웹 API 컨트롤러 DTO를 만들고 시장을 뒤흔드는 UI를 추가하는 것이다. 그것뿐이다!먼저 API를 설계하거나 토론해야 할 때 우리는 비로소 그것을 실현하기 시작한다.다양한 기술과 API 사용자가 있는 분산 시스템에서는 언어와 무관한 도구를 사용해 사용자 간에 API를 협의하고 공유하는 것이 좋다.Open API 사양이 만들어졌습니다.

The OpenAPI Specification (OAS) defines a standard, language-agnostic interface to RESTful APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection.


세 번째 라이브러리는 SourceApi라고 하는데 API first 방법에 의해 만들어졌습니다. 당신(또는 당신의 팀)은 yaml 또는 json 형식으로 API를 정의하여 웹 API 프로젝트에 추가합니다. 이 패키지는 당신의 API에 DTO와 문서가 있는 컨트롤러 기본 클래스를 생성합니다.너는 컨트롤러에서 논리를 실현하기만 하면 된다.DTO가 이미 생성되었기 때문에 DTO를 만들 필요가 없습니다.
예를 들어, 표준 개방 API 예제에 대해 다음과 같이 정의합니다.
openapi: 3.0.1
info:
  title: Swagger Petstore
  description: 'This is a sample server Petstore server.  You can find out more about     Swagger
    at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).      For
    this sample, you can use the api key `special-key` to test the authorization     filters.'
  termsOfService: http://swagger.io/terms/
  contact:
    email: [email protected]
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.0
externalDocs:
  description: Find out more about Swagger
  url: http://swagger.io
servers:
- url: https://petstore.swagger.io/api/v1
- url: http://petstore.swagger.io/api/v1
paths:
  /store/inventory:
    get:
      summary: Returns pet inventories by status
      description: Returns a map of status codes to quantities
      operationId: getInventory
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: integer
                  format: int32
      security:
      - api_key: []
  /store/order:
    post:
      summary: Place an order for a pet
      operationId: placeOrder
      requestBody:
        description: order placed for purchasing the pet
        content:
          '*/*':
            schema:
              $ref: '#/components/schemas/Order'
        required: true
      responses:
        200:
          description: successful operation
          content:
            application/xml:
              schema:
                $ref: '#/components/schemas/Order'
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        400:
          description: Invalid Order
          content: {}
      x-codegen-request-body-name: body
  /store/order/{orderId}:
    get:
      summary: Find purchase order by ID
      description: For valid response try integer IDs with value >= 1 and <= 10.  Other
        values will generated exceptions
      operationId: getOrderById
      parameters:
      - name: orderId
        in: path
        description: ID of pet that needs to be fetched
        required: true
        schema:
          maximum: 10.0
          minimum: 1.0
          type: integer
          format: int64
      responses:
        200:
          description: successful operation
          content:
            application/xml:
              schema:
                $ref: '#/components/schemas/Order'
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        400:
          description: Invalid ID supplied
          content: {}
        404:
          description: Order not found
          content: {}
    delete:
      summary: Delete purchase order by ID
      description: For valid response try integer IDs with positive integer value.         Negative
        or non-integer values will generate API errors
      operationId: deleteOrder
      parameters:
      - name: orderId
        in: path
        description: ID of the order that needs to be deleted
        required: true
        schema:
          minimum: 1.0
          type: integer
          format: int64
      responses:
        400:
          description: Invalid ID supplied
          content: {}
        404:
          description: Order not found
          content: {}
components:
  schemas:
    Order:
      type: object
      additionalProperties: false
      properties:
        id:
          type: integer
          format: int64
        petId:
          type: integer
          format: int64
        quantity:
          type: integer
          format: int32
        shipDate:
          type: string
          format: date-time
        status:
          type: string
          description: Order Status
          enum:
          - placed
          - approved
          - delivered
        complete:
          type: boolean
          default: false
      xml:
        name: Order

StoreControllerBase 클래스 생성(엔진에서 NSwag CSharpControllerGenerator을 사용하여 추상 컨트롤러 생성):
namespace Compentio.SourceApi.WebExample.Controllers
{
    using System = global::System;

    [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v12.0.0.2))")]
    [Microsoft.AspNetCore.Mvc.Route("api/v1")]
    public abstract class StoreControllerBase : Microsoft.AspNetCore.Mvc.ControllerBase
    {
        /// <summary>Returns pet inventories by status</summary>
        /// <returns>successful operation</returns>
        [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("store/inventory")]
        public abstract System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.ActionResult<System.Collections.Generic.IDictionary<string, int>>> GetInventory();
        /// <summary>Place an order for a pet</summary>
        /// <param name = "body">order placed for purchasing the pet</param>
        /// <returns>successful operation</returns>
        [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("store/order")]
        public abstract System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.ActionResult<Order>> PlaceOrder([Microsoft.AspNetCore.Mvc.FromBody][Microsoft.AspNetCore.Mvc.ModelBinding.BindRequired] Order body);
        /// <summary>Find purchase order by ID</summary>
        /// <param name = "orderId">ID of pet that needs to be fetched</param>
        /// <returns>successful operation</returns>
        [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("store/order/{orderId}")]
        public abstract System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.ActionResult<Order>> GetOrderById([Microsoft.AspNetCore.Mvc.ModelBinding.BindRequired] long orderId);
        /// <summary>Delete purchase order by ID</summary>
        /// <param name = "orderId">ID of the order that needs to be deleted</param>
        [Microsoft.AspNetCore.Mvc.HttpDelete, Microsoft.AspNetCore.Mvc.Route("store/order/{orderId}")]
        public abstract System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.IActionResult> DeleteOrder([Microsoft.AspNetCore.Mvc.ModelBinding.BindRequired] long orderId);
    }

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v12.0.0.2)")]
    public partial class Order
    {
        [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public long Id { get; set; }

        [Newtonsoft.Json.JsonProperty("petId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public long PetId { get; set; }

        [Newtonsoft.Json.JsonProperty("quantity", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public int Quantity { get; set; }

        [Newtonsoft.Json.JsonProperty("shipDate", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public System.DateTimeOffset ShipDate { get; set; }

        /// <summary>Order Status</summary>
        [Newtonsoft.Json.JsonProperty("status", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
        public OrderStatus Status { get; set; }

        [Newtonsoft.Json.JsonProperty("complete", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public bool Complete { get; set; } = false;
    }

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v12.0.0.2)")]
    public enum OrderStatus
    {
        [System.Runtime.Serialization.EnumMember(Value = @"placed")]
        Placed = 0,
        [System.Runtime.Serialization.EnumMember(Value = @"approved")]
        Approved = 1,
        [System.Runtime.Serialization.EnumMember(Value = @"delivered")]
        Delivered = 2,
    }
}
또한 웹 API 컨트롤러의 기본 추상 클래스로 사용할 수 있습니다.응답 코드 정의가 아닌 논리에 집중하고 DTO와 동기화하기만 하면 됩니다. 열려 있는 API 정의 파일을 변경하면 기본 추상 클래스와 DTO가 새로 고쳐집니다.
namespace Compentio.SourceApi.WebExample.Controllers
{
    [ApiController]
    [ApiConventionType(typeof(DefaultApiConventions))]
    public class StoreController : StoreControllerBase
    {
        /// <inheritdoc />
        public async override Task<IActionResult> DeleteOrder([BindRequired] long orderId)
        {
            // Implement your async code here
            return Accepted();
        }

        /// <inheritdoc />
        public async override Task<ActionResult<IDictionary<string, int>>> GetInventory()
        {
            throw new NotImplementedException();
        }

        /// <inheritdoc />
        public async override Task<ActionResult<Order>> GetOrderById([BindRequired] long orderId)
        {
            throw new NotImplementedException();
        }

        /// <inheritdoc />
        public async override Task<ActionResult<Order>> PlaceOrder([BindRequired, FromBody] Order body)
        {
            throw new NotImplementedException();
        }
    }
}
패키지에는 기본 컨트롤러의 이름 공간을 정의할 수 있거나 특정 REST API 사용자인 경우 DTO만 생성할 수 있는 구성 속성도 추가되었습니다.
소스 코드와 설명서는 SourceApi GitHub에 있습니다.

좋은 웹페이지 즐겨찾기