반응형

Action 반환 시리즈는 기초단원에서 1 - 웹페이지를 반환하는 가장 기초적인 View(), 2 - 다른페이지로 넘기는 RedirectToAction()과 Redirect()를 다뤘었습니다. 중급에서는 웹페이지와 REST API에서 공통적으로 사용할 수 있는 IActionResult 일체와 REST API에 특화된 ActionResult를 다루겠습니다.

 

 

 

사실 1,2에서 다뤘던 View나 Redirect류도 IActionResult 리턴이 가능합니다만, 3을 분리한 이유는 HTTP Status code에 대한 이해가 더 자세히 필요하다고 판단해서 나눴습니다. 참고로 이전글 예제에 보면 메소드의 반환 타입이 Task< IActionResult> 또는 IActionResult 입니다. Task는 비동기 반환 값으로 개념자체는 이글의 범위를 벗어나기 때문에 크게 다루지 않습니다. 

 

 

1. IActionResult와 ActionResult 의 차이

ActionResult에는 제네릭으로 ActionResult<T> 타입을 지원합니다. 따라서 T에 기입한 타입으로 반환할 수 있습니다. 

 

2. IActionResult와 ActionResult 공통점

 

Http Status Code 함수로의 반환이 가능합니다. 종류가 워낙많기 때문에 대표적인 것 몇가지만 본문에서 작성하겠습니다.

 

BadRequest(), NotFound(), Ok(), Created(), Unauthorized()

 

이 함수들은 모델을 포함하여 반환할 수도 있습니다. 

 

 

위 스크린샷 예제 하단에서 보듯이 StatusCode를 Enum Type으로 넘겨줄 수도 있습니다. StatusCode($"Status{http status code}{http status description}" , 모델) 형태로 반환이 가능합니다.

 

반환가능한 Result의 종류도 더 있습니다. 아래에는 함수가 아닌 클래스 이기 때문에 return new xxxx() 형태로 반환해주어야 하는 것들 중 일부를 가져왔습니다.

 

ContentResult - 반환할 문자열, 반환 타입, status code를 기입할 수 있습니다.

 

JsonResult - ContentType이 자동으로 application/json이라고 보시면 되겠습니다. 문자열 또는 문자열과 시리얼라이즈시의 옵션으로 사용할 수 있습니다.status code또한 변경할 수 있습니다. 위 ContentResult예제와 아래의 JsonResult예제가 서로 동일한 결과를 반환한다고 보시면 되겠습니다.

 

이상 IActionResult와 ActionResult에 대해 알아보았습니다. 타입과 status code가 다양하게 반환될 수록 설계에 고민이 많으실텐데 가장 간단한 방법부터 사용해보시는 것을 권장드립니다.

반응형
반응형

기초 6편에서 Controller 반환 타입 중 웹 페이지로 이동하는 View() 반환과 모델 전달하는 다양한 방법들을 소개시켜드렸었죠. 오늘까지는 웹 반환 형식으로 RedirectToAction를 간단하게 소개시켜드리고 기초를 마무리 하고자 합니다. 

 

RedirectToAction()은 다른 컨트롤러 액션 메소드를 타게 하는 기능을 합니다. 컨트롤러 진입 url 엔드포인트와 결과화면의 url이 다른경우에는 차이가 생기게 됩니다. 결과화면으로 바로 넘기는 액션함수는 Redirect가 있습니다.

 

페이지에서 400, 401, 500 등 에러가 발생했을 때 처리방법은 여러가지가 있습니다만, 간단한 예제 중에 에러 발생만 한게 없을 것 같아서 예제를 준비해보았습니다.

 

public async Task<IActionResult> Page1()
{
	try
    {
    	var 아무값 = 아무함수();
        return View(아무값);
    }
    catch (Exception e)
    {
        return RedirectToAction("ErrorPage", new { id = 500 });
    }
}


public IActionResult ErrorPage(string id) => 
	Redirect($"{_configuration["에러url"]}/error/{id}");

 

사실 에러페이지를 넘기는 것은 이렇게 하나하나 반영해주기 보다는 program.cs에서 app.UseStatusCodePagesWithReExecute("/error/{0}"); 형태로 남기는것이 적합하고, .NET8.0이상부터는 IExceptionHandler를 활용한 분기도 탁월합니다. 그러나 이것이 아니더라도 기본적으로 asp.net core에 내장되어있는 app.UseExceptionHandler("/Home/Error")로 넘기는 것도 가능합니다만, 기초 예제로 간단히 떠오른 것을 바로 작성하다보니도 그렇고 Redirect를 이용해서 외부에 다른페이지로 넘기는 것을 함께 보여드리면 좋을 것 같아서 고안했습니다.

 

 

기초 6편에서 배운 것처럼 아무런 이상이 없다면 View와 아무값이라는 모델을 뷰로 보내줄 수 있었죠. 그런데 이 과정에서 에러가 생길때 그리고 에러의 종류마다 다른 에러 페이지를 보여주고 싶을때 위 소스처럼 사용해보실 수 있습니다.

 

