GN2.Common.Web 3.0.0

dotnet add package GN2.Common.Web --version 3.0.0
                    
NuGet\Install-Package GN2.Common.Web -Version 3.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="GN2.Common.Web" Version="3.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="GN2.Common.Web" Version="3.0.0" />
                    
Directory.Packages.props
<PackageReference Include="GN2.Common.Web" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add GN2.Common.Web --version 3.0.0
                    
#r "nuget: GN2.Common.Web, 3.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package GN2.Common.Web@3.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=GN2.Common.Web&version=3.0.0
                    
Install as a Cake Addin
#tool nuget:?package=GN2.Common.Web&version=3.0.0
                    
Install as a Cake Tool

GN2.Common.Web

GN2.Common.Web 는 GN2 인증서버(OpenIddict 기반)를 사용하는 ASP.NET Core Web API 를 최소한의 코드로 구성하기 위한 패키지입니다. 컨트롤러 · JSON · API 버저닝 · 인증 · 인가 · CORS · Swagger · 예외 처리 · 헬스체크를 한 번에 등록합니다.

패키지 정보

  • Target Framework: net10.0
  • Version: 3.0.0
  • NuGet: GN2.Common.Web
  • 의존성: GN2.Core, GN2.Business, Asp.Versioning.Mvc.ApiExplorer, Swashbuckle.AspNetCore, Microsoft.EntityFrameworkCore(.Relational), Microsoft.AspNetCore.DataProtection.EntityFrameworkCore

구성

Configurations/  SwaggerConfiguration, WebApiOptions, SwaggerDocumentOptions
Extends/         RegisterWebApi · UseWebApi · RegisterApiServer · RegisterVersioning · RegisterCors
                 MapHealthEndpoint · UseRefuseSearchEngine · HttpRequest · IFormFile 확장
Swagger/         RegisterSwaggerForIdentity · UseSwaggerVersion · 문서/오퍼레이션 필터
ErrorHandling/   RegisterProblemDetails · Gn2ExceptionHandler
Controllers/     BaseApiController
EntityFramework/ AuditableEntity 매핑 · 공개/관리자 조회 필터 · DataProtection 키 저장
Models/Paging/   PagingHelper

빠른 시작

using GN2.Common.Web.Configurations;
using GN2.Common.Web.Extends;
using GN2.Core.Configurations;

var builder = WebApplication.CreateBuilder(args);

builder.Services.RegisterWebApi(options =>
{
    options.Identity = builder.Configuration.GetSection("Identity").Get<IdentityConfiguration>();
    options.Swagger = builder.Configuration.GetSection("Swagger").Get<SwaggerConfiguration>();
    options.AllowedOrigins = ["https://admin.example.com", "https://app.example.com"];
    options.XmlCommentsFile = "MyApi.xml";
});

var app = builder.Build();

app.UseWebApi();
app.Run();

UseWebApi() 는 아래 순서로 파이프라인을 구성합니다.

ForwardedHeaders → 예외 처리(ProblemDetails) → CORS → Swagger → 인증 → 인가 → MapControllers → 헬스체크

options.Identity 를 지정하면 GN2.Business 의 JwtBearer 인증과 4단계 역할 정책 (Admin · Manager · LocalManager · User)이 함께 등록됩니다.

개별 구성

한 줄 구성 대신 필요한 것만 조합할 수도 있습니다.

builder.Services.RegisterApiServer();
builder.Services.RegisterVersioning();
builder.Services.RegisterProblemDetails();
builder.Services.RegisterAuthentication(identityConfig);      // GN2.Business
builder.Services.RegisterAuthorization(identityConfig);       // GN2.Business
builder.Services.RegisterCors(["https://admin.example.com"]);
builder.Services.RegisterSwaggerForIdentity(swaggerConfig, identityConfig, "MyApi.xml");
builder.Services.RegisterHealthChecks().AddDbContextCheck<AppDbContext>();

var app = builder.Build();

app.AddForwardHeaders();                                      // GN2.Business
app.UseExceptionHandler();
app.UseCors();
app.UseSwaggerVersion(swaggerConfig);
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapHealthEndpoint();

Swagger

API 버전마다 문서가 하나씩 만들어집니다. 문서 이름은 ApiExplorer 그룹 이름(v1, v2)이며 컨트롤러 네임스페이스와 무관합니다.

