이번 포스팅에서는 ASP.NET Core의 Razor Page를 사용해 간단한 웹서비스를 구현해 보고자 합니다.
1. HTTP
클라이언트(User-Agent)가 서버와의 통신에서 HTTP를 사용한다는 것은 특정 주소에 대한 HTTP요청을 만들어 서버에 요청하게 되고 서버는 이 요청에 따라 해당 응답을 생성해 클라이언트로 반환하는 구조로 통신함을 의미합니다.
예를 들어 사용자가 웹 브라우저에서 http://cliel.com/ 주소를 입력하면 해당 요청을 서버에 전송하게 되고 서버는 해당 주소에 지정된 서버의 Resource를 반환함으로서 사용자의 웹브라우저에서 반환된 Resource를 표시하게 됩니다.
2. URL
우리가 인터넷 주소라고 칭하는 URL은 세부적으로 다음과 같이 구성되어 있습니다.
- Scheme : http 혹은 https 입니다.
- Domain : TLD(Top-Level Domain)라고 하는 주소를 의미하며 cliel.com과 같은 것을 말하며 www나 intranet과 같은 서브도메인을 포함합니다.
- Port : 웹의 전용포트는 80번이며 https와 같은 경우 443포트를 사용합니다. 80번과 443포트는 웹의 기본 포트이기에 웹브라우저에서 해당 포트를 일일이 지정하지 않더라도 해당 포트를 통해 통신하게 됩니다.
- Path : cliel.com 도메인 끝에서 /customer/product 와 같이 붙은 주소를 의미합니다.
- Query String : HTTP 요청시 특정 값을 전달하기 위한 부분입니다. 예를 들어 http://cliel.com/product?id=10 에서 id=10이 id이름으로 10의 값을 전달하도록 Query String을 사용한 것입니다.
- Fragment : 해당 웹페이지 내부를 이동하기 위한 부분으로 #document와 같은 표시로 사용됩니다. 문서가 긴 경우 내부링크를 통해 원하는 부분으로 한번에 이동시키기 위한 방법으로 주소 사용됩니다.
3. 웹 브라우저 사용해 보기
macOS의 경우 safar가 주요 웹브라우저로 선호될 수 있으며 Windows의 경우 Google의 Chrome을 주소 사용할 수도 있습니다. 각자 나름대로 선호하는 웹브라우저를 사용할 수 있지만 이번 포스팅에서는 마이크로 소프트의 Edge를 사용해볼 것입니다.
브라우저를 실행해 F12키를 눌러 개발자 도구를 실행합니다. 그리고 주소입력창에 http://cliel.com 을 입력해 봅니다.
개발자 도구에서 Network 탭에 들어가 cliel.com 부분을 클릭한뒤 Header를 선택하면 요청과 응답에 대한 Header내용을 살펴볼 수 있습니다.
- Request Method : 요청방식을 의미합니다. 예제에서는 GET이며 경우에 따라 POST, DELETE, PATCH등 다양한 방식의 요청이 있을 수 있습니다. 이에 대한 자세한 사항은 별도로 다루도록 하겠습니다.
- Statuc Code : 응답 상태코드입니다. 예제에서의 값은 200이며 정상을 의미합니다. 서버 에러의 경우 500번대가 나올 수 있으며 요청한 페이지를 찾을 수 없는 경우 주로 404가 응답되기도 합니다.
- Request Header : 요청할때의 정보를 의미합니다. 여기서 accept는 웹브라우저가 받아들일 수 있는 문서의 형식을 말하는 것으로 예제에서는 html이나 xhtml, xml, image등의 정보를 받을 수 있다고 서버에 알려주고 있습니다. 대부분 이것은 가장 선호되는 형식을 의미하는 것으로 마지막의 */* 요청을 통해 기본적으로는 모든 문서를 받아들일 수 있음을 명시하고 있습니다. 또한 q=에서 q는 품질값을 의미합니다. 기본 가중치는 1.0이지만 예제에서 XML에 0.9가 기타 그 밖에 다른 형식에는 q=0.8로 0.8값이 지정됨으로서 HTML이나 XHTML보다는 더 적게 선호됨을 알 수 있습니다. accept-encoding은 브라우저가 받아들일 수 있는 압축알고리즘 방식입니다. 예제에서 gzip, deflate의 방식을 명시하였습니다. accept-language는 선호되는 국가별 언어입니다. 웹브라우저가 국제적인 다중 언어를 지원하는 경우 해당 값을 분석해 원하는 언어로 응답할 수 있도록 활용되기도 합니다. 참고로 여러언어가 지정될 수 있으며 해당 값의 경우에도 품질값을 적용해 가장 선호되는 언어를 구분할 수 있습니다.
- Response Header : 서버가 응답할때의 정보를 의미합니다. Content-Type으로 응답된 문서형식을 알 수 있습니다.
4. Client-Side 웹 개발
웹 개발에서 Server Side언어로는 ASP.NET Core를 사용하기로 했습니다. Back-End개발에서는 대부분 하나의 언어가 사용되지만 반면 Client Side에서 사용되는 기술은 HTML이나 CSS, Javascript등 다양한 언어가 사용될 수 있습니다.
- HTML : 웹문서 내부의 content를 구성하는데 사용됩니다. 2022년 현재까지 표준화가 가장 잘 적용된 HTML5가 많이 사용되고 있습니다.
- CSS : 웹 페이지를 보기쉽고 단정하게 꾸미기 위한 스타일을 입히는 기술이며 2022년 현재까지 표준화가 가장 잘 적용된 CSS3 버전이 많이 사용되고 있습니다.
- JavaScript : 웹브라우저에서 동작하는 프로그램을 위해 사용됩니다. 최근에는 node.js와 같은 플렛폼을 통해 웹브라우저가 아닌 서버개발분야에서도 JavaScript가 많이 사용되고 있습니다.
기타 이 밖에 Bootstrap이나 CSS전처리기인 SASS와 LESS 그리고 TypeScript, JQuery, React, Vue등 다양한 CSS나 Javascript기반의 기술이 Font-End에 사용될 수 있습니다.
5. ASP.NET Core
ASP.NET Core는 수년간 발전을 거듭해온 마이크로소프트의 웹개발기술중 하나이며 다음과 같은 웹개발 기술에서 발전된 형태로 이해할 수 있습니다.
- ASP (Active Server Page) : 1996년에 발표된 마이크로소프트의 첫번째 웹개발 플렛폼입니다. ASP는 Visual Basic를 간소화한 VBScript라는 것을 사용하였으며 HTML과 같이 섞어서 사용하는 방식이었습니다.
- ASP.NET WebForm : 2002년 .NET Framework와 함께 발표된 웹개발 플렛폼입니다. 특이하게 WinForm과 같이 UI를 끌어다 원하는 형태로 디자인이 가능하였으며 이로 인해 비 웹개발자들이 쉽게 웹개발에 적응할 수 있었습니다. 이후 ASP.NET MVC의 등장으로 점차 쇠퇴하기 시작하였으나 아직도 많은 레거시 시스템에 적용되어 사용중에 있습니다.
- WCF(Windows Communication Foundation) : 2006년에 릴리즈되었으며 SOAP, REST 서비스등 웹서비스를 구축하기 위한 플랫입니다. SOAP는 강력한 웹서비스 구축을 가능하게 했지만 다소 복잡한 측면이 있었기에 그만한 웹서비스의 가능을 필요로 하지 않는 이상 잘 사용되지 않았습니다.
- ASP.NET MVC : 데이터를 다루는 Model, 사용자에게 UI등을 보여주기 위한 View 그리고 이들 사이를 중개하는 Controller의 구성을 통해 각각의 역활에 맞는 분리된 웹개발을 가능하게 하였으며 2009년 발표되었습니다. 이러한 개발 방식은 재사용성의 향상과 단위테스트에서도 용이하게 적용될 수 있었습니다.
- ASP.NET Web API : 2012년에 발표되었으며 기존 SOAP보다 더욱더 확장이 가능하면서도 간소화된 웹서비스의 개발을 가능하게하였습니다.
- ASP.NET SignalR : 2012년에 발표되었으며 채팅과 같은 실시간 통신시스템을 손쉽고 구현할 수 있도록 하였습니다. 특히 다양한 웹브라우저 상에서 실시간으로 처리되어야 하는 상황에 따른 해결점을 제시하였으며 심지어 WebSocket과 같은 기술을 지원하는 않는 브라우저에서도 사용할 수 있도록 지원하였습니다.
- ASP.NET Core : 2016년에 발표되었으며 기본 MVC, Web API, SignalR 기술을 Razor Pages, gRPC 나 Blazor등으로 결합한 것입니다. 또한 cross-platform 실행이 가능하게 되었습니다.
ASP.NET 2.0~2.2는 .NET Framework 4.6.1 및 이후 버전하에서 동작할 수 있으며 ASP.NET Core 3.0은 .NET Core 3.0을 ASP.NET Core 6.0은 .NET Core 6.0만을 지원합니다.
6. Class ASP.NET과 ASP.NET Core
ASP.NET은 .NET Framework내부에서 System.Web.dll이라는 하나의 거대한 Assembly로 존재했으며 또한 Windows Server의 IIS와 밀접하게 연결되어 왔습니다. 웹개발에 필요한 많은 것들을 제공하기는 했지만 cross-platform개발에는 맞지 않았습니다.
반면 ASP.NET Core는 기존 ASP.NET을 새롭게 설계하여 System.Web.dll과 IIS와의 의존성을 제거하고 상대적으로 가벼운 package로 구성하였습니다. 물론 IIS는 여전히 지원하고 있으며 Linux나 MAC OS에서의 개발또한 지원하고 있습니다.
또한 Microsoft는 Kestrel이라는 cross-platform용 Web Server를 만들어 ASP.NET Core에 내장하였으며 이 또한 오픈소스화 하였습니다. ASP.NET Core 2.2이후에서는 기본적으로 새로운 in-process 호스팅모델을 사용해 IIS 호스팅에서 기존대비 400%이상의 성능향상을 제공한다고 합니다. 하지만 더 나은 성능을 위해 Kestrel사용을 권장하고 있습니다.
7. ASP.NET Core Project 생성하기
Visual Studio 2022를 실행하여 File - New - Project 메뉴를 선택하고 'ASP.NET Core Empty' 템블릿을 선택합니다.
보시는 바와 같이 Project생성에 필요한 많은 Template을 지원하고 있지만 처음부터 비어있는 Project를 생성해 필요한 기능을 하나씩 붙여보는 것도 좋은 방법일 수 있기에 빈 프로젝트 생성부터 시작하고자 합니다.
'Next'버튼을 눌러 다음으로 넘어갑니다.
프로젝트명(Project name)에는 임의의 이름을 지정합니다. 예제에서는 MyWebProject라고 하였지만 반드시 이름이 같을 필요는 없습니다. Location설정으로 프로젝트의 경로를 바꿀 수 있습니다.
'Next'버튼을 눌러 다음으로 넘어갑니다.
아무런 문제가 없다면 위 화면과 동일할 것이며 현재 설정된 상태 그대로 두고 'Create'버튼을 눌러 프로젝트를 생성합니다.
프로젝트가 생성되었으면 Solution Explorer에서 프로젝트를 마우스 오른쪽 버튼을 눌러 'Edit Project File'을 선택하면 프로젝트의 csproj파일을 열어볼 수 있습니다.
그리고 Program.cs 파일을 열어봅니다.
Console Application의 Top-level Program과 매우 유사해 보입니다. (IDE환경 자체는 설정에 따라 다르게 보일 수 있습니다.)
Program.cs에서는 우선 CreateBuilder를 호출하여 웹 사이트에 대한 기본값을 사용하여 웹 사이트를 위한 호스트를 만듭니다. 그리고 웹사이트는 모든 GET요청에 대해 app.MapGet("/", () => "Hello World!"); 부분에 명시된 'Hello World!'를 응답하게 되고 마지막에 Run 메서드를 호출합니다.
Run은 일종의 차단메서드로서 웹서버가 동작을 중지할때까지 숨겨진 Main메서드(Top-Level Program에서 Main은 없는것이 아니라 숨겨진 것입니다.)가 반환되지 않도록 합니다.
이를 확인해 보기 위해 아래와 같이 간단한 메세지를 출력하는 구문을 맨 아래에 추가해줍니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Console.WriteLine("서버의 실행이 중지됨");
8. ASP.NET Core Project 실행하기
위와 같이 Program.cs 파일을 수정하고 Debug -> Start Without Debugging을 선택하거나 Ctrl + F5키를 눌러 프로젝트를 실행합니다.
성공적으로 실행되었습니다. 이전에는 프로젝트를 개발목적으로 실행하는데 IIS Express가 기본적으로 사용되었으며 지금도 여전히 사용가능하지만 ASP.NET Core에 들어와서는 Kestrel 웹서버가 사용됩니다.
서버의 실행을 중지하기 위해 Ctrl + C키를 사용합니다. 그러면 웹서버가 중지되면서 조금전 추가했던 Writeline() 메서드의 메세지를 볼 수 있게 됩니다.
예제에서 프로젝트의 실행에 사용된 https://localhost:7041은 HTTPS를 통한 보안연결에 해당합니다. 위 화면에 표시된것처럼 만약 http://localhost:5041로의 접속을 시도한다면 보연연결이 아닌 일반언결이 시도됩니다.
개발과정에서는 http와 https모두의 접속 테스트가 필요할 수 있지만 서비스로 배포하고 나면 https로의 접속만을 필요로 하는 경우가 많게됩니다. 즉, 개발환경이 아닌 경우 https로의 접속을 강제해야 한다면 아래와 같은 처리가 필요합니다.
if (!app.Environment.IsDevelopment())
{
app.UseHsts();
}
app.MapGet("/", () => "Hello World!");
UseHsts() 메서드에서 Hsts는 https 연결을 의미합니다. 만약 개발환경과 구분없이 모든 연결을 https로 강제한다면 아래와 같이 UseHttpsRedirection() 메서드를 사용합니다.
app.UseHttpsRedirection();
app.MapGet("/", () => "Hello World!");
그러면 사용자가 http로의 연결을 시도하더라도 강제로 https접속으로 redirect하게 됩니다.
또한 개발단계에서 예외가 발생하는 경우 WebBrowser를 통해 해당 상세내용을 보기위한 UseDeveloperExceptionPage() 메서드를 사용할 수도 있습니다.
if (!app.Environment.IsDevelopment())
{
app.UseHsts();
app.UseDeveloperExceptionPage();
}
단, ASP.NET Core 6 나 이후버전에서는 자동으로 실행되므로 프로젝트 템플릿에는 기본적으로 상기 코드를 포함하지 않습니다.
예제에서 사용된 IsDevelopment()는 현재 실행이 개발환경인지 아닌지를 판단하는 메서드인데 그렇다면 무엇을 기준으로 개발환경을 판단하는지 의문을 가질 수 있습니다.
ASP.NET Core는 호스팅 환경에서 사용하는 설정값을 설정파일로 부터 읽어 적용하게 됩니다. 따라서 설정파일에서의 설정을 통해 기본설정내용을 변경할 수 있습니다. 프로젝트의 Properties폴더안의 launchSettings.json 파일을 열어보면 관련 내용을 살펴볼 수 있습니다.
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:9841",
"sslPort": 44313
}
},
"profiles": {
"MyWebProject": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7041;http://localhost:5041",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
설정내용중 profiles부분이 개발을 위한 호스팅 환경설정입니다. applicationUrl은 프로젝트가 실행될때 연결되는 URL을 의미하므로 이곳에서 포트를 변경할 수 있고 launchBrowser는 Visual Studio가 자동적으로 browser를 실행할지의 여부를 나타냅니다. 특히 environmentVariables의 ASPNETCORE_ENVIRONMENT가 Development로 되어 있는것에 주목해 주세요. 이 값을 Production으로 바꾸면 IsDevelopment()는 false를 반환하게 됩니다.
9. 서비스와 pipeline을 위한 설정분리
웹서비스와 시작점과 설정은 별도의 Startup.cs 파일에 2개의 Method를 구현함으로서 분리할 수 있습니다. 프로젝트에 Startup.cs 파일을 추가하고 다음과 같이 ConfigureServices(IServiceCollection services)와 Configure(IApplicationBuilder app, IWebHostEnvironment env) 메서드를 추가합니다.
namespace MyWebProject
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (!env.IsDevelopment())
{
app.UseHsts();
}
app.UseRouting();
app.UseHttpsRedirection();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", () => "Hello World!");
});
}
}
}
ConfigureServices는 Razer Page이나 Cross-Origin Resource Sharing (CORS), database context와 같이 의존성 주입 컨테이너를 의존성 서비스에 추가하기 위한 메서드입니다. 아직까지는 추가가 필요한 의존성 서비스가 없기에 이 Method는 비워둡니다.
Configure는 요청과 응답전역에 대한 HTTP설정과 기능의 순차적 처리를 위한 Pipeline구성을 위해 app 매개변수를 사용하여 다양한 Use 메서드를 호출하는 부분입니다. 현재 HTTP 요청에 다한 설정과 함께 Endpoint를 사용하여 최상위 경로 '/'에 대한 Get요청에 설정된 문자열값을 반환하도록 하고 있습니다.
이 2개의 Method는 웹서비스가 시작되면 자동적으로 호출됩니다.
Startup.cs가 추가되었으면 기존의 Program.cs를 수정해 웹프로그램의 주요 진입점에서 Startup.cs를 사용할 것임을 지정해야 합니다.
using MyWebProject;
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
}).Build().Run();
위와 같이 수정한 후 프로젝트를 실행해 보면 이전과 같은 동작이 수행됨을 확인할 수 있습니다.
10. 정적요소 서비스하기
HTML이나 CSS혹은 Image와 같은 적정 컨텐츠를 서비스하기 위해서는 해당 파일들을 wwwroot이름의 폴더에 넣어두는데 이는 동적서비스요소와 분리하여 관리할 수 있는 효과를 가져오기도 합니다.
프로젝트에서 wwwroot라는 폴더를 다음과 같이 생성합니다.
그리고 wwwroot안에 간단한 내용의 Index.html 파일을 추가합니다.
하지만 파일을 추가하는 것만으로는 당장 서비스를 실행할 수 없습니다. 정적요소의 서비스를 위해서는 우선 아래와 같이 UseStaticFiles() 메서드를 호출하도록 추가해야 합니다.
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.MapGet("/hello", () => "Hello World!");
app.Run();
주의 : 이전에 설정을 분리하기 위한 Startup.cs 파일을 생성했는데 예제에서는 해당 파일은 사용하지 않고 .NET 6부터 기본제공되는 형태의 것을 그대로 사용할 것입니다.
UseStaticFiles() 메서드는 정적파일을 서비스할것임을 지정하고 있으며 UseDefaultFiles() 메서드를 통해 최상위 경로에서 요청파일이 지정되지 않으면 Default나 Index처럼 기본파일을 응답할 수 있도록 합니다. 또한 MapGet()에서 기존의 / 경로를 /hello로 변경하여 더이상 / 경로 요청을 MapGet()에서 받지 않도록 합니다.
참고로 UseDefaultFiles() 메서드는 UseStaticFiles()전에 호출되어야 합니다.
프로젝트를 실행하면 추가한 Index.html 파일이 정상적으로 서비스됨을 확인할 수 있습니다.
11. Razor Page 사용
Razor Page를 사용하면 C#코드와 HTML을 결합하여 동작페이지를 서비스할 수 있습니다.
Razor Page를 추가하기 위해 프로젝트에서 Pages라는 폴더를 생성하고 Index.cshtml 파일을 추가합니다. 단, 이전에 추가했던 Index.html파일은 삭제합니다. cshtml은 Razor Page의 확장자이며 특별한 이유가 없는한 Razor Page는 Pages폴더안에 추가합니다.
그리고 Program.cs에서 다음과 같이 Razor Page사용을 위한 AddRazorPages() 메서드를 서비스에 추가하고 Endpoint에서도 Razor Page가 응답할 수 있도록 MapRazorPages()메서드를 추가합니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.MapRazorPages();
app.MapGet("/hello", () => "Hello World!");
app.Run();
다시 조금전에 추가한 Index.cshtml파일로 돌아와 보면 Solution Explorer내부에서 cshtml로 추가한 파일이 2개의 cshtml과 cshtml.cs 2개의 파일로 나뉘어진것 확인할 수 있습니다.
첫번째 cshtml파일은 사용자에게 직접 보여지는 HTML을 포함하는 부분이며 cshtml.cs는 C#코드를 통해 특정 로직을 처리하는 부분입니다.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace MyWebProject.Pages
{
public class IndexModel : PageModel
{
public void OnGet()
{
}
}
}
해당 파일을 보면 우선 클래스가 PageModel로 부터 상속받고 있음을 알 수 있습니다. PageModel은 서비스를 위한 여러가지 모델을 정의하고 있으며 잠시 후 사용해볼 ViewData와 같은 멤버도 가지고 있으므로 페이지간 데이터전달을 손쉽게 구현할 수 있습니다.
cshtml.cs파일을 열어 파일 내용을 다음과 같이 수정합니다.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace MyWebProject.Pages
{
public class IndexModel : PageModel
{
public DateTime NowDay { get; set; }
public void OnGet()
{
NowDay = DateTime.Now;
}
}
}
그리고 cshtml 파일을 보면
@page
@model MyWebProject.Pages.testModel
@{
}
위와 같이 되어 있는걸 확인할 수 있는데 @model 부분은 cshtml.cs에서 구현된 testModel이라는 클래스를 Model이름으로 인스턴스화 하여 사용할 것임을 나타내는 것입니다. Model의 인스턴스는 자동으로 생성되기 때문에 cshtml안에서는 별도로 구현하지 않아도 Model을 통하여 cshtml.cs에서 구현한 속성등에 자유롭게 접근(한정자만 맞으면)할 수 있게 되는 것입니다.
과거 WinForm에서도 HTML부분과 cs부분을 나누었는데 원한다면 하나의 단일 페이지안에서 cs로직과 html구현을 같이 사용할 수 있었습니다. 이러한 작업 방식은 현재도 유효하므로 cshtml.cs없이 cshtml만 추가하여 다음과 같이 @function을 통해 cshtml.cs의 기능을 동일하게 구현할 수 있습니다.
@page
@functions
{
public DateTime NowDay { get; set; }
public void OnGet()
{
Model.NowDay = DateTime.Now;
}
}
cshtml에서 @Page는 자신이 Razor Page임을 알리는 것이므로 모든 Razor페이지마다 공통적으로 요구되는 부분입니다. cshtml.cs에서는 OnGet() 이라는 메서드를 포함하고 있는데 OnPost, OnDelete와 같은 메서드도 정의할 수 있습니다. 이들 메서드는 이름에서도 유추할 수 있듯이 HTTP요청과 관련된 메서드이므로 OnGet()은 HTTP의 Get요청에 응답하게 될것입니다.
cshtml을 수정하여 cshtml.cs에서 구현된 NowDay값을 가져와 화면에 표시되도록 합니다.
@page
@model MyWebProject.Pages.testModel
@{
}
<p>@Model.NowDay</p>
프로젝트를 실행한 다음 /test 주소를 붙여 주면 정상적으로 Razor Page가 화면에 표시될 것입니다.
12. Layout Page 사용
많은 페이지를 포함하고 있는 프로젝트에서 각 페이지마다 공통적인 영역(상단 혹은 하단이나 측면에)을 가져야 할때 페이지마다 일일이 공통영역을 표시하게 되면 공통영역의 내용이 수정되는 경우 해당 영역을 가진 모든 페이지를 수정해야 하므로 관리하는데 어려움이 따를 수 있습니다.
이를 위해 ASP.NET Core에서는 Layout Page 지원하고 있습니다.
Layout Page 사용을 위해서는 우선 공통영역에 해당하는 Razor Page를 만들어야 하며 이 페이지는 Shared라는 이름이 폴더에 저장하는 것이 좋습니다. 관례상 Layout Page는 Shared에 있음이 기본으로 정해져 있기 때문입니다. 파일 이름 은 어떤것을 사용해도 되지만 일반적으로 _Layout.cshtml를 사용하는 것이 좋습니다.
Layout Page를 만들기 전에 우선 '_ViewStart.cshtml'라는 이름의 파일을 먼저 생성할 것입니다. 이를 위해 Project Explorer에서 프로젝트의 Pages폴더에 마우스 오른쪽 버튼을 눌러 'Add -> New Item'을 선택한 다음 'Razor View Start'를 선택합니다.
파일명에 _ViewStart.cshtml이라고 표시될 것이며 이 상태에서 'Add'버튼을 눌러 페이지를 추가합니다. 파일내용은 다음과 같은 code가 기본적으로 생성될 것입니다.
@{
Layout = "_Layout";
}
그리고 Pages폴더에서 다시 'Shared'라는 폴더를 생성하고 해당 폴더에서는 이전과 같은 방법으로 하되 대신 'Razor Layout'을 선택해 추가합니다.
파일을 추가하면 최종적으로 다음과 같이 될것입니다.
대부분이 HTML 태그로 이루어져 있지만 @ViewBag.Title과 @RenderBody()라는 특이한 구문을 볼 수 있습니다. @ViewBag.Title에서 @ViewBag은 페이지사이에 데이터를 전달하는 가장 일반적인 방법으로서 이 경우 다른 페이지에서 @ViewBag의 Title에 값을 설정하면 방금 추가한 Layout 페이지에서 해당 값을 <title>태그에 표현하는 방식으로 작동합니다. 그리고 @RenderBody()는 Layout 페이지안에서 요청된 페이지의 내용을 삽입해 출력하는 부분입니다.
_Layout.cshtml 페이지의 동작을 확인해 보기 위해 _Layout.cshtml파일을 다음과 같이 수정하고
<body>
<h1>레이아웃 영역입니다.</h1>
<div>
@RenderBody()
</div>
</body>
Index.cshtml.cs안에서는 다음과 같이 ViewData["Title"] 를 추가해 _Layout.cshtml안에 있는 <title>태그에 제목을 표시하도록 합니다.
public void OnGet()
{
ViewData["Title"] = "메인페이지 입니다.";
NowDay = DateTime.Now;
}
그리고 프로젝트를 실행하면 다음과 같은 화면을 볼 수 있습니다.
Index 파일의 내용이 _Layout에 반영되어 표시되고 있습니다.
Index.cshtml.cs(code-behind 파일이라고 하며 줄여서 behind파일이라고 하겠습니다.)에서는 또한 현재 시간을 표시하기 위한 NowDay라는 변수를 포함하고 있는데 이 변수는 Index.cshtml(View)안에서 Model을 통해 NowDay변수에 접근하여 해당 값을 표현하고 있습니다. 이와 같은 방식을 사용하면 behind파일에서 View로 필요한 데이터를 전달해 표현할 수 있습니다.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace MyWebProject.Pages
{
public class IndexModel : PageModel
{
public DateTime NowDay { get; set; }
public List<PhoneBook>? Names { get; set; }
public void OnGet()
{
ViewData["Title"] = "메인페이지 입니다.";
NowDay = DateTime.Now;
Names = new() { new() { Name = "홍길동", PhoneNumber = "010-1234" }, new() { Name = "홍길순", PhoneNumber = "010-4567" } };
}
}
public class PhoneBook
{
public string? Name { get; set; }
public string? PhoneNumber { get; set; }
}
}
참고로 View 페이지에서는 C#구문을 사용하기 위해 @문자를 사용할 수 있습니다.
@page
@model MyWebProject.Pages.IndexModel
@{
}
<p>@Model.NowDay</p>
<table>
<thead>
<tr>
<th>이름</th>
<th>전화번호</th>
</tr>
</thead>
<tbody>
@if (Model.Names != null) {
@foreach(var item in Model.Names) {
<tr>
<td>@item.Name</td>
<td>@item.PhoneNumber</td>
</tr>
}
}
</tbody>
</table>
12. 데이터 처리를 위한 요청만들기
예제는 데이터를 처리를 위해 간단히 List형식을 빌리고 있지만 실제 상황에서는 대부분 MS SQL Server와 같은 데이터베이스를 사용할 것입니다. 그리고 필요한 경우 사용자에 의해 데이터를 변경하는 과정이 수반됩니다.
● Model과 Data정의
우선 Model(폴더)에는 위에서 정의한 PhoneBook을 분리합니다.
using System.ComponentModel.DataAnnotations;
namespace MyWebProject.Models
{
public class PhoneBook
{
[Required]
public string? Name { get; set; }
public string? PhoneNumber { get; set; }
}
}
PhoneBook 모델에는 Name에 [Required] attribute를 붙여 반드시 입력이 필요한 Field로 생성하였으며 이러한 설정은 ModelState.IsValid로 유효성을 판단할 수 있습니다.
그 다음 Data(폴더)에는 PhoneBook을 초기화 하고 추가한 Data를 저장할 수 있는 List형식의 저장소를 정의합니다. 대부분의 경우 이는 MS SQL Server나 MariaDB같은 관계형 Database가 사용되지만 예제는 소스를 간단히 List형식의 Property로 대신하고자 합니다.
using MyWebProject.Models;
namespace MyWebProject.Data
{
public static class DataPhoneBook
{
public static List<PhoneBook>? Names { get; set; }
}
}
위 2개의 파일을 추가하면 프로젝트는 전체적으로 아래와 같이 구성될 것입니다.
● 데이터 추가를 위한 Post 처리
앞에서 OnGet()은 Get요청을 처리한다고 하였습니다. 마찬가지로OnPost()는 사용자의 Post요청에 대응하게 됩니다.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using MyWebProject.Data;
using MyWebProject.Models;
namespace MyWebProject.Pages
{
public class IndexModel : PageModel
{
[BindProperty]
public PhoneBook? phoneBook { get; set; }
public void OnGet()
{
ViewData["Title"] = "메인페이지 입니다.";
if (DataPhoneBook.Names == null)
DataPhoneBook.Names = new() { new() { Name = "홍길동", PhoneNumber = "010-1234" }, new() { Name = "홍길순", PhoneNumber = "010-4567" } };
}
public IActionResult OnPost()
{
if (DataPhoneBook.Names != null && phoneBook != null && ModelState.IsValid)
{
DataPhoneBook.Names.Add(phoneBook);
return RedirectToPage("/Index");
}
else
return Page();
}
}
}
먼저 PhoneBook형식의 phoneBook 속성은 필요한 데이터를 전달받기 위한 것인데 [BindProperty] attribute를 사용해 전달되는 데이터를 자동으로 해당 클래스의 속성으로 바인딩하여 처리할 수 있도록 합니다.
새롭게 추가한 OnPost()는 조건에 맞으면 Names에 새로운 명단을 추가한뒤 가장 상위의 경로(Index 표시를 위한)로 Redirect를 수행하고 그렇지 않으면 Page()를 반환합니다. Page()는 원래 머물던 Page를 의미합니다.
다시 Index의 View페이지로 돌아와 페이지의 @model 구문 위에 @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 구문을 추가하고 페이지 아래부분에 새로운 PhoneBook의 추가를 위한 HTML을 작성합니다.
@page
@using MyWebProject.Data
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@model MyWebProject.Pages.IndexModel
@{
}
<table>
<thead>
<tr>
<th>이름</th>
<th>전화번호</th>
</tr>
</thead>
<tbody>
@if (DataPhoneBook.Names != null) {
@foreach(var item in DataPhoneBook.Names) {
<tr>
<td>@item.Name</td>
<td>@item.PhoneNumber</td>
</tr>
}
}
</tbody>
</table>
<div class="row">
<form method="POST">
<input asp-for="phoneBook.Name" placeholder="이름" /><br />
<input asp-for="phoneBook.PhoneNumber" placeholder="Country" /><br />
<input type="submit" />
</form>
</div>
TagHelper는 Razor Page에서 asp-for와 같은 Helper 태그를 사용할 수 있도록 하는 것이며 asp-for는 Index의 behind에서 정의한 phoneBook으로 값이 바인딩될 수 있도록 합니다. 그리고 위에서 Data로 추가한 DataPhoneBook에서 데이터를 가져오는 방식으로 같이 변경하도록 합니다.
프로젝트를 실행한 후 아래와 같이 입력하고
submit 버튼을 누르면 정상적으로 Phonebook이 추가되었음을 확인할 수 있습니다.
Names값은 프로젝트를 종료하면 다시 초기화 됩니다.
● DB 사용하기
위 예제에서와 같이 단순 List를 사용하는 것이 아닌 실제 DB구성을 통해서 이전 예제와 동일하게 데이터를 불러오고 추가하는 방법을 살펴보도록 하겠습니다.
우선 기존 프로젝트에서 Data와 Models에 만들었던 파일을 삭제하고 Index.cshtml과 Index.cshtml.cs파일의 내용을 아래와 같이 초기화 합니다.
@page
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@model MyWebProject.Pages.IndexModel
@{
}
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace MyWebProject.Pages
{
public class IndexModel : PageModel
{
public void OnGet()
{
}
}
}
새로운 예제를 위해 MS SQL Server(2019)를 사용할 예정이며 가정먼저 아래 글을 참고하여 서버설치를 진행합니다.
2022.02.23 - [Server/SQL Server] - [MSSQL] MS SQL Server 다운로드및 설치/설정
설치가 완료되었으면 SSMS(Microsoft SQL Server Management Studio)를 실행해 필요한 DB와 Table을 생성합니다. DB는 Northwind를 사용할 예정이며 아래 파일을 받아 모든 쿼리를 SSMS에 붙여넣은뒤 실행하면 Northwind DB를 생성할 수 있습니다.
DB제어에는 Entity Framework Core를 사용할 것이므로 NuGet에서 관련 패키지를 내려받아 설치합니다.
DB의 Table을 빠르게 모델화 하기 위해 Visual Studio Extention 도구인 'EF Core Power Tools'를 사용하려고 합니다. Visual Studio 2022의 'Extentions'메뉴에서 'Manage Extentions'를 선택한뒤 'EF Core Power Tools'를 검색해 설치합니다.
설치가 완료되었으면 다시 Visual Studio 2022를 실행한뒤 프로젝트에서 마우스 오른쪽 버튼을 눌러 'EF Core Power Tools -> Reverse Engineer'를 선택합니다.
처음 화면에서 'Add'버튼을 눌러 서버접속에 필요한 알맞은 정보를 입력하고
Northwind를 선택한뒤 'OK'버튼을 눌러줍니다. 그리고 아래와 같이 Northwind가 선택되어 있으면
그대로 OK버튼을 누릅니다.
보시는 것처럼 테이블이나 프로시저, 뷰등 DB상에 필요한 요소를 가져올 수 있는데 예제에서는 우선 모든 Table과 Procedure를 선택하도록 하겠습니다.
위와 같은 설정화면에서는 EntityTypes path에 Data를 입력해 프로젝트에 만들어둔 Data폴더에 Entity 객체를 생성하도록 하고 'Include connection string in generated code'를 선택하여 연결문자열을 포함하도록 하였습니다.
아무런 문제가 없으면 다음과 같이 Table및 Procedure와 관련된 파일들이 생성된것을 확인할 수 있습니다.
만들어진 파일중 NorthwindContext.cs의 OnConfiguring 메서드를 보면 연결문자열이 다음과 같이 표시되는데
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
optionsBuilder.UseSqlServer("Data Source=192.168.1.153;Initial Catalog=Northwind;Persist Security Info=True;User ID=sa;Password=477859@1");
}
}
해당 연결문자열을 참고하여 DbContextExtensions.cs에 아래와 같은 확장메서드를 추가합니다.
public static IServiceCollection AddNorthwindContext(this IServiceCollection services, string connectionString = "Data Source=192.168.1.153;Initial Catalog=Northwind;Persist Security Info=True;User ID=sa;Password=477859@1")
{
services.AddDbContext<NorthwindContext>(options => options.UseSqlServer(connectionString));
Configuration = builder.Build();
return services;
}
이 확장 메서드는 프로젝트에 의존성서비스를 추가하기 위한 것입니다. 그리고 NorthwindContext.cs의 OnConfiguring메서드는 삭제합니다.
이쯤에서 한가지 주의가 필요한건 'EF Core Power Tools'는 기본적으로 한번 생성한 파일을 그대로 보존하지 않습니다. 이 말은 한번 파일을 생성한 뒤 DB상의 어떠한 변화(테이블 추가, 프로시저 변경등..)로 인해 새롭게 DB클래스 파일을 생성을 위해서 'EF Core Power Tools'를 다시 사용하면 기존의 파일은 모두 삭제하고 새롭게 모든 파일을 다시 생성한다는 것을 의미합니다.
따라서 위에서 처럼 임의로 확장메서드를 추가하는 등의 수정사항은 'EF Core Power Tools'을 다시 사용하면 삭제되므로 다시 변경사항을 일일이 적용하기 보다는 다른 대체적인 방안을 찾아서 처리해야 한다는 점을 말씀드립니다. 대게는 별도의 폴더를 만들어 내부에 동일한 이름의 partial 클래스를 만들고 해당 클래스파일 안에 수정사항을 적용하는 방법을 사용하기도 합니다.
새롭게 추가한 확장메서드는 connectionString매개변수를 통해 DB접속과 관련한 문자열 정보를 전달하고 있는데 이상태로 사용하기 보다는 DB접속정보는 별도의 설정으로 분리하기를 권장합니다. 따라서 우선 프로젝트의 appsettins.json에 아래와 같이 DB연결 관련 정보를 추가한뒤
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"NorthwindConnectionString": "Server=192.168.1.153;user id=sa;password=c477859@1;Database=Northwind",
},
확장 메서드에서는 위의 설정정보만을 가져오도록 변경합니다.
public static IConfigurationRoot? Configuration { get; set; }
public static IServiceCollection AddNorthwindContext(this IServiceCollection services)
{
var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json");
Configuration = builder.Build();
services.AddDbContext<NorthwindContext>(options => options.UseSqlServer(Configuration.GetConnectionString("NorthwindConnectionString")));
return services;
}
참고로 IConfigurationRoot사용을 위해서는 프로젝트에 'Microsoft.Extensions.Configuration.Json' Nuget package가 설치되어 있어야 합니다.
이어서 프로젝트의 Program.cs파일을 수정해 다음과 같이 서비스를 추가합니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddNorthwindContext();
이제 Index.cshtml.cs에서는 생성자주입을 통하여 NorthwindContext의 객체를 가져올 수 있도록 하면 Northwind를 사용할 준비는 완료된 것입니다.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using MyWebProject.Data;
namespace MyWebProject.Pages
{
public class IndexModel : PageModel
{
private NorthwindContext DB;
public IndexModel(NorthwindContext context)
{
DB = context;
}
public void OnGet()
{
}
}
}
정상적으로 동작하는것을 확인하기 위해 Products테이블의 ProjectName 컬럼을 가져와 보여주는 화면을 만들어 보겠습니다.
Index.cshtml.cs에서 아래와 같이 Products List를 만들고 OnGet()에서 Products의 ProductName을 가져와 Products List에 담아두도록 합니다.
namespace MyWebProject.Pages
{
public class IndexModel : PageModel
{
private NorthwindContext DB;
public IEnumerable<string>? Products { get; set; }
public IndexModel(NorthwindContext context)
{
DB = context;
}
public void OnGet()
{
Products = DB.Products.Select(x => x.ProductName).ToList();
}
}
}
Index.cshtml에서는 Products의 값을 가져와 보여줄 수 있도록 합니다.
@page
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@model MyWebProject.Pages.IndexModel
@{
}
<ol>
@if (Model.Products is not null) {
@foreach(var item in Model.Products) {
<li>@item</li>
}
}
</ol>
프로젝트를 실행하면 다음과 같이 정상적으로 페이지가 표시됨을 확인할 수 있습니다.
만약 behind 파일이 없는 독립적인 Razer Page를 사용하는 경우에는 @inject 지시문을 통해 의존성주입을 구현할 수 있습니다.
@page
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@model MyWebProject.Pages.IndexModel
@using MyWebProject.Data
@inject NorthwindContext DB
@{
}
<p>@DB.Products.Where(x => x.ProductId == 1).Select(x => x.ProductName).SingleOrDefault()</p>
<ol>
@if (Model.Products is not null) {
@foreach(var item in Model.Products) {
<li>@item</li>
}
}
</ol>
13. partial view
partial view를 사용하면 Razor Page내부에서 공통된 영역을 따로 분리할 수 있습니다. 이 분리된 partial view는 이 후 다른 Razor Page안에서 공통적으로 사용될 수 있습니다.
partial view를 사용해 보기 위해 우선 Products의 모든 컬럼을 가져올 수 있도록 기존 Project의 Index를 다음과 같이 수정합니다.
public class IndexModel : PageModel
{
private NorthwindContext DB;
public List<MyWebProject.Data.Products>? Products { get; set; }
public IndexModel(NorthwindContext context)
{
DB = context;
}
public void OnGet()
{
Products = DB.Products.ToList();
}
}
그리고 프로젝트의 Shared폴더안에서는 아래와 같이 _Product.cshtml 페이지를 추가해
Northwind의 Products테이블에서 ID와 Name을 표시할 수 있도록 View를 수정합니다. parital view의 이름은 _로 시작해 직접 페이지를 호출할 수 없도록 하며 Shared폴더안에 partial view를 만들어 두면 따로 경로를 명시하지 않고 partial view페이지의 이름만 지정해도 알아서 해당 파일을 찾아 사용될 수 있도록 해줍니다.
@model List<MyWebProject.Data.Products>?
@{
}
<table border="1">
<thead>
<tr>ID</tr>
<tr>Name</tr>
</thead>
<tbody>
@if (Model is not null)
{
@foreach(var item in Model)
{
<tr>
<td>@item.ProductId</td>
<td>@item.ProductName</td>
</tr>
}
}
</tbody>
</table>
그리고 Index view에서는 위에서 추가한 _Product.cshtml을 partial view로 참조하기 위해 parital 태그헬퍼를 추가합니다.
@page
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@model MyWebProject.Pages.IndexModel
@{
}
<partial name="_Product" model="Model.Products" />
만약 다른 페이지에서 동일하게 Product의 ID와 Name을 표시해야 한다면 위와 동일하게 분리된 _Product의 partial view를 사용해 필요한 기능을 구현할 수 있습니다.
14. services 와 HTTP 요청 pipeline 설정
● endpoint routing
ASP.NET Core 2.2에서 소개된 endpoint routing은 이전의 IRouter에서 교체된것으로 라우팅이 필요한 프레임워크(예: Razor Pages, MVC 또는 Web API)와 라우팅이 인증, 권한 부여, CORS 등과 같은 영향을 미치는 방식을 이해해야 하는 미들웨어 간의 상호 운용성을 개선하도록 설계되었습니다. 이에 따라 라우팅과 action method 선택에 대한 성능이 크게 향상되었습니다.
endpoint routing은 ASP.NET Core 2.2부터 기본적으로 작동하도록 되어 있으며 기존 MapRoute 메서드나 속성에 의해 등록된 route는 이 새로운 endpoint routing으로 매핑됩니다. 또한 HttpContext를 필요로 하지 않는 의존성 서비스에 의해 등록된 링크 생성 서비스를 포함하고 있습니다.
endpoint routing은 UseRouting과 UseEndpoints이 2개의 메서드 호출을 필요로 하는데 UseRouting은 라우팅이 결정되는 곳의 pipeline위치를 표시하며 UseEndpoints는 endpoint가 실행되도록 선택된 곳의 pipeline위치를 표시합니다. localization와 같은 미들웨어는 이들 Method사이에서 선택된 endpoint를 볼 수 있으며 필요하다면 다른 endpoint로 전환할 수 있습니다.
endpoint routing은 기존 ASP.NET MVC에서 부터 사용한 route 템플릿 구문과 2013년 ASP.NET MVC 5에서 소개된 [Route]속성을 그대로 사용할 수 있습니다. 레거시에서 마이그레이션할때 필요로 하는 것은 단지 Startup의 설정뿐입니다.
MVC controllers나 Razor Pages 혹은 SignalR과 같은 frameworks에서는 UseMvc메서드를 호출함으로서 endpoint routing을 사용할 수 있습니다. 다만 현재 이들은 현재 middleware와 함께 같은 routing system으로 통합되었기에 UseEndpoints메서드 호출에 추가되었습니다.
● services
예제 프로젝트에서 Program.cs파일을 보면 다음과 같이 2개의 서비스가 등록되어 있는걸 확인할 수 있습니다.
builder.Services.AddRazorPages();
builder.Services.AddNorthwindContext();
이들 서비스는 의존성 주입사용을 필요로 하는 공급자가 필요할때 해당 기능을 제공하기 위한 것이며 특히 AddNorthwindContext()는 DB처리에 필요한 객체를 의존성 주입을 통해 가져올 수 있도록 이전 예제에서 구현한 것입니다.
기타 이 밖에 기본적으로 등록가능한 서비스로는 아래와 같은 것이 있습니다.
메서드 | 기능 |
AddMvcCore | 요청을 라우트하고 컨트롤러를 호출하기 위한 최소한의 서비스입니다. |
AddAuthorization | 권한및 인증서비스입니다. |
AddDataAnnotations | MVC data 주석(Annotations) 서비스 입니다. |
AddCacheTagHelper | MVC 캐시 태그 helper 서비스 입니다. |
AddRazorPages | Razor view 엔진을 포함한 Razor Page 서비스이며 추가적으로 아래 Method를 같이 호출합니다. AddMvcCore AddAuthorization AddDataAnnotations AddCacheTagHelper |
AddApiExplorer | Web API explorer 서비스 입니다. |
AddCors | 보안 향상을 위한 CORS 관련 서비스입니다. |
AddFormatterMappings | URL 형식과 해당 미디어 유형 간의 매핑입니다 |
AddControllers | Controller 서비스이며 일반적으로 ASP.NET Core Web API에 사용됩니다. 또한 아래 메서드를 추가로 호출합니다. AddMvcCore AddAuthorization AddDataAnnotations AddCacheTagHelper AddApiExplorer AddCors AddFormatterMappings |
AddViews | .cshtml 뷰를 지원합니다. |
AddRazorViewEngine | @문자처리를 포함하여 Razor view 엔진을 지원합니다. |
AddControllersWithViews | 컨트롤러, 뷰에 대한 서비스이며 일반적으로 ASP.NET Core MVC 프로젝트에 사용됩니다. 또한 아래 메서드를 추가로 호출합니다. AddMvcCore AddAuthorization AddDataAnnotations AddCacheTagHelper AddApiExplorer AddCors AddFormatterMappings AddViews AddRazorViewEngine |
AddMvc | AddControllersWithViews와 비슷하지만 이전버전과의 호환성을 위해서만 사용됩니다. |
AddDbContext<T> | DbContext를 위해 사용됩니다. |
● HTTP 요청 pipeline 설정
Configure 메서드는 처리를 수행한 후 응답을 직접 반환하거나 파이프라인의 다음 위임자에게 처리를 전달하도록 결정할 수 있는 연결된 위임자 시퀀스로 구성된 HTTP 요청 pipeline을 구성하며 돌아오는 응답도 변경될 수 있습니다.
delegate는 delegate연결을 구현할 수 있는 method를 구현하는데 HTTP 요청 pipeline은 다음과 같이 간략하게 구현될 수 있습니다.
public delegate Task RequestDelegate(HttpContext context);
매개변수가 HttpContext임에 주목해 주세요. 이를 통해 URL 경로, 쿼리 문자열 매개 변수, 쿠키 및 사용자 에이전트를 포함하여 수신 HTTP 요청을 처리하는 데 필요한 모든 항목에 액세스할 수 있습니다.
이러한 delegate는 종종 브라우저 클라이언트와 웹사이트 혹은 웹서비스사이에 있어서 미들웨어라고 불리기도 하며 아래 메서드중 하나 혹은 자신을 호출하는 사용자 지정 메서드를 통해서 설정될 수 있습니다.
Run | 다음 미들웨어를 호출하는 대신 즉각적으로 응답을 반환함으로서 pipeline을 종료하는 미들웨어를 추가합니다. |
Map | URL에 기반한 요청과 일치하는 경우 pipeline의 분기를 생성하는 미들웨어를 추가합니다. |
Use | pipeline의 일부를 구성하므로 pipeline에서 다음 delegate에 요청을 전달할 것인지 혹은 요청과 응답을 다음 delegate전/후에 변경할 수 있습니다. |
이들 대부분의 확장 메서드는 pipeline을 구성하기 쉽게 만들 수 있습니다. 예를 들어 UseMiddleware<T>의 경우 T는 다음 pipeline으로 전달할 수 있는 RequestDelegate 매개변수를 가진 생성자 혹은 HttpContext 매개변수와 Task를 반환하는 Invoke method를 지정할 수 있습니다.
● 미들웨어(middleware)
예제 프로젝트의 Program.cs를 보면 app.UseHsts() 와 같이 여러가지 미들웨어가 사용중인것을 볼 수 있습니다. 프로젝트에서 사용가능한 미들웨어는 여러가지가 있지만 그 중 기본적인것으로 다음과 같은 것들이 있습니다.
UseDeveloperExceptionPage() | pipeline및 HTML에러 응답에 대한 동기및 비동기 예외 인스턴스를 캡쳐합니다. |
UseHsts() | 엄격한 전송 보안 헤더를 추가하는 HSTS사용을 위한 미들웨어를 추가합니다. |
UseRouting() | 라우팅이 결정되고 요청이 처리되는 UseEndpoint호출이 결합되어야 하는 pipeline의 지점을 정의하는 미들웨어를 추가합니다. |
UseHttpsRedirection() | HTTP요청을 HTTPS로 리다이렉트 하기위한 미들웨어를 추가합니다. |
UseDefaultFiles() | Index.html과 같은 기본페이지를 사용하기 위한 미들웨어를 추가합니다. |
UseStaticFiles() | wwwroot의 정적파일을 응답하기 위한 미들웨어를 추가합니다. |
UseEndpoints() | 파이프라인이전에 응답을 생성하기 위해 실행할 미들웨어를 추가하며 2개의 endpoint가 추가됩니다. - MapRazorPages : 요청 URL로 /Pages폴더에 있는 Razor Page 파일명을 찾을 수 있도록 하며 이에 대한 응답을 생성하는 미들웨어를 추가합니다. - MapGet : 요청 URL로 inline delegate를 찾을 수 있도록 하며 HTTP응답을 직접적으로 만들어 내는 미들웨어를 추가합니다. |
UseRouting()과 UseEndpoints()메서드는 항상 같이 사용되어야 합니다. 비록 UseEndpoints()에서 특정 URL요청에 대한 route를 찾을 수 있도록 정의되었다 하더라도 수신 HTTP 요청 URL 경로가 일치하는지 여부 및 실행할 endpoint에 대한 결정은 pipeline의 UseRouting지점에서 이루어집니다.
● 익명 inline delegate의 middleware 구현
delegate는 inline 익명 method로 정의될 수 있습니다. 아래 예제는 endpoints에 대한 라우팅이 만들어진 후 pipeline에 연결될 delegate를 등록하는 것입니다.
app.Use(async (HttpContext context, Func<Task> next) =>
{
RouteEndpoint? rep = context.GetEndpoint() as RouteEndpoint;
if (rep is not null)
{
await context.Response.WriteAsync($"{rep.DisplayName} - {rep.RoutePattern.RawText}");
return;
}
if (context.Request.Path == "/test")
{
await context.Response.WriteAsync("test");
return;
}
await next();
});
위 예제는 UseRouting()를 호출한 후 그리고 UseHttpsRedirection()메서드를 호출하기 전에 구현되어야 하며 선택된 endpoint를 출력하며 /test와 같은 지정된 route를 제어합니다. 따라서 요청 URL이 route와 일치하면 더 이상 pipeline을 호출하지 않고 test라는 문자열을 응답할 것입니다.
'.NET > ASP.NET' 카테고리의 다른 글
[ASP.NET Core] MVC패턴 웹프로젝트 만들기 (0) | 2022.03.04 |
---|---|
[ASP.NET Core] HttpContext.User (0) | 2022.02.10 |
[ASP.NET Core] Session (0) | 2021.11.14 |
[ASP.NET Core] Logging (0) | 2021.11.13 |
[ASP.NET Core] Claim 인증과 권한 (0) | 2021.11.11 |