vs code에서 컨트롤 클릭을 해보면 RedirectToAction에는 오버라이드가 7가지가 있습니다.

 

그중 대표적으로 설명드릴 것이 몇가지 있는데요.

 

 

1. RedirectToAction(action이름, controller이름, 라우터 value, 쿼리스트링에 사용할 object)

   =>   /controller이름/action이름/라우터밸류?object명=object값 형태로 리다이렉트 됩니다.

 

2. RedirectToAction(action이름, controller이름 , 라우터 value )

   => /controller이름/action이름/라우터밸류 형태로 리다이렉트 됩니다. 따로 쿼리스트링으로 전달할 값이 없을때 사용합니다.

 

3. RedirectToAction(action이름/라우터밸류 )

   => 함수를 호출하는 위치에서 동일한 controller 내 다른 action에 redirect하려고 할때 사용합니다. /action이름/라우터밸류 형태로 리다이렉트 됩니다.

 

4. RedirectToAction(action이름)

    => 3번과 마찬가지로 함수를 호출하는 위치에서 동일한 controller 내 다른 action에 redirect하려고 할때 사용합니다. 

 

 

 

 

Redirect는 다른 주소의 페이지 화면으로 이동하는 기능입니다. RedirectToAction과 다르게 외부 url을 사용해도 됩니다.(외부 페이지로 넘어가게 됩니다.)

 

위 소스 중 아래 부분에서 _configuration["에러url"] 부분이 이전에 설명드렸던 appsetting에서 값을 읽어오는 Dictionary 역할을 하는 configuration 내에 "에러url"로 매핑된 값을 가져오는 것으로 외부 url이어도 무방하기 때문에 변수로 가져와서 적었습니다.

public IActionResult ErrorPage(string id) => 
	Redirect($"{_configuration["에러url"]}/error/{id}");

 

 

 

정리하자면 RedirectToAction는 웹 애플리케이션 내부에 다른 액션메소드를 타게하기 위해 간편하게 만들어져 웹의 주소가 변경되더라도 소스에 손댈필요가 없는 그런 함수 입니다.

 

Redirect는 내부 페이지를 사용해도되지만, base address는 서버의 설정에 따라 바뀔 수 있는 값이므로 내부페이지에는 제 기준 권장하진 않습니다만(dns가 변경되면 소스를 수정해야하기 때문에.), 외부 페이지로 이동시킬 때 주로 사용합니다. 특히 제 예제처럼 사용하는 경우, 외부페이지의 주소가 변경되더라도 설정파일만 수정하면 빌드, 호스트 경로 위치로 복사 등이 전혀 필요 없게 됩니다. -> 내부 페이지도 설정파일로 하면안되나요? 하실 수 있지만 RedirectToAction을 사용하면 에초에 변경할 필요가 없습니다.

 

 

이상으로 RedirectToAction과 Redirect 반환에 대해서 알아보았습니다. 기초 파트였지만 마지막이었기 때문에 조금 이해할 부분이 필요했던 것 같습니다. 다음 asp.net core의 포스트 부터는 중급으로서 Api Controller를 포함한 반환 값 [IActionResult와 ActionResult , IResult와 Result, 사용자 지정 개체타입]과 쿠키와 세션, 다른 Rest API 사용법 정도를 다룰 예정입니다. 고급에서는 미들웨어, 예외처리, 액션필터, Cors/Origin 이외에도 떠오른 것이 있으면 작성해보겠습니다.

 

 

반응형
반응형

오늘은 컨트롤러 내 액션메소드의 반환값에 대해 알아보겠습니다.

 

이전 asp.net core mvc 기초 시리즈들을 정주행하신 독자분들이라면 기본적으로 아래 두 사항이 당연하게 인식되고 있으시리라 믿습니다.

 

 

1. HomeController 내 액션함수 Private()에서 반환값이  View() 인 경우 

프로젝트 하위의 Views/Home/Privacy.cshtml 페이지로 넘어가게 됩니다.

 

2. HomeController 내 액션함수 Abc()에서 반환값이  View("Privacy") 인 경우

[HttpGet("[action]")]
    public IActionResult Privacy()
    {
        return View();
    }

    [HttpGet("custom_route")]
    public IActionResult Abc()
    {
        return View("Privacy");
    }

프로젝트 하위의 Views/Home 폴더 내에서 확장자를 제외한 파일명이 Privacy .cshtml 페이지를 찾아 거기로 넘어갑니다.

View()내에 string으로 작성하면 위 예시인 경우 컨트롤러가 가리키는 Views내 폴더 내에서 파일을 찾습니다. 만약에 Views/Home외에 다른 경로를 가리키고 싶다면 두가지 방법이 있습니다. 

 

a. 절대경로 ->   return View("Views/Route/Home.cshtml"); 형태로 반환합니다.