[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class ArticlesController : BaseApiController { }
  • SwaggerConfiguration.OAuthClientId 를 지정하면 Swagger UI 의 Authorize 버튼이 인증서버의 Authorization Code + PKCE 흐름으로 연결됩니다. 스코프는 IdentityConfiguration.Scope 를 사용합니다.
  • 인증서버의 클라이언트 설정에 Swagger UI 의 리디렉션 주소({BaseUrl}/swagger/oauth2-redirect.html)를 등록해야 합니다.
  • XML 주석을 포함하려면 프로젝트에 <GenerateDocumentationFile>true</GenerateDocumentationFile> 를 켜고 XmlCommentsFile 에 파일 이름을 지정합니다. 파일이 없으면 조용히 건너뜁니다.

오류 응답

처리되지 않은 예외와 모델 검증 실패가 RFC 7807 ProblemDetails 로 통일됩니다.

throw new RestException(HttpStatusCode.NotFound, "게시글을 찾을 수 없습니다.", "article.not_found");
{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
  "title": "대상을 찾을 수 없습니다.",
  "status": 404,
  "detail": "게시글을 찾을 수 없습니다.",
  "instance": "/api/v1/articles/10",
  "errorKey": "article.not_found",
  "traceId": "00-9f2b...-01"
}
  • RestException 은 예외에 담긴 상태 코드로 응답하고 ErrorKey · 하위 오류 목록을 함께 내려보냅니다.
  • 그 밖의 예외는 500 으로 처리하며, 스택 트레이스는 개발 환경에서만 응답에 포함됩니다.
  • 클라이언트가 연결을 끊은 요청은 499 로 기록하고 오류 로그를 남기지 않습니다.

컨트롤러

[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class ArticlesController : BaseApiController
{
    [HttpGet]
    public async Task<IActionResult> GetList([FromQuery] PagingParameter paging)
    {
        var (items, total) = await _service.GetListAsync(paging, RequestAborted);
        return ApiPaged(items, total, paging);       // X-Total-Count · X-Page · X-Page-Size 헤더 포함
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> Get(long id)
        => ApiResult(await _service.GetAsync(id, RequestAborted), HttpStatusCode.NotFound);

    [HttpPost, Authorize(Policy = AuthorizationConsts.ManagerPolicy)]
    public async Task<IActionResult> Create(ArticleRequest request)
    {
        var result = await _service.CreateAsync(request, CurrentUserId, RequestAborted);
        return ApiCreated(result, $"/api/v1/articles/{result.Data?.Id}");
    }
}

CurrentUserId · CurrentRole · CurrentRoles · HasAnyRole() · HasScope() · ClientIpAddress · RequestAborted 를 그대로 사용할 수 있습니다.

CORS

builder.Services.RegisterCors(["https://admin.example.com", "https://*.example.com"], allowCredentials: true);
  • 끝의 슬래시는 자동으로 제거합니다(브라우저는 슬래시가 붙은 오리진을 매칭하지 못합니다).
  • https://*.example.com 처럼 와일드카드 하위 도메인을 사용할 수 있습니다.
  • 모든 오리진을 허용하려면 ["*"] 을 전달합니다. 이때 자격 증명 허용은 브라우저 규격상 불가능하므로 예외로 알립니다.

EF Core

using GN2.Common.Web.EntityFramework;

builder.Entity<Article>().ConfigureBaseEntity();
builder.Entity<Article>().OwnsOne(x => x.Condition, y => y.ConfigureAuditableEntityForSqlServer());

var items = db.Articles.WherePublicVisible();     // 활성 + 사용
var all = db.Articles.WhereAdminVisible();        // 논리 삭제 제외

여러 인스턴스가 쿠키 · 토큰 보호를 공유하려면 DataProtection 키를 DB 에 저장합니다.

builder.Services.RegisterDataProtection<AppDbContext>("MyApi");

업로드 파일

using GN2.Common.Web.Extends;

if (!file.IsAllowedContentType()) return ApiFail("지원하지 않는 형식입니다.");

var fileName = file.CreateSafeFileName();          // 3f2a...c1.png

확장자는 허용 목록에서만 결정합니다. 클라이언트가 보낸 Content-Type 이나 파일 이름을 그대로 쓰면 실행 가능한 확장자로 저장될 수 있기 때문입니다.

2.x 에서 올라올 때

2.x 3.0.0
RegisterApiServer() + RegisterVersioning() + RegisterSwaggerForIdentity() + RegisterAuthentication() RegisterWebApi(options => …) 한 번
UseSwaggerVersion() + UseAuthentication() + UseAuthorization() + MapControllers() UseWebApi() 한 번
BaseController (쿠키 · 세션 · 뷰) BaseApiController (ControllerBase 기반)
SwaggerConfiguration.oAuthClientID SwaggerConfiguration.OAuthClientId
SwaggerConfiguration.BaseUrl · IdentityUrl · ApiVersion 제거 — 인증서버 정보는 IdentityConfiguration 사용
GroupByApiRootConvention 제거 — 그룹 이름은 API 버전에서 생성
IFormFile.GetExtensionFromMime() IFormFile.GetSafeExtension() · CreateSafeFileName()
HtmlRender, RenderCache, ErrorViewModel, CookieSetting, SetSessionUse, RegisterIdentitySelfhost 제거 (뷰 · 자체 호스팅 Identity 전용)
GN2.Common.LibraryRegisterAllowedCors RegisterCors

사용 시 주의

  • UseWebApi()RegisterWebApi() 로 등록한 옵션을 컨테이너에서 찾습니다. 등록 없이 호출하면 예외로 알립니다.
  • 리버스 프록시(nginx · IIS) 뒤에서는 ForwardedHeaders 가 켜져 있어야 발급자 검증과 리디렉션이 https 로 유지됩니다. 기본값은 켜짐이며, 신뢰 경계 밖이라면 KnownProxies 를 직접 구성하세요.
  • 헬스체크 엔드포인트는 인증 없이 열립니다. 외부에 노출하고 싶지 않다면 UseHealthChecks = false 로 끄고 내부 경로에 직접 매핑하세요.
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.0 115 8/22/2026
2.1.1 137 3/17/2026
2.1.0 116 3/11/2026
2.0.4 119 3/2/2026
2.0.3 119 3/2/2026
2.0.2 153 2/8/2026
2.0.1 125 2/8/2026
2.0.0 127 2/8/2026
1.2.4 242 10/20/2025
1.2.3 224 10/20/2025
1.2.2 216 10/20/2025
1.2.1 226 10/19/2025
1.2.0 228 10/19/2025
1.1.9 235 10/19/2025
1.1.8 222 10/19/2025
1.1.7 236 10/9/2025
1.1.6 212 10/3/2025
1.1.5 207 9/5/2025
1.1.4 197 9/5/2025
1.1.3 199 9/5/2025
Loading failed

3.0.0 (호환성 없는 변경 포함)

패키지 범위를 "OpenIddict 인증서버를 사용하는 Web API 클라이언트 구축" 으로 좁히고, MVC · 뷰 전용 코드를 걷어냈습니다.

구성
- GN2.Business 를 참조합니다. RegisterWebApi 한 번으로 인증 · 인가까지 등록되며, 소비 프로젝트가 두 패키지를 각각 조립할 필요가 없습니다.
- SDK 를 Microsoft.NET.Sdk.Web 에서 Microsoft.NET.Sdk + FrameworkReference 로 바꿨습니다. 패키지에 launchSettings · staticwebassets 가 섞여 들어가지 않습니다.
- RegisterWebApi / UseWebApi 로 서버 구성과 미들웨어 순서를 한 번에 처리합니다.

추가
- ProblemDetails 기반 전역 예외 처리(Gn2ExceptionHandler)와 모델 검증 응답 통일. RestException 의 상태 코드 · ErrorKey · 하위 오류 목록이 응답에 반영되고, traceId 가 모든 오류에 포함됩니다.
- RegisterCors — 끝 슬래시 제거, 와일드카드 하위 도메인, 자격 증명 조합 검증을 포함합니다. GN2.Common.Library 2.x 의 RegisterAllowedCors 를 대체합니다.
- 헬스체크 등록과 JSON 응답 엔드포인트(MapHealthEndpoint).
- BaseApiController — ReturnValue 변환, 201/204 응답, 페이징 헤더(X-Total-Count · X-Page · X-Page-Size), 현재 사용자 · 역할 · 스코프 조회.
- 업로드 파일 확장자 · 안전 파일명 생성, 절대 URL · 현재 URL · 클라이언트 IP 조회.

수정
- Swagger 문서가 두 경로에서 중복 등록되던 문제를 해결했습니다. 이제 API 버전마다 문서 하나가 생성됩니다.
- GroupByApiRootConvention 을 제거했습니다. 컨트롤러 네임스페이스의 마지막 토큰으로 ApiExplorer 그룹 이름을 덮어써서, 네임스페이스가 버전 규칙을 따르지 않으면 Swagger 문서가 비는 문제가 있었습니다. 그룹 이름은 API 버전에서 생성됩니다.
- 업로드 파일의 확장자를 Content-Type 문자열에서 그대로 잘라 쓰던 문제를 수정했습니다. 조작된 Content-Type 으로 실행 가능한 확장자가 만들어질 수 있었습니다. 이제 허용 목록에서만 결정합니다.
- GetAbsoluteUrl 이 PathBase 를 빠뜨리고, 경로 앞 슬래시를 보정하지 않으며, 스킴과 무관하게 포트 80 · 443 을 생략하던 문제를 수정했습니다.
- RemoveVersionParameterFilter 가 api-version 파라미터가 둘 이상일 때 예외를 던지던 문제를 수정했습니다.
- BadRequest(result.Message) 로 메시지만 반환하며 오류 코드를 버리던 응답 변환을 결과 본문 전체 반환으로 바꿨습니다.

제거
- MVC · 뷰 전용 자산: BaseController(쿠키 · 세션 헬퍼), HtmlRender, RenderCache, ErrorViewModel, CookieSetting, SetSessionUse, RegisterIdentitySelfhost, GN2Common
- SwaggerConfiguration 의 미사용 속성(BaseUrl · IdentityUrl · ApiVersion). 인증서버 주소와 스코프는 IdentityConfiguration 이 단일 출처입니다.
- oAuthClientID 를 OAuthClientId 로 이름을 정리했습니다.
- RegisterApiServer 가 AddControllersWithViews 대신 AddControllers 를 사용합니다.

전체 변경 내역: https://github.com/gn2studio/GN2/releases