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
<PackageReference Include="GN2.Common.Web" Version="3.0.0" />
<PackageVersion Include="GN2.Common.Web" Version="3.0.0" />
<PackageReference Include="GN2.Common.Web" />
paket add GN2.Common.Web --version 3.0.0
#r "nuget: GN2.Common.Web, 3.0.0"
#:package GN2.Common.Web@3.0.0
#addin nuget:?package=GN2.Common.Web&version=3.0.0
#tool nuget:?package=GN2.Common.Web&version=3.0.0
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.Library 의 RegisterAllowedCors |
RegisterCors |
사용 시 주의
UseWebApi()는RegisterWebApi()로 등록한 옵션을 컨테이너에서 찾습니다. 등록 없이 호출하면 예외로 알립니다.- 리버스 프록시(nginx · IIS) 뒤에서는 ForwardedHeaders 가 켜져 있어야 발급자 검증과 리디렉션이 https 로 유지됩니다. 기본값은 켜짐이며, 신뢰 경계 밖이라면
KnownProxies를 직접 구성하세요. - 헬스체크 엔드포인트는 인증 없이 열립니다. 외부에 노출하고 싶지 않다면
UseHealthChecks = false로 끄고 내부 경로에 직접 매핑하세요.
| Product | Versions 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. |
-
net10.0
- Asp.Versioning.Mvc.ApiExplorer (>= 10.2.1)
- GN2.Business (>= 3.0.0)
- GN2.Core (>= 3.0.0)
- Microsoft.AspNetCore.DataProtection.EntityFrameworkCore (>= 10.0.11)
- Microsoft.EntityFrameworkCore (>= 10.0.11)
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.11)
- Swashbuckle.AspNetCore (>= 10.2.3)
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 |
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