b. 상대경로 ->   return View("../Route/Index");

 

 

 

3. HomeController 내 액션함수 Index()에서 반환값이  View(Model) 인 경우 

프로젝트 하위의 Views/Home/Privacy.cshtml 페이지로 Model과 함께 넘어가게 되어 프론트단에서 모델을 받아올 수 있습니다. 모델은 기본자료형, 선언한 클래스의 인스턴스 모두 올 수 있습니다.

 

[  Views/Home/Privacy.cshtml 파일 소스 예제]

@{
    ViewData["Title"] = "Privacy Policy";
    @model int? 
}
<h1>@ViewData["Title"]</h1>

<p>Use this page to detail your site's privacy policy. Route</p>
@if (Model != null)
{
    <p>받은값: @Model</p>
}

 

가령 위와 같이 넘어가는 페이지 상단에서 @model <받아온 모델의 자료형> 을 하게되면 model 또는 model.속성 으로 자료형을 뷰에서도 자유롭게 사용할 수 있습니다.

 

4. 2번과 3번의 구분

2번에 string 값을 넣었어서 다른 페이지로 이동했고 3번에 모델을 넣어서 페이지로 이동하며 모델을 가져갔는데, string으로 반환하면 사실 두개가 겹치는 것은 아닌지?

 

Controller 추상 클래스 설명입니다.

 

이 구성을 보시면 string으로 반환하게 되면 viewName으로 인식합니다.

 

모델을 반환하고 싶을때 위에 따른 기본적인 방법은  string이 아닌 return View((object)"메시지 문자열"); 형태로 object 캐스팅을 하는 것입니다. 다른 방법은 named parameter를 사용하는 것입니다. named parameter라는 용어를 asp.net core에서도 사용하는지 모르겠네요. 아래와 같이 View(model: 모델); 로 반환해줍니다.

[HttpGet]
    public IActionResult Privacy()
    {
        return View(model: "모델입니다.");
    }

 

 

5. 기본적으로 매핑되는 뷰 말고 다른 페이지를 열면서 모델도 반환하고 싶을때는 View(뷰경로, 모델)을 반환합니다.

return View("Orders", Orders);

 

 

6. 모델을 뷰로 전달하는 방법은 모델 반환 외에도 있습니다. ViewData와 ViewBag입니다. 간단히 예제로 남기겠습니다.

 

먼저 ViewData입니다.

public IActionResult SomeAction()
{
    ViewData["Greeting"] = "Hi";
    ViewData["Address"]  = new Address()
    {
        Name = "Pichen",
        Street = "456 Street",
        City = "Seoul",
        PostalCode = "12345"
    };

    return View();
}

 

뷰에서는 아래와 같이 받습니다. object로 넘어가기 때문에 Address로 casting 해줘서 받고 활용할 수 있습니다. 

@{
    var address = ViewData["Address"] as Address;
}

@ViewData["Greeting"] World!

<address>
    @address.Name<br>
    @address.Street<br>
    @address.City @address.PostalCode
</address>

 

 

다음은 ViewBag입니다.

 

 

public IActionResult SomeAction()
{
    ViewBag.Greeting = "Hi";
    ViewBag.Address  = new Address()
    {
        Name = "Pichen",
        Street = "456 Street",
        City = "Seoul",
        PostalCode = "12345"
    };

    return View();
}​
@ViewBag.Greeting World!

<address>
    @ViewBag.address.Name<br>
    @ViewBag.address.Street<br>
    @ViewBag.address.City @ViewBag.address.PostalCode
</address>

 

ViewData와 ViewBag은 동시에 같이 사용하는 것도 당연히 가능합니다.

 

 

오늘을 View()와 모델을 뷰에 전달하는 방법에 대해 알아보았습니다. 액션메소드의 반환값들을 모두 정리할까 싶었는데 분량이 길것같아 나머지는 추후 포스트에서 다루겠습니다.

 

 

반응형
반응형

백엔드에 라우팅 방식에는 HTTP Method의 한정이 필요하겠죠. 생각나는 이유에는 일단 두가지가 있네요. 첫째는 허용하지 않은 Method의 진입을 차단하는 것. 그리고 같은 라우트라도 메소드별로 다른 기능을 타기 위해서입니다. 1편에서 다루었던 Route 속성 방식은 모든 메소드로 진입이 가능해서 http method에 따라 결과가 바뀌지 않는 상황에는 쓸 수 있겠지만 RESTful하려면 get, post, patch, put, delete 등을 나눠서 같은 경로로 라우팅하는 것이 중요할 수 있고, 보안 등 여러 측면을 고려해서도 진입경로를 최소화하는 것이 좋을 것입니다. 그렇다면 어떻게 http method를 한정할 수 있는지 보여드리도록 하겠습니다.

 

 

먼저 Convention-based가 아니기 때문에 controller에도 Route를 걸어줄 것입니다. 라우트 경로에 [controller]라고 쓰게되면 해당부분에 클래스명 중 Controller 앞부분을 가져옵니다. 만약에 controller 클래스의 이름이 Controller로 끝나지 않는다면 클래스명 자체를 라우트 경로에 가져옵니다.

 

아래 스크린샷예제에서는 컨트롤러 이름이 RouteController이기 때문에 Route까지만 라우팅되겠습니다.

 

1편에서 사용했던 Route속성 과 함께 허용할 Http Method만 써주면됩니다. 위 예제에서는 Get방식만 허용해주었습니다. 결과는 아래와 같이 나옵니다. 

 

post방식으로 호출이 안되는지 살펴볼까요? postman으로 확인해보겠습니다. post방식을 살펴보기에 앞서 get방식은 어떻게 표출되는지 먼저 보여드리겠습니다.

 

아래 스크린샷은 포스트맨에서 get방식으로 호출한 예제입니다. 주소를 복사 붙여넣기 하는 과정에서 한글부분은 자동으로 url에서 읽을 수 있도록 유니코드 형식으로 변환됐구요. 아래 result 중에서도 html 코드를 세세히 봐야 같은 페이지인지 아실 것 같아서 Preview로 보여드렸습니다.

 

 

get방식은 잘 되네요. post 방식으로 요청해볼까요? 상단 네모박스보시면 post로 요청된 것을 확인하실 수 있고, 결과는 405 입니다. Method Not Allowed죠.

 

두개 이상의 http method를 허용하려면 단순히 속성하나를 더붙이면 됩니다.

 

 

[HttpPost]
[HttpGet]
[Route("내가만든/액션경로")]
public IActionResult Privacy()
{
    return View();
}

 

이제 post 방식으로도 접근이 가능합니다.

 

또는 

아래와 같이 httpPost, HttpGet 속성 대신에 AcceptVerbs 속성을 이용하는 방법도 있습니다.

//[HttpPost]
//[HttpGet]
[AcceptVerbs("GET", "POST")]

 

 

 

 

Http Method를 한정하는 라우트방식은 스프링에서도 route같은 애너테이션이 있고, httpget에 바로 라우트경로를 붙여주는 애너테이션도 있는데요, asp.net core에서도 마찬가지 방식도 가능하답니다. 여태까지 진행 했던 다른 속성은 주석처리 하고 HttpGet("라우팅 경로") 방식으로 작성하면 됩니다.

//[HttpPost]
[HttpGet("privacy-get")]
//[AcceptVerbs("GET", "POST")]
//[Route("내가만든/액션경로")]
public IActionResult Privacy()
{
    return View();
}

 

RESTful 하게 만드려면 아래와 같이 조회하는 경로와, 추가, 삭제하는 경로가 같으면서도 뒤에 변수만 차이가 있으면 이쁘게나오겠죠. 근데 여기서는 기본 템플릿으로 간단예제를 구현하느라 FindbyId로 get에서 변수받기, post 방식을 request body form으로 받아오기까지는 하지 않고, 복습차원에서 privacy라는 asp.net core의 기본 페이지 이기 때문에 static 변수 하나를 선언해서 /route/privacy/숫자 에서 숫자를 누적시켜보도록하겠습니다.

[Route("[controller]")]
public class RouteController(ILogger<RouteController> logger) : Controller
{  
    private readonly ILogger<RouteController> _logger = logger;
    private static int _sum = 0;

    public IActionResult Index(int? id)
    {
        return View(id);
    }

    [HttpGet("[action]")]
    public IActionResult Privacy()
    {
        return View();
    }

    [HttpGet("Privacy/{id}")]
    public IActionResult AddPrivacy(int? id)
    {
        if (id != null)
            _sum = _sum + (int)id;
        return View(_sum);
    }
}

 

위와 같이 개발했을때는 AddPrivacy에 매핑되는 Views/Route/AddPrivacy.cshtml을 생성해야합니다. 만약에 기본적인 웹파일경로와 다른 위치에 파일을 생성하려면 View("~/Views/지정경로")로 작성하시면 되겠습니다. 저같은 경우는 asp.net core에 기본적으로 생성되어있던 privacy를 복사해다가 Views/Route/AddPrivacy.cshtml로 만든 뒤 RouteController의 sum 값을 표출되도록 하였습니다.

 

브라우저 접근은 기본적으로 get방식이므로 /route/privacy는 접속이 잘되고,

이 라우팅 경로를 postman에서 post로 호출하면 405에러가 나오는 것을 확인하실 수 있습니다.

 

 

 

반대로, post 방식으로 라우팅했던 /route/privacy/{id} 경로는 브라우저에서 405에러가 납니다.

 

postman으로 post방식으로 호출해보면 잘 나오는 것을 알 수 있습니다. 1을 입력하고 9를 입력해볼텐데요. 누적합산 방식으로 받은값에 10이 나와있으면 결과는 성공입니다. 먼저 1입력.

 

결과가 10으로 잘된 것을 확인하실 수 있습니다.

반응형
반응형

이번 포스트에서도 asp.net core MVC의 라우트방식에 대해서 설명드릴텐데요. 지난 포스트에서는 기본으로 설정되어있는 라우트 방식인 Convention-based Routing에 대해서 다뤄봤었구요. 이번 포스트에서는 라우트를 커스텀하게 지정할 수 있는 Attribute Routing에 대해서 알아보겠습니다.

 

asp.net core에서 Attribute를 알고 계신가요? 일단 모르시더라도 어떤 형태인지만 보여드리고 넘어갈게요. 그것자체가 아주 커다란 카테고리이기 때문에 본문이 지나치게 길어질 것 같아서요. 자바를 하신적이 있으시다면 스프링에서 애너테이션과 유사한 역할인데 개념자체는 지금 글에서는 모르셔도 무방합니다.

위 이미지에서 [HttpGet]이라고 적힌 부분이 보이시죠? 지금 알고 계셔야하는 부분은 .net에서 함수 위에 대괄호로 [] 쓸 수 있는 것이 Attribute라는 것 뿐이예요. Attribute는 http에 연관된 것 뿐아니라 액션필터 등 메소드 진입 후에 통과될 로직을 작성하고 적용할 수도 있답니다. 자세한건 따로 찾아보시거나 아주 추후에 올리도록 하겠습니다.

 

 

 

Attribute Routing이라고하면, Controller의 Action Method 위에 attribute형태로 작성 지정한 경로로 라우팅을 가능하게 하는 방식이예요.

 

바로 예제로 들어가볼게요. 지난 글에서 설명드린 Convention-based Routing 방식으로 asp.net core mvc의 기본 템플릿 내에서 localhost:<port>/Home/Privacy 라는 페이지가 있어요. 이 부분을 복사해다가 경로를 변경해볼거예요. 먼저 이 페이지를 복사해다가 Route/Privacy에 위 페이지를 만들어볼게요. Views폴더의 Home폴더 내부의 파일들도 Views폴더 내에 Route (controller 앞부분과 동일하게) 라는 폴더를 생성하고 그아래 복사해주세요.

public class RouteController(ILogger<RouteController> logger) : Controller
{  
    private readonly ILogger<RouteController> _logger = logger;

    public IActionResult Index(int? id)
    {
        return View(id);
    }

    public IActionResult Privacy()
    {
        return View();
    }


    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult Error()
    {
        return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    }
}

 

 

그러면 이제 localhost:<port>/route/privacy 경로에 페이지가 보일꺼예요. 

 

간단하게 라우팅 경로를 "내가만든액션경로" 라고 하겠습니다. Attribute의 이름은 Route이고 변수를 string형태로 주시면 됩니다.

[Route("내가만든액션경로")]
public IActionResult Privacy()
{
    return View();
}

아주 쉽죠?

 

액션 메소드별로 경로를 바꿀 수 있는건 좋은데, 2단, 3단경로를 만들고 싶은데요? 라는 의문이 생기실 수 있어요. 간단합니다. route attribute에 그대로 추가하면돼요. "내가만든/액션경로"

 

[Route("내가만든/액션경로")]
public IActionResult Privacy()
{
    return View();
}

 

이것도 참 쉽죠? 

 

여러 경로를 일일히 컨트롤러의 메소드마다 집어넣는것은 너무 번거롭지 않냐는 생각이 드실 수 있어요. asp.net core에서 컨트롤러 자체에도 Route속성을 지원합니다. 컨트롤러에는 [Route("내가컨트롤러다이말이야")]를 붙이고 실행하겠습니다. 결과부터 말씀드리면 "localhost:<port>/내가컨트롤러다이말이야/내가만든/액션경로" 에서 해당페이지에 진입하게 됩니다.

 

 

 

그런데 여기서, controller에 전체적으로 경로 지정하고 싶긴했는데 한두개의 메소드에는 예외적으로 다른 경로를 쓰고 싶을 수 있죠. 결론 먼저 말씀드리면 가능합니다. 우리 개발자들이라면 다 아는 절대경로와 상대경로가 있죠. controller와 method에 둘다 route속성이 있는 경우에 method 부에 상대경로를 기입하면 <웹주소>/<controller의 라우트경로>/<메소드의 라우트경로> 가 되고 method부에 절대경로를 기입하게 되면 <웹주소>/ <메소드의 라우트경로> 가 됩니다.

 

 

아래는 절대경로 예제입니다. 절대경로임을 강조하기 위해 경로 앞 / 부분만 표시해두었습니다.

 

 

결과는 다음과 같죠.

 

여기서 주의하실점은 method 부분의 route경로의 절대/상대경로는 controller부분에 route attribute가 있을때만 성립한다는 점이예요. controller가 route가 없으면 <웹주소>/ 다음에 적을것이 따로 없기 때문에 무조건 웹주소/<메소드의 route경로>가 됩니다. 여기서 헷깔리시는 분들은 기본패턴이 존재했던 convention-base routing 방식과 혼동이 오셔서 그럴텐데, 메소드에 라우트를 입력하는 순간 convention-base routing이 아니게 됩니다.

 

정리해드리겠습니다.

 

아래 표에서 *O는 있음, X는 없음

controller에서 route attribute method에서 route attribute 결과
x x Convention-base attribute로 별도의 설정 수정이 없다면
<server-url>:<port>/{controller}/{action}를 따름
x o method의 route 경로를 따름
<server-url>:<port>/{method의 route경로}

o x 404 에러 발생.
controller에서 라우트 어트리뷰트를 작성한 순간 convention-base attribute의 영향을 받지 않고, 액션 메소드의 경로가 지정되지 않아 라우팅되지 않음
o o <server-url>:<port>/{controller의 route경로}/{method의 라우트경로}

 

이상으로 지난 글에 이어 라우트 방식 두가지에 대해서 알아보았습니다. 

 

감사합니다.

반응형
반응형

이번 포스트에서는 ASP.NET Core MVC에서 라우트 방식에 대해 설명드리겠습니다.

웹을 만들때 어떤 url에 들어갔을때 어떤 페이지를 열리게 할 것이냐에 대한 내용입니다.

 

ASP.NET core MVC에서는 기본적인 라우트 방식이 있습니다. 제가 기본적인 라우트 방식이라고 부른 방식은 asp.net core mvc 프로젝트의 program.cs 내부에 적혀 있습니다.

이 기본 라우트 방식의 pattern을 보시면 controller / action / id? 라고 적혀있죠? 

 

 

이는 각각 controller 클래스의 앞부분, action은 컨트롤러 내 함수의 이름, id는 id라는 변수가 있다면 그것을 나타냅니다.  또한pattern: "{controller=Home}/{action=Index}/{id?}" 이라는 부분에서 = 뒤에 나오는 값은, 해당 위치에 아무것도 없을때 표기할 패턴을 말합니다. 가령, localhost:5000으로 웹을 구동했다고 가정했을때 브라우저에서 localhost:5000로 진입하면, 이 웹 애플리케이션이 자동으로 localhost:5000/Home/Index로 진입하게 됩니다.

 

말로만 하면 무슨 말인지 모르시겠죠? 아래에서 간략하게 소스를 보면서 설명드리겠습니다.

 

 

바로 위 이미지에서 controller클래스의 앞부분이라고 말씀드렸던 "Home"이 패턴에서 {controller}에 해당하는 부분입니다. 그리고 빨간색 네모로 표시해둔 상자 중 아래 것의 부분인  HomeController() 내에 존재하는 Index()함수가 보이시죠? 함수명인 Index가 action 부분입니다. 따라서 브라우저 주소창에 http://localhost:5208/Home/Index를 입력하게 된다면 HomeController() 내에 있는 Index()함수가 호출하는 뷰를 볼 수 있게 되는 것이지요. 여기서는 반환값이 View()이기 때문에 프로젝트 내 폴더구조에서 Views/Home/Index.cshtml을 보게됩니다.

 

여기서, 프로젝트를 생성하고 빌드하신 분들은 사실 localhost:<port>를 입력했을때도 이 화면을 보셨을꺼예요.

 

이게 아까 설명드렸던 뒤에 나오는 값은, 아무것도 없을때 표기할 패턴입니다.  pattern: "{controller=Home}/{action=Index}/{id?}" 에서 localhost:<port> 뒤에 아무것도 없을 때는 Home/Index가 되도록 기본값이 지정되어 있죠. 마찬가지 원리로 localhost:<port>:Home 까지만 치더라도 같은 화면이 표출됩니다.

 

독자분들은 하나의 궁금증이 머릿속에 있으실꺼예요. 컨트롤러 라우트 패턴에 id도 있었는데 id가 어디갔나요? 라고 말이죠.

패턴에 보시면 id뒤에는 ?이 표기되어있죠? c#을 공부한 상태시라면 감은 오실꺼예요. ?은 nullable이라는 것을! 따라서 값이 없어도 됐던 것이죠. 지금은 id를 붙이더라도 설명드렸던대로의 원리로 동일한 화면이 나옵니다.

 

 

 

id값을 인식해서 뭔가 변화를 주고싶다면? 아래와 같이 아주 간단하게 보여드리겠습니다.

 

먼저 id를 받을 수 있도록 Index()에 자료형과 변수를 기입합니다. 변수명은 반드시 패턴과 매핑이 되는 id여야 합니다. id로 기입하지 않아도 받을 방법이 있지만 오늘 포스팅에서 다룰내용은 아니었어서 Route 중급편 정도에서 다루도록 하겠습니다.

 

그리고 뷰에 해당값이 전달될 수 있도록 모델을 반환해줍니다. 여기서는 id값만 전달하면 되므로 별도 클래스의 인스턴스나 컬렉션으로가 아닌 id자체를 반환해줍니다.

 

// HomeController 내 Index()

public IActionResult Index(int? id)
{
    return View(id);
}

 

Controller 에서 View로 반환했기 때문에 뷰쪽에서 Home/Index.cshtml을 찾습니다. Views폴더 내부에서 패턴형식에 맞게 {controller}/{Action}.cshtml를 찾으면 된다고 생각하시면 되겠습니다.

 

아래 소스이미지는 asp.net core MVC의 기본 템플릿에서 빨간 부분을 추가하여 표시한 것입니다.  컨트롤러에서 반환한 Model의 자료형은 int? 였기 때문에 상단의 @{} 문에서 @model을 자료형으로 선언한 뒤 본문에서 Model 변수로 사용하면 되겠습니다. 없을때도 있으므로 null이 아닐때만 표기하도록 하겠습니다.

 

결과는 아래와 같습니다. id값이 null이 아닐때만 표기하도록 개발한 것이라 localhost:<port>만 입력했을때는 이전에 보여드렸던 화면과 동일합니다.

 

이상으로 .net6.0이상의 core mvc 프로젝트에서 기본적으로 세팅되어있는 라우트 방식에 대해 알아보았습니다. 더많은 내용을 담으려는 생각도 있었는데 본문이 이미 너무 긴 것 같아 라우트 커스터마이징, id말고 다른변수 사용하기, 쿼리스트링에서 변수받아오기, post 요청시 request body에서 변수받아오기, 컨트롤러에서 view외에도 자주쓰이는 반환 값의 종류 등은 이후 포스팅에서 다루도록 하겠습니다. 감사합니다.

반응형
반응형

 

설계 유연성, 운영 이후의 유지보수성과 확장성까지 고려하면 의존성 주입은 수많은 웹기술에서 필수라고 할 수 있겠습니다.

spring boot에서는 대표적인 방법 중 하나로 @Component 애너테이션 스프링 컨테이너에 등록 후 컴포넌트스캔으로 구현체를 읽어오는 방식이 있는데, 과연 asp.net core 에서는 의존성 주입을 위한 방법에 어떤 것들이 있을까요?

 

asp.net core에서는 DI 컨테이너를 대변하는 IServiceProvider라는 인터페이스가 있습니다. 그리고 실제 컨테이너 그 자체는 아니지만, 등록된 서비스를 관리하는 IServiceCollection라는 인터페이스가 있습니다. 이부분은 소스부터 보고 말씀드리겠습니다.

 

아래와 같이 인삿말을 하는 interface, 구현체가 있다고 가정하겠습니다.

. - interface

public interface IHelloService
{
    public void Hello(string message = "Hello.");
}

 

 - 구현체

public class HelloService(IConfiguration configuration) : IHelloService
{
    private readonly IConfiguration _configuration = configuration;

    public void Hello(string message)
    {
        Console.WriteLine(message);
    }
}

 

asp.net core에서는 설정/등록은 program.cs 에서 거의 모두 처리한다고 보시면 되는데요, program.cs를 열어봅시다.

var builder = WebApplication.CreateBuilder(args);

위와 같이 builder가 선언되어있고, builder 내부에는 다양한 것들이 모두 포함되어있지만 그중에서도 Services 변수는 아까 설명드렸던 IServiceCollection을 구현한  변수로 여기에 DI을 할 구현체를 등록할 수 있습니다.

 

아래와 같이 말이죠.

builder.Services.AddTransient<IHelloService, HelloService>();

 

의존성 주입을 하는 함수는 AddTransient 를 포함하여 총 3가지가 있으며 상황에 맞게 사용하시면 되겠습니다.

 

1. AddTransient

 - 요청할때마다 새로운 인스턴스 생성 -> 상태를 별도로 보관하지 않고(stateless), 가볍게 생성 후 소멸

 

2. AddScoped

 - http request 단위로 동일한 인스턴스 유지 -> 요청 후 응답까지 공유해야하는 변수(필드, 프로퍼티) 등을 같은 인스턴스에서 사용

 

3. AddSingleton

 - 어플리케이션의 시작부터 종료까지 일관된 인스턴스 공유 - 서버 캐싱, 전역 변수 둥 처럼 매요청시마다 공유가 필요한 서비스에 사용

 

 

asp.net core MVC의 Controller에서 사용예제를 보여드리겠습니다.

using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;

// primary constructor
public class HomeController(ILogger<HomeController> logger, IHelloService helloService) : Controller
{
    private readonly ILogger<HomeController> _logger = logger;
    IHelloService _helloService = helloService; 

    public IActionResult Index()
    {
        _helloService.Hello();	// 사용
        return View();
    }

    public IActionResult Privacy()
    {
        return View();
    }

    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult Error()
    {
        return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    }
}

 

이제 디버그모드로 실행 후 메인 페이지을 열면 Hello 함수가 실행됐음을 알 수 있습니다.

 

 

 

asp.net core에서의 의존성 주입을 알아보았습니다.

 

java lombok의 @RequestArgConstructor 처럼 의존성 주입과 그에 필요한 작성부분이 더 편리하게 쓸 수 있으면 좋았겠는 반면에, program.cs만 보더라도 어떤 구현체가 사용되고 있는지 한눈에 볼 수 있는 장점이 있었던 asp.net core에서의 의존성 주입이었습니다. 

반응형
반응형

오랜만에 작성하네요.

업무에 주는 아니지만 .NET Web을 꽤 사용하고 있는데, 원래 스프링 웹개발 부터 시작했다보니 스프링부트와 asp.net core mvc간에 유사점이 많아서 어느정도 선까지 익히기는 어렵지 않았어요.

 

 

아주 간단한 기본개념 또는 원리 부터 시작해보겠습니다.

 

아래는 .net8.0 환경에서 dotnet new mvc로 생성한 기본 프로젝트의 폴더 내부입니다.

MVC가 모델 뷰 컨트롤러죠. Models, Views, Controllers 폴더에 기본 템플릿으로 하나씩 생성되어있습니다.

 

이중에서 오늘은 appsettings.json, appsettings.Development.json에 대해서 다뤄볼꺼예요.

 

appsettings.json, appsettings.Development.json  이 두개의 파일은 설정파일입니다.

기본적으로 ide 등에서 디버그모드로 실행하는 경우에는 appsettings.Development.json을 읽고 빌드 후 사용하게 되면 appsettings.json을 읽습니다.

 

이제 본격적으로 설정파일을 읽고 프로젝트 소스 내에서 사용하는방법을 다루도록 하겠습니다.

 

설정 파일 내 값들은 program.cs에서는 아래 json의 value 값을 가져오기 위해서 다음과 같이 사용하실 수 있습니다. 제네릭은 입력한 값의 형태에 맞게 사용하시면 되겠습니다.

(appsettings.json 내 구조, 사용소스 순으로 본문 내 다른부분에서도 작성하겠습니다.)

{
	"key": "value"
}
builder.Configuration.GetValue<string>("key")

 

형태로도 불러올 수 있고, json파일 내에서 한 키값 내에 여러 값을 집어넣고, 그와 매핑되는 클래스를 만들어서 클래스의 인스턴스로 바인딩도 가능합니다.

{
	"MyConfig": {
      	"Config1": 1,
        "Config2": "1"
    }
}
public class MyConfig {
	public int config2 {get; set;}
	public string? config2 {get; set;}
}

var myConfig = builder.Configuration
    .GetSection("MyConfig")
    .Get<MyConfig>();

 

 

다음은 Controller 등의 다른 클래스에서 사용방법 입니다.

 

클래스의 속성으로 private readonly IConfiguration _configuration;를 추가 후 생성자에도 추가해줍니다.

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IConfiguration _configuration;

    public HomeController(ILogger<HomeController> logger, IConfiguration configuration)
    {
        _logger = logger;
        _configuration = configuration;
    }
}

 

그 다음 컨트롤러의 메소드 내부에서 다음과 같이 사용하시면 되겠습니다.

{
	"key": "value"
}
string? key = _configuration["key"];

 

json 내부가 2단 이상의 구조라면 다음과 같이 사용하실 수 있습니다.

{
	"MyConfig": {
      	"Config1": 1,
        "Config2": "1"
    }
}
string? MyConfig2 = _configuration["MyConfig:Config2"];

 

 

 

마지막으로 section을 바인딩한 클래스로 사용하는 방법입니다.

Program.cs 파일을 아래와 같이 수정합니다.

var builder = WebApplication.CreateBuilder(args); 
// Program.cs 내 윗 부분 바로 아래 쯤에 아래 코드를 삽입합니다.

// MyConfig 섹션을 MyConfig 클래스로 바인딩하여 DI 컨테이너에 등록
builder.Services.Configure<MyConfig>(
    builder.Configuration.GetSection("MyConfig")
);

 

Program.cs에서 DI 컨테이너에 등록한 MyConfig클래스 Configure를 Controller에서 의존성을 주입하기 위해 private readonly로 선언 후 생성자에서 넣어줍니다.

using Microsoft.Extensions.Options;

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly MyConfig _myConfig;

    public HomeController(ILogger<HomeController> logger, IOptions<MyConfig> options)
    {
        _logger = logger;
        _myConfig = options.Value;
    }
}

 

이제 메소드 내에서 _myConfig.Config1 과 같이 사용이 가능해집니다.

 

 

오늘은 .net 6.0이상의 asp.net core mvc에서 설정파일 사용하는 방법을 알아보았습니다.

이전에 Serilog.AspNetCore 와 결합하면 의존성 주입으로 설정파일과 로그작성을 자유롭게 하시게 되었겠네요!

 

다음글에서는 controller와 viewer의 관계에 대해서 다룰까합니다.

감사합니다.

반응형

+ Recent posts