Skip to content

프로 ASP.NET MVC5 메모

JaeYeonLee0621 edited this page May 15, 2018 · 6 revisions

ASP.NET 프레임워크

그냥 메모용으로 정리한 거니까 안보셔도 됩니다. 책을 사서 보는 걸 추천드려요!!

MVC

  • 전송 방식
GET : 링크 클릭시
POST : @html.begin 시
  • Data Annotation을 체크하기 위해선 Model Validation을 사용해야한다.

  • @Html.ValidationSummary()

: 제출한 폼이 받아들여지지 않는 이유를 알려주는 정보 (Validation Error를 나타내줌) ValidationSummary가 있으면 폼이 다시 돌아오더라도 데이터는 유지된다.

  • 유효성 검사 오류가 발생하면 Helper Method가 input 요소에 input-validation-error라는 이름의 CSS 클래스를 추가한다.
<input class="input-validation-error" type="text" name="Name"/>
  • MVC Architecture 각 부분들은 경계가 명확하고 자기 충족적이므로 이를 관심사의 분리라고 한다 (Separation of Concerns) 이렇게 각각을 분리하면 유지 보수가 쉬워진다.

  • View Model : 물리적인 데이터 / Domain Mode : 실제로 구현

  • TDD(Test Driven Development) 개발

  1. Unit Testing : 개별적인 클래스들의 동작을 응요프로그램의 다른 부분과 독립적으로 정의하고 검정
  2. Integration Testing : 전체 웹 응용 프로그램을 구성하는 다수의 구성요소들이 더불어 수행하는 동작들을 정의하고 검증
  3. Regression Testing : 오래된 기능에서 발생할 수 있는 새로운 버그를 감지하는 기능

보통 배포시 버그의 원인이 될 가능성이 높다고 판단되는 특성이나 기능에 대한 단위 테스트 작성에 사용

테스트를 수행하는 방법에 관련되서는 그 사람의 선택이지만 무조건 테스트는 진행해야 한다 (당연)

  • Unit Test
  1. Arrange : 테스트에 대한 조건들을 설정
  2. Act : 테스트를 수행
  3. Asset : 반환된 결과가 요구 사항을 만족하는지 검증

+) DI를 사용하면 단위테스트에 큰도움이 된다. Repository의 가상 구현 을 생성한 다음 이를 컨트롤러에 주입해서 구체적인 시나리오르르 만들 수 있다.

  • 실패 > 성공 > 리펙터 (Red - Green - Refactor) 작업 방식
  1. 응용프로그램에 추가할 새로운 기능이나 메서드 결정
  2. 새로운 기능의 동작을 검증할 테스트부터 먼저 작성하고 작성이 완료되면
  3. 테스트를 수행해서 빨간불을 얻는다 (일부러)
  4. 새로운 기능을 구현하기 위한 코드를 작성한다
  5. 녹색 불을 얻을 때까지 테스트 수행과 수정을 반복한다.
  6. 필요한 경우 코드를 래팩터한다.
  7. 테스트를 수행해서 변경사항이 여러분이 추가한 기능의 동작에 영향을 주지 않았는지 확
  • TDD가 권장되는 이유는 프로그래머가 코딩을 시작하기 전에 변경이나 개선 작업이 어떻게 동작하게 될지에 관해서 많은 생각을 하게 만듬 계획상의 명확한 최종 목표와 그 목표를 달성했는지 여부를 언제든지 확인 가능 테스트를 먼저 작성함으로써 코드 작성에 사용되는 기술로 인한 선입견이 생기기 전에 어떻게 완벽한 구현을 할 수 있을 지를 먼저 생각하게 해준다.

  • 통합 테스트 - UI 자동화 (UI Automation) 버튼 눌림, 링크 이동, 폼 제출과 같이 사용자가 수행할 수 있는 동작들을 재현해서 응용프로그램의 전체 기술 계층을 테스트 할 수 있도록 함

  • Routing

RouteConfig.cs 새로운 Routing을 생성하려면 기존의 Routing보다 우선순위가 더 높아야한다. 높아야한다는 것은 결국 더 앞서서 정의되어있다는 뜻이다.

C#

yield

IEnumerable 형식에 대한 확장 메서드에서 yield 키워드를 이용해서 원본 데이터의 항목들에 특정 선택 기준을 적용한 다음 선별된 결과물들의 모음을 다시 IEnumerable로 반환할 수 있다.

// static으로 정의된 FilterByCategory
foreach(Product prod in productEnum)
{
  if(prod.Category == categoryParam){
    yield return prod;
  }
}

foreach(Product prod in products.FilterByCategory("Soccer"))

Delegate

/*예시 1*/
public static IEnumerable<Product> Filter{
  (this IEnumerable<Product> productEnum, Func<Product,bool> selectorParam){}
}

// Fun 생성
Func<Product,bool> categoryFilter = delegate(Product prod){
  return prod.Category == "soccer";
};
Func<Product,bool> categoryFilter = prod => prod.Category == "soccer";

// 삽입 및 실행
products.Filter(categoryFilter);

/*예시 2*/
delegate double MathAction(double num);

MathAction ma2 = delegate(double input) { return input * input; };
double square = ma2(5);

MathAction ma3 = s => s * s * s;
double cube = ma3(4.375);
  • var : 자동 형식 추론

  • LINQ : 클래스에 존재하는 데이터를 질의하기 위함 (찾아보세요-거의 사용할 일 없음 (나는)) LINQ 의 확장 메서드

  1. 마침표 표기법(Dot Notation)
  2. 지연되는 LINQ 확장 메서드 : 가장 최신의 값을 가져와서 수행할 수 있음

비동기 메서드

return httpTask.ContinueWith((Task<HttpResponseMessage> antecedent) => { return antecedent.Result.Content.Headers.ContentLength; });

httpTask가 실행되는 동안 다른 동작을 실행 가능 Task는 Background 작업이 만들어내는 결과에 기반한 강력한 형식

  • 백그라운드 작업이 완료됬을 때 수행할 작업을 지정 -> Continuation

위의 예제를 보면 return이 두번 사용되고 있는 것을 확인할 수 있음

ContentLength는 nullable long 충족적이므로 GetPageLength() 함수를 만들려면 Task<long?> GetPageLength()로 만들어야한다.

  • Async, Await 키워드 사용하기
public async static Task<long?> GetPageLenth(){
  HttpClient client = new HttpClient();
  var httpMessage = await client.GetAsync("url");

  // HTTP 요청이 완료되기를 기다리는 동안
  // 여기에서 다른 작업들을 처리할 수 있다.
  return httpMessage.Content.Headers.ContentLenth;
}

async : 비동기 Method

await : async 메서드가 반환해주는 Task의 결과를 대기하는 한편 계속해서 메서드 내의 나머지 구문들을 실행하도록함

** await를 사용하면 async를 사용해야함!

Razor

  • @ model

@model 구문은 Razor 페이지의 상단에 위치하는데 실제로 html에서 사용할 때에는 @Model 으로 사용한다.

넘길때 View(Model 이름) 으로 넘긴다!

  • Views 폴더 하위에 존재하는 이름이 밑줄( _ ) 로 시작하는 파일은 사용자에게 반환되지 않으므로 Render 하고자 하는 파일고 그 파일들을 지원하기 위한 파일들을 파일 이름 기준으로 구분해 지을 수 있다.

  • Razor Attribute

<div data-discount="@ViewBag.ApplyDiscount" data-express="@ViewBag.ExpressShip" data-supplier="@ViewBag.Supplier"/>
<input type = "checkbox" checked = "@ViewBag.ApplyDiscount"/>

data- 로 시작하는 Attrivute를 뜻하는 data Attribute는 사용자 지정 Attribute 이다!!!!

data- 의 기능은 ViewBag의 개체 속성값이 null 인 경우 Razor가 알아서 빈 문자열로 Render 해준다!

checked 같은 Attribute들은 자신의 값 대신 존재 여부 자체를 통해 자신이 포함된 HTML 요소의 구성을 변경 속성 값이 false 이거나 null 인 경우 HTML 요소에서 Attribute 자체를 완전히 제거

  • HTML 요소를 담고 있지 않은 일반적인 문자열을 뷰에 삽입해야하는 경우 @: text

  • @model 배열로 넘기기

@model Razor.Models.Product[]

@if (Model.Length > 0){
  @foreach(Razor.Models.Product p in Model){
    <td> @p.Name </td>
  }
}
  • Razor.Models를 사용하면 불편함 즉 namespace를 지정해 주는 것이 필요! (깔끔~)
@using Razor.Models
@model Product[]

@if (Model.Length > 0){
  @foreach(Product p in Model){
    <td> @p.Name </td>
  }
}
  • 가격을 정의할 때
@p.Price.ToString("c")

MVC 필수 도구

DI(Dependency Injection)

  • Infrastructure : 다른 폴더에 들어가기 애매한 파일이나 폴더를 넣을 때 사용
  • MVC 프레임워크가 필요한 개체를 얻을 때 IDependencyResolver Interface 사용
  1. Ninject
  • Infrastructure에 NinjectDependencyResolver class를 생성하였다. (EssentialTools.Infrastructure는 임의로 지정한 namespace 이름이다.) 아래는 NinjectWebCommon Class이다

    private static void RegisterServices (IKernel kernel) {
      System.Web.Mvc.DependencyResolver.SetResolver(new EssentialTools.Infrastructure.NinjectDependencyResolver(kernel));
    }

    WebCommon RegisterServices 함수에 System.Web.Mvc.DependencyResolver 클래스에 정의되어있는 정적 SetResolver 메서드를 사용하여 이 해결자를 프레임워크에 등록한다. Ninject와 MVC 프레임워크의 연결을 만드는 것이다.

  • 생성자 주입 (Contructure Injection) 과정

- MVC 프레임워크가 요청을 수산한 다음 HomController를 대상으로 하고 있다는 것을 알아낸다
- GetService 메서드의 Type 매개별수에 HomeController 클래스를 지정해서 사용자 지정 의존성 해결자 클래스에게 새로운 HtomeController 클래스의 인스턴스 생성을 요청
- 사용자 지정 의존성 해결자 클래스에게 Ninject Kernel의 TryGet Method에 Type 매개 변수 개체를 전달해서 Ninject 에 새로운 HomeController 클래스의 생성 요청
- Ninject는 HomeController에 자신이 정보를 가지고 있는 IValueCalculator Interface에 대한 의존성을 선언하고 있다는 것을 알아낸다
- Ninject는 LinqValueCalculator 클래스의 인스턴스를 생성하고 이를 이용해서 HomeController 클래스에 새로운 인스턴스를 생성
- Ninject는 인스턴스를 사용자 지정 의존성 해결자에게 전달해주고 이를 다시 MVC 프레임워크에 반환해준다
  • Binding과 함께 Parameter를 넘기는 방법

    Property 설정

    kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize",50M);

    Constructor 설정

    kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithConstructorArgument("DiscountSize",50M);

    "" 안에 있는 이름은 Property 이름이거나 생성자의 Parameter 이름이다.

  • 조건적 바인딩 사용하기

    만약 같은 Interface가 2개 이상의 Class로 Binding 되어있다면 조건 Binding Method를 이용하여 선택할 수 있다.

  • 개체 범위 설정하기

    Ninject가 생성해주는 개체들의 생명주기를 응용프로그램의 상황에 맞게 조정할 수 있도록 지원해주는 기능 만약 단일 인스턴스를 응용 프로그램 전체에서 공유하고 싶을 수도 있고, 또는 플랫폼이 수신한 각 HTTP 요청마 새로운 인스턴스를 생성하고 싶은 경우도 있을 수 있다.

  • 범위 설정 방법

InRequestScope()
 : 요청마다 오직 하나의 클래스 인스턴스만 생성, 결과적으로 각 요청은 자신만의 개별적인 객체를 얻음

InTransientScopte()
: 범위를 지정하지 않는 상태와 동일하며 의존성을 해결할 때마다 매번 새로운 개체를 생성

InSingletonScope()
: 응용 프로그램 전체에 공유되는 단일 인스턴스

InThreadScope()
: 단일 인스턴스를 생성해서 단일 스레드에서 요청된 모든 개체들의 의존성을 해결

사용 예제

kernel.Bind<IValueCalculator>().To<LinqValueCalculator>().InRequestScope();
  1. Unity

단위 테스트 프레임워크

Mocking 도구

Moq Library를 사용해야한다.

  • 독립적으로 동작할 수 없는 개체들을 테스트해야하는 경우 관심있는 클래스나 메서드에서만 테스트를 집중할 수 있어야한다. Mock 개체를 사용하면 관심있는 기능을 검사하는 일에만 집중할 수 있다.

  • 유료버전에 Fakes라는 기능이 있는데 이보다 Moq Package를 NuGet에서 Download 받는 것이 좋다.

  • 코드 전부를 작성한게 아닌, 일부만 작성

/* 예시 1*/

// Arrange (시나리오 작성)
Mock<IDiscountHelper> mock = new Mock<IDiscountHelper>();
mock.Setup(m => m.ApplyDiscount(It.IsAny<decimal>())).Returns<decimal>(total=>total);
var target = new LinqValueCalculator(mock.Object);

// Act (작업 시도)
var result = target.ValueProducts(products);

// Assert (결과 검증)
Assert.AreEqual(products.Sum(e=>e.Price),result);

/* 예시 2*/
// 만약 Mocking을 하고 싶은데 DI를 사용했다고 한다면
// Ninject Resolver Class로 가서 아래의 로직을 작성한다
// 그렇다면 어떤 코드의 변화도 없이 Mocking을 실행할 수 있다.
Mock<IproductRepository> mock = new Mock<IproductRepository>();
mock.Setup(m => m.Products).Returns(new List<Product>{
  new Product {Name = "name1", Price = 100},
  new Product {Name = "name2", Price = 200}
  });

kernel.Bind<IproductRepository>().ToConstant(mock.Object);
  • 먼저 Setup 메서드를 호출해서 Mock 개체에 메서드를 추가한다. Moq 기본적으로 LINQ와 람다식을 이용해서 동작한다. Setup을 호출하면 Moq은 구현을 요청한 인터페이스에 접근할 수 있도록 해당 인터페이스를 전달한다. 전달된 인터페이스를 이용해서 람다 식으로 구성하고자 하는 메서드를 선택할 수 있다.

  • It 클래스를 사용해서 관심을 갖고 있는 매개변수 값들을 Moq에 알려줘야한다. Mock으로 구성한 메서드를 호출했을 때 Moq이 반환할 결과를 지정할 수 있다. 현재 Setup에서 결과값을 가공하지 않는 통과 메서드(Pass-Through)를 정의하고 있다.

  • mock.Object는 개체의 Object 속성 값을 읽어서 Mock 개체를 사용하는 것이다.

  • Moq은 정의된 순서의 역순으로 동작들을 평가하기 때문에 가장 마지막으로 호출된 Setup 메서드를 첫번재로 고려한다. 따라서 Moq을 작성할 때에는 가장 일반적인 동작들은 먼저 작성하고 제한적인 동작들은 나중에 작성한다.

  • Mocking 예외 던지기

mock.Setup(m => m.ApplyDiscount(It.Is<decimal>(v=>v==0))).Throws<System.ArgumentOutOfRangeException>();
  • 특정 범위의 값들을 위한 Mocking
mock.Setup(m => m.ApplyDiscount(It.IsInRange<decimal>(10,100,Range.Inclusive))).Returns<decimal>(total=>total-5);
mock.Setup(m => m.ApplyDiscount(It.Is<decimal>(v => v>=10 && v<=100)));

** 잘 만들어진 단위 테스트는 간결하고 한가지 문제에만 집중해야한다

MVC5 정리

  • App_Data

: SQL Server Express, SQLite 및 기타 파일 기반 저장소를 사용할 경우 XML파일이나 데이터베이스 같은 내부 데이터를 저장하는 위치 IIS는 이 폴더의 내용을 외부로 서비스 하지 않는다

  • Views/Web.config

: 응용프로그램을 위한 구성파일이 아니고 View가 동작하는데 필요한 구성과 IIS에 의해서 뷰 파일 자체가 서비스 되지 않도록 제한하는데 필요한 구성 그리고 기본적으로 뷰에 임포트하는 네임스페이스에 대한 정보가 포함되어있다.

  • Global.asax

: 전역 ASP.NET 응용프로그램 클래스 , 코드 숨김 (code-behind) 클래스에는 라우팅 구성 정보가 등록되며, 응용프로그램이 초기화되거나 종료될 때 또는 처리되지 않은 예외가 발생했을 때 실행될 코드가 설정된다.

  • Web.config

: 응용 프로그램 구성을 위한 파일이다.

+) Convention over Configuration(CoC) : 컨트롤러와 뷰의 관계를 명시적으로 구성할 필요가 없이 명명규칙에 따라 파일의 이름을 지정하기만 하면 된다.

  • Debugger Mode

Web.config에 가면

<system.web>
    <compilation debug="true".../>

에서 정의해 놓는다. 이는 배포시에 무조건 False로 변경해줘야하는데, 프로젝트 구성을 Release 모드로 변경하면 자동으로 변경된다.

URL Routing

  • Route Config 파일을 작성할 때 주의해야할점

위에서부터 우선순위를 두고 적용해 나간다. 따라서 제약조건이 작은 것부터 위에 둬야 적용할 수 있다.

  • URL Test시 주의해야할점 : URL은 반드니 (~) 틸트 문자로 시작해야한다.

  • 가변적인 Routing을 지정하고 싶을 땐 앞에 에스터리스트( * )를 붙여야한다. : {controller}/{action}/{ * catchAll}

ex) /Customer/List/Delete/Perm Controller : Customer Action : List
catchAll : Delete/Perm

  • Routing Option

ex)

Route myRoute
= routes.MapRoute("AddControllerRoute","Home/{action}/{id}/{*catchAll}",

// 기본값 설정이 먼저 실행된다.
new {controller= "Home", action="index", id = UrlParameter.Optional },

// H로 시작하는 Controller만 실행 | 정규식을 이용해서 지정할 수 있다.
new {controller = "^H.*", action="^Index$|^About$",

// Get 형식만 받을 수 있음
httpMethod = new HttpMethodConstraint("GET"),
// httpMethod = new HttpMethodConstraint("GET", "POST")

// 받은 값 제약조건

// 한 개만 적용
id = new RangeRouteConstraint(10,20),

// 여러개 적용
id = new CompoundRouteConstraint(new IRouteConstraint[] {
  new AlphaRouteConstraint(),
  new MinLengthRouteConstraint(6)
  })
},

// Chrome에서만 접근 가능
new {customConstraint = new UserAgentConstraint("Chrome") },

// 아래의 namespace부터 찾은 이후 찾지 못하면 전체를 둘러본다 (Route를 먼저 찾는 우선순위를 준다)
new[] {"URLsAndRoutes.AdditionalControllers"});

// Route의 속성을 변화시키고 싶을 때 (현재는 다른 namespace에 대한 검색을 비활성화 시킴)
myRoute.DataTokens["UseNamespaceFallback"] = false;
  • Route Attribute 제약 조건
// id는 int 형식일 때만
[Route("Users/Add/{user}/{id:int}")]

[Route("Users/Add/{user}/{password:alpha:length(6)}")]

// RoutePrefix가 적용되지 않음
[Route("~/Test")]

고급 라우팅 기능

<a href ="/Home/CustomVariable"> URL </a>

위와 같은 형식으로 지정하면 매우 귀찮고 불편하다

@Html.ActionLink("This is an outgoing URL","CustomVariable");
@Html.ActionLink("This is an outgoing URL","CustomVariable",new{ id = "Hello" });
@Html.ActionLink("This is an outgoing URL","Index","Home");

위와 같이 실행될 Action Method 이름을 Mapping하면 URL이 변경되더라도 일일이 변경해 줄 필요가 없다


@Html.ActionLink("This si an outgoing URL","Index","Home",null, new{
  id = "myAnchorID",
  @class = "myCssClass"
  })

위의 것을 a 태그로 바꾸면

<a class="myCssClass" href="/" id="myAnchorId"/>

@Html.ActionLink("This is an outgoing URL","Index","Home",
"https","myserver.mydomain.com","myFragmentName",
new {id="MyId"},
new {id="myAnchorID",@class="myCssClass"})

위의 것을 a 태그로 바꾸면

<a class="myCssClass" href="https://myserver.mydomain.com/Home/Index/MyId#myFragmentName" id="myAnchorID" class="myCssClass"/>

ActionLink 이외에도 URL을 만들어주는 것이 있음 (a태그 없이 URL만 생성)

@Url.Action("Index","Home", new {id = "MyId"})
/Home/Index/MyId

RedirectToRoute를 지정

return RedirectToRoute(new {controller="Home",action="Index",id="MyId"});

Route 이름 지정 후 사용하기

// RouteConfig 파일에서 정의 혹은 Attribute로 정의해도 괜찮음
route.MapRoute("MyOtherRoute","App/{action}",new {controller="Home"})

// <a Length="8" href="/App/Index?Length=5">Click me </a>
@Html.RouteLink("Click me","MyOtherRoute","Index","Customer");

Segment 재사용

routes.MapRoute("MyRoute","{controller}/{action}/{color}/{page}");

라는 단일 라우트가 있고 사용자가 /Catalog/List/Purple/123 이라는 URL을 Render 한다고 가정해보자

@Html.ActionLink("Click me","List","Catalog",new {page=789}, null)

color segment에 대한 값이 제공되지 않았고 기본값도 없기 때문에 Route에 매치할 수 없을 것이라고 예상하지만 가상의 사용자가 페이지에 접근할 때 사용했던 URL으로부터 얻어진 Purple이라는 값이 color변수에 할당될 것이다. 다만 Routing System은 URL Pattern에서 Html.ActionLink 메서드에 제공되는 어떤 매개변수들보다 더 먼저 나타나는 Segment 변수들에 대해서만 값을 재사용할것이다.

@Html.ActionLink("Click Me","List","Catalog",new {color="Aqua"},null);

color 변수가 page 변수 앞쪽에 위치하므로 Routing System은 들어오는 URL에 존재하는 값을 재사용하지 않아 Route에 매칭하지 않을 것이다.

이럴땐 그냥 이런 상황 자체를 발생하지 않도록 하는 것이 좋다. URL 패턴에 존재하는 모든 Segment 변수들에 대한 값을 제공하는 것을 강력하게 권한다.

Area

각 응용프로그램의 기능별 단위를 나타낸다. 각 개발자가 충돌없이 프로젝트 상에서 작업할 수 있게 해준다.

+) Routing을 개별적으로 할 때에는 이미 그 url이 존재하는지 존재하지 않는지를 확인해야한다.

Application_Start()에

AreaRegistration.RegisterAllAres()

가 호출되면 MVC 프레임워크가 응용프로그램에 존재하는 모든 클래스를 살펴보고 AreaRegistration Class에서 파생된 클래스가 발견되면 해당 클래스의 RegisterArea 메서드를 호출한다.

+) Routing은 역시 처음이 우선순위가 높은 Routing이다

+) AreaRegistrationContext 클래스의 MapRoute 메서드는 자동으로 등록한 Route의 Namespace를 해당 영역의 컨트롤러가 위치한 Namespace로 제한한다. 따라서 영역에 Controller를 생성할 때는 반드시 기본 네임스페이스를 유지해야한다.

하지만 사실 Area를 두더라도 완전히 독립되지 않을 수도 있다. Area에 Home/Index가 존재하고 기존 프레임워크에도 존재한다면 둘중 하나를 골라야 하는 상황이 올 수 있다. 이를 해결하는 방법으로는

  1. 우선순위를 Area로 둔다

  2. [RouteArea] 라는 Attribute를 둔다.

[RouteArea("Services")]
[RoutePrefix("Users")]
public class CustomerController : Controller{

  // Services/Users/Add
  [Route("Add")]
  public ActionResult Index(){
    return View();
  }
}

액션 링크

만약 다른 영역에 존재하는 액션이나 아예 영역에 포함되어있지 않은 액션에 대한 링크를 만들려면 아래와 같이 정의해야한다.

@Html.ActionLink("Click","Index",new {area="Support"})
// <a href="/Support/Home">Click</a>

만약 최상위 수준의 컨트롤러 중 하나에 정의된 액션에 대한 링크를 만들려면

@Html.ActionLink("Click","Index",new{area=""})

Routing System 우회하기

RouteCollection 클래스의 IgnoreRoute 메서드를 통해서 기능 구현

routes.IgnoreRoute("Content/{filename}.html");

IrnoreRoute 메서드는 RouteCollection에 MvcRouteHandler 대신 StopRouting Handler 클래스의 인스턴스를 라우트 처리기로 사용하는 항목을 생성

사용자 친화적인 URL을 만들기

  • 응용프로그램의 상세정보가 아닌 내용 설명

/Website_V2/CachgedContentServer/FromCache/AnnualReport -> /Articles/AnnualReport

  • ID 번호보다는 콘텐츠의 제목을 사용하는 편이 좋다

Article/2392/ -> Article/AnnualReport

  • HTML 페이지들에 대한 확장자는 사용하지 않는다. 하지만 특별한 형식의 파일에 대한 확장자는 사용하는 것이 좋다 MIME 형식만 적절하게 됬다면 파일명의 확장자는 신경쓰지 않지만 사람들은 여전히 PDF 파일은 .pdf 라는 확장자를 갖고 있을 것으로 기대할 것이다.

  • 이해할 수 있는 계층 구조를 사용해서 방문자가 상위 카테고리의 URL을 예츨할 수 있도록 한다.

Products/Menswear/Shirts/Red

  • 대소문자를 구분하지 않는다. (ASP.NET은 기본적으로 구분하지 않는다.)

  • 기호, 코드, 문자 시퀀스는 사용하지 않고 단어 구분을 위해서는 - 을 사용하자 (/my-great-article)

  • URL을 변경하지 말자

  • 일관성이 있어야한다. (전체에서 한가지 URL 형식만 사용해야한다)

Controller

들어오는 데이터를 받는 방법

1. Context 개체로부터 추출

string userName = User.Identity.Name;
string serverName = Server.MachineName;
string clientIp = Request.UserHostAddress;
Datetime dateStamp = HttpContext.Timestamp;

string oldProductName = Request.Form["OldName"];
string newProductName = Request.Form["NewName"];

2. Action Method에 매개변수로 전달되는 데이터들을 사용

public ActionResult ShowWeatherForecast(string city, Datetime forDate){}

// nullable 변수를 사용하는 것이 좋은데 만약 그렇지 않다면 아래와 같이 초기화를 해줘도 괜찮다
public ActionResult ShowWeatherForecast(string city = "seoul", Datetime forDate="2018-05-14"){}

만약 nullable 변수를 사용하지 않는다면 ModelState와 유효성 검사를 해야한다.

  • 액션 결과를 줄때에는 바로 주는 것보다 Class를 하나 만들어서 중간 다리를 연결시켜주는 것이 더욱 낫다.
public class CustomRedirectResult : ActionResult{
public string Url {get;set;}
}
public ActionResult ProduceOutput(){
  if(Server.MachineName="TINY"){
    return new CustomRedirectResult {Url = "/Basic/Index"};
  }else{
    return null;
  }
}

3. 프레임워크의 모델 바인딩 기능을 명시적 호출

  • View를 지정할 경우 ("~/") 로 시작해야한다

  • Action Method에서 View로 데이터 전달하기

public ViewResult Index(){
  DateTime date = DateTime.Now;
  return View(date);
}
  1. 약한 형식의 데이터 뷰
The day is :@(((DateTime)Model).DayOfWeek)
  1. 강력한 형식의 데이터 뷰
@model DateTime
The day is : @Model.DayOfWeek
  • ViewBag을 이용하여 전달하기

Redirect

Action Method의 결과로 어떠한 출력을 만드는 것이 아니라 사용자의 브라우저를 다른 URL로 재전송해야하는 경우도 있다.

Redirect 방법

POST 요청을 처리하는 Action Method 안에서 바로 HTML을 반환한다면 브라우저가 새로고침 버튼을 클릭할 경우 폼이 다시 제출되는 문제가 생길 수 있다.

이를 예방하기 위해 Post/Redirect/Get 패턴을 사용하면 되는 데 Post로 보내고 Redirect 로 브라우저가 또 다른 URL로 GET 요청을 수행하도록 하고 Get 요청은 응용프로그램의 상태를 변경하지 않기에 실수로 재요청을 하더라도 문제가 없다

재전송 HTTP 코드는 아래와 같다

  1. 302 전송 : 임시적인 재전송을 의미 (Post/Redirect/Get) 형식에서는 사용해야한다.
    ex) return Redirect("/Example/Index");
  2. 301 전송 : 영구적인 재전송, 경고와 함께 사용, 사용자에게 다시는 원본 URL이 요청되지 않음 & 재전송 코드가 포함된 새로운 URL을 앞으로 사용하게 될 것임을 알려주어야 한다.
    ex) return RedirectPermanent("/Example/Index")

응용 프로그램 안의 다른 부분으로 재전송을 해야할 때 사용하는 것은 아래와 같다

RedirectToRoute : 임시 재전송

RedirectToRoutePermanent : 영구적인 재전송

public RedirectToRouteResult Redirect(){
  return RedirectToRoute(new{
    controller="Example",
    action = "Index",
    ID = "MyID"
    });
}

같은 컨트롤러 단이나 다른 컨트롤러를 참조할 때는 아래와 같다

//  같은 컨트롤러 단의 Redirect
public RedirectToRouteResult Redirect(){
  return RedirectToAction("Index");
}
//  다른 컨트롤러 단의 Redirect
public RedirectToRouteResult Redirect(){
  return RedirectToAction("Index","Basic");
}

재전송시 데이터 보존하기 (TempData)

Redirect는 완전히 새로운 HTTP 요청을 보내오게 하기 때문에 기존 요청의 상세 정보에대해서는 전혀 알수 없다. 따라서 다음 요청으로 데이터를 전달하고 싶다면 TempData 기능을 사용해야한다.

TempData는 Session과 유사한데 TempData값은 그 값을 읽을 경우 삭제된 것으로 표시되고 요청이 처리되고 난 뒤 제거된다는 차이가 있다.

  • 보내기
public RedirectToRouteResult RedirecToRoute(){
  TempData["Message"] = "Hello";
  TempData["Data"] = DateTime.Now;
  return RedirectToAction("Index");
}
  • 불러오기
public ViewResult Index(){
  ViewBag.Message = TempData["Message"];
  ViewBag.Date = TempData["Data"];
  return View();
}
The Day is @(((DateTime)TempData["Date"]).DayOfWeek)

만약 읽어온 값을 가져오지만 제거된 것으로 표기하지 않으려면 아래와 같이 표시되는데 한번 더 읽어오면 삭제된 것으로 나타나진다.

(DateTime)TempData.Peek("Date");
TempData.Keep("Date");

HTTP 코드와 오류 반환하기

  • 기본적인 HttpCodeResult 반환하기
public HttpStatusCodeResult StatusCode(){
  return new HttpStatusCodeResult(404,"URL Cannot be serviced");
}
  • 404 결과 전송하기
public HttpStatusCodeResult StatusCode(){
  return HttpNotFound();
}
  • 401 결과 전송하기 : 요청이 권한 허가를 받지 않음
public HttpStatusCodeResult StatusCode(){
  return new HttpUnauthorizedResult();
}

Filter

MVC 프레임워크 요청 처리 파이프라인에 추가적인 로직을 삽입하기 위한 기능으로 Cross-Sutting concerns(횡단 관심사)를 구현하기 위한 간단하고도 세련된 방법이다. 횡단 관심사의 일반적인 예로는 Loggin, Authentication, Caching이 있다.

if(!Request.IsAuthenticated){}

에서

[Authorize]

로 깔끔하게 변경할 수 있다

필터 형식

  • Authentication

: 다른 필터나 Action Method가 실행되기 전에 가장 먼저 실행되지만 권한 부여 필터가 수행된 후에 다시 실행될 수도 있다.

  • Authorization

: 인증 필터가 수행된 후 다른 필터 들이나 액션 메서드가 실행되기 전에 실행된다.

  • Action

: 액션 메서드가 실행되기 전과 후에 실행된다.

  • Result

: 액션 결과가 실행되기 전과 후에 실행된다.

  • Exception

: 다른 필터들이나 액션 메서드 액션 결과가 예외를 던지는 경우에만 실행된다.

권한 부여 필터 사용하기

인증 필터가 수행된 이후에 액션 필터 및 액션 메서드가 호출되기에 앞서 실행된다. 권한 부여 정책을 강제하는 필터이다.

IAuthorizationFilter Interface를 구현하는 클래스를 직접 만들어서 자체적인 보안 로직을 구현할 수 있지만, Security Bug가 심각할 수 있어 권장하지 않는다.

public class CutomAuthAttribute : AuthorizeAttribute{
  private bool localAllowed;
  public CustomAuthAttribute(bool allowedParam){
    localAllowed = allowedParam;
  }

  // AuthorizeCore Method를 재정의 하는 방식을 사용해서 내장 AuthorizeAttribute 클래스가 제공해 주는 기능들의 이점을 활용
  public override bool AuthorizeCore(HttpContextBase httpContext){
    if(httpContext.Request.IsLocal)
    {return localAllowed;}
    else
    {return true;}
  }
}

위와 같이 Custom 된 Authorization을 사용하는 방법은 아래와 같다

[CustomAuth(false)]
public string Index(){
  return "";
}

만약 CustomAuth에 false를 넣는다면 local에서는 접근하지 못하게 하는 것이다.

내장 권한 부여 필터 사용하기
  • Users : 해당 액션 메서드에 접근이 허용되는 , 로 연결된 사용자들의 이름 목록

  • Roles : , 연결된 역할 이름들의 목록, 사용자가 액션 메서드에 접근하기 위해서는 이 역할 들 중 적어도 하나 이상에 속해 있어야 한다.

[Authorize(Users="admin")]
public string Index(){
  return "";
}

admin 사용자만 Index 액션 메서드를 호출할 수 있도록 지정하고 있는 것이다.

인증 필터 사용하기

Authentication은 MVC5에서 새롭게 등장했으며 응용프로그램에 존재하는 컨트롤러와 액션에 대해서 사용자가 인증되는 방식을 세밀하게 제어할 수 있는 방안을 제공해준다.

인증 필터는 다른 필터들보다 앞서 실행되기에 다른 모든 필터 형식들이 사용되기 전에 적용될 인증 정책을 정의할 수 있다. 또한 부여정책에 따르지 않은 요청에 대해 인증을 요구하기 위해서 권한 부여 필터와 결합하여 사용될 수 도 있다.

IAuthentication Filter Interface 이해하기

OnAuthenticationChallenge Method 는 요청이 액션 메서드의 인증 및 권한 부여 정책에 부합하지 않을 때마다 MVC 프레임워크에 의해서 호출된다. 이 메서드에는 AuthenticationChanllengeContext 개체가 인자로 전달되는데 ControllerContext Class로부터 파생된 개체로

  • ActionDescriptor

: 필터가 적용되어 있는 액션 메서드를 설명하는 ActionDescriptor를 반환한다.

  • Result

: 인증 시도의 결과를 나타내는 ActionResult를 설정한다.

Result를 사용하면 인증 필터가 MVC프레임워크에게 ActionResult을 전달하는데 이를 짧은 순환 (short- circuiting)이라고 한다.

AuthenticationContext 클래스는

  • ActionDescriptor

: 필터가 적용된 액션 메서드를 설명하는 ActionDescriptor를 반환한다.

  • Principal

: 만약 사용자가 이미 인증되어 있다면 현재 사용자를 식별하는 Iprincipal Interface 구현을 반환한다.

  • Result

: 인증 검사의 결과를 나타내는 ActionResult를 설정한다.

예를 들어

1) OnAuthentication Method에서 Context 개체의 Result 속성에 값을 설정하면 Framework가 OnAuthenticationChallenge Method를 호출한다
2) OnAuthenticationChallenge Method가 자신의 컨텍스트 개체의 Result 속성에 값을 설정하지 않는다면 OnAuthentication Method에서 자신이 설정한 값이 그대로 실행될 것이다.
public class GoogleAuthAttribute:RilterAttribute, IAuthenticationFilter{

  public void OnAuthentication(AuthenticationContext context){
    IIdentity ident = context.Principal.Identity;
    if(!ident.IsAuthenticated || !ident.Name.EndsWith("@google.com")){
      context.Result = new HttpUnauthorizedResult();
    }
  }

// 요청이 부합되지 않을 때마다 호출
  public void OnAuthenticationChallenge(AuthenticationChallengeContext context){
    if(context.Result == null || context.Result is HttpUnauthorizedResult){
      context.Result = new RedirectToRouteResult(new ReouteValueDictionary {
        {"controller","GoogleAccount"},
        {"action","Login"},
        {"returnUrl",context.HttpContext.Request.RawUrl}
      });
    }
  }

}

이를 Controller에서 호출하면

[GoogleAuth]
public string List(){
  return "";
}

인증 필터와 권한 부여 필터 함께 사용하기

[GoogleAuth]
[Authorize(Users="bob@google.com")]

Authorize 필터는 bob@google.com 계정을 사용해서만 List 액션 메서드에 접근할 수 있도록 제한하고 있다.

마지막 인증 요청 처리하기

ActionMethod가 실행된 이후이자, ActionResult가 반환 및 실행되기 전에 마지막으로 한번 더 OnAuthenticationChallenge Method를 호출한다. 인증이 완료되었거나 결과가 변경되었다는 사실을 알리기 위한 마지막 기회를 제공해준다

public class GoogleAuthAttribute:RilterAttribute, IAuthenticationFilter{

  public void OnAuthentication(AuthenticationContext context){
    IIdentity ident = context.Principal.Identity;
    if(!ident.IsAuthenticated || !ident.Name.EndsWith("@google.com")){
      context.Result = new HttpUnauthorizedResult();
    }
  }

// 요청이 부합되지 않을 때마다 호출
  public void OnAuthenticationChallenge(AuthenticationChallengeContext context){
    if(context.Result == null || context.Result is HttpUnauthorizedResult){
      context.Result = new RedirectToRouteResult(new ReouteValueDictionary {
        {"controller","GoogleAccount"},
        {"action","Login"},
        {"returnUrl",context.HttpContext.Request.RawUrl}
      });
    }else{
      // 여기를 추가
      FormsAuthentication.SignOut();
    }
  }
}

이렇게 구현하게 되면 bob@google.com 으로 인증해서 Action Method를 실행할 수 있지만 다시한번 요청을 해보면 else 구역이 호출되어 자격 증명을 다시 묻는다. 즉 호출할 대마다 인증을 해야하는 중대한 Method가 된 것이다.

예외 필터 사용하기

IException Filter를 사용해야한다. 처리되지 않은 예외가 발생하면 OnException Method가 호출되고 이 Method의 매개변수는 ExceptionContext 개체인데 이는 ControllerContext Class에서 파생되었으며 요청에 대한 정보를 얻어오기 위해서 사용할 수 있는 유용한 속성들도 제공한다.

ExceptionContext 클래스는 ControllerContext 클래스에서 상속된 속성들에 더해서 예외를 다룰 때 유용한 추가적인 속성들도 제공해준다. 그리고 ExceptionHandled 속성을 true로 설정하여 다른 필터들에 예외가 처리되었음을 알릴 수도 있다.

그렇기엔 다른 필터가 이미 문제를 해결했는지 여부를 확인하는 것은 매우 좋은 습관이다! 불필요하게 다시 복구할 필요가 없으니 말이다!!!!!

public RangeExceptionAttribute : FilterAttribute, IExceptionFilter{
  public void onException(ExceptionContext filterContext){
    if(!filterContext.ExceptionHandled && filterContext.Exception is ArtumentOutOfRangeException){
      filterContext.Result = new RedirectResult("~/Content/ErrorPage.cshtml");
      filterContext.ExceptionHandled = true;
    }
  }
}

이를 Controller에 적용하면 아래와 같다

[RangeException]
public string RangeTest(int id){
  if(id>100){}
else{
  throw new ArgumentOutOfRangeException("id",id,"");
}
}

으로 적용하면 Range Error화면이 예쁘게 나타날 것이다.

하지만 이러한 화면은 너무 단순하기 때문에 사용자가 자세한 정보를 알지 못하고 이 화면이 반복적으로 나왔을 경우 사용자가 응용프로그램을 사용하지 않고 싶을 수 있다. 따라서 위의 코드를 아래처럼 고쳐보자

public RangeExceptionAttribute : FilterAttribute, IExceptionFilter {

  public void onException(ExceptionContext filterContext){

    if(!filterContext.ExceptionHandled && filterContext.Exception is ArgumentOutOfRangeException){

      int val = (int)(((ArgumentOutOfRangeException)filterContext.Exception).ActionValue);

      filterContext.Result = new ViewResult{
        ViewName="RangeError",
        ViewData = new ViewDataDictionary<int>(val)};
        filterContext.ExceptionHandled = true;
      }
    }
  }

내장 필터 사용하기

Web.config

<system.web>
<customErrors mode="On" defaultRedirect="/Error.cshtml"/>
</system.web>

customErrors 노드의 mode attribute 기본값은 RemoteOnly 이고 로컬머신에서 연결할 경우 노란색의 표준페이지를 보낸다는 말이다.

하지만 로컬에서 개발할 경우 mode 를 On으로 설정하여 연결이 어디에서 이루어지던 오류처리 정책이 항상 적용되도록 지정하면 된다.

[HandlerError(ExceptionType=typeof(ArgumentOutOfRangeException),View="RrangeError")]
@model HandlerErrorInfo
<span>
@(((ArgumentOutOfRangeException)Model.Exception).ActionValue)
</span>

액션 필터 사용하기

OnActionExecuting Method는 ActionexecutingContext 매개변수를 가지고 있고, ControllerContext 클래스의 하위 클래스 이다.

OnActionExecuting메서드는 액션 메서드가 불려지기 전에 호출되기 때문에 요청을 확인하거나 취소하거나 변경하는 액션의 수행과 관련된 행동을 한다.

public class CustomActionAttribute : FilterAttribute, IActionFilter{

  // Action이 실행되기 전
  public void OnActionExecuting(ActionExecutingcontext filterContext){
    if(filterContext.HttpContext.Request.IsLocal){
      filterContext.Result = new HttpNotFoundResult();
    }
  }

  // Action이 완료되는 시점
  public void OnActionExecuted(ActionExcutedContext filterContext){

  }
}
[CustomAction]
public string FilterTest(){
  return "";
}

위를 실행하면 Local에서는 필터를 거치게 만들었기 때문에 오류가 나타난다

결과 필터 사용하기

OnResultExecuting, OnResultExecuted 메서드를 이용한다.

이 Method는 매개변수로 각각 ResultExecutingContext 개체와 Result ExecutedContext를 전달받는다.

결과 필터는 이전에 만들었던 Action Filter를 보완해주는 역할을 수행해서 결과를 실행하는데 걸리는 시간을 측정하는 역할을 한다.

ActionFilter

public class ProfileActionAttribute : FilterAttribute, IActionFileter{
  private Stopwatch timer;
  public void OnActionExecuting(ActionExecutingContext filterContext){
    timer = Stopwatch.StartNew();
  }
  public void OnActionExecuted(ActionExecutedContext filterContext){
    timer.Stop();
    if(filterContext.Exception == null){
      filterContext.HttpContext.Response.Write(string.Format("<div>{0:F6}</div>",timer.Elapsed.TotalSeconds));
    }
  }
}

ResultFilter

public class ProfileResultAttribute : FilterAttribute, IResultFileter{
  private Stopwatch timer;
  public void OnResultExecuting(ResultExecutingContext filterContext){
    timer = Stopwatch.StartNew();
  }
  public void OnResultExecuted(ResultExecutedContext filterContext){
    timer.Stop();
    filterContext.HttpContext.Response.Write(string.Format("<div>수행해서 결과를 실행하는 데 걸리는 시간</div>"));
  }
}
[ProfileAction]
[ProfileResult]

를 실행하면 timer의 Second 값과 수행하는데 걸리는 시간이 출력된다.

Action과 Result Filter 둘 다 실행하기

public class ProfileAllAttribute : ActionFilterAttribute{
  private Stopwatch timer;

  // Action
  public void OnActionExecuting(ActionExecutingContext filterContext){
    timer = Stopwatch.StartNew();
  }

  // Result
  public void OnResultExecuted(ResultExecutedContext filterContext){
    timer.Stop();
    filterContext.HttpContext.Response.Write(string.Format("<div>수행해서 결과를 실행하는 데 걸리는 시간</div>"));
  }
}

#### 그 밖의 필터 기능들을 사용하기

1. Attribute 없이 필터 적용하기

: 그냥 저 코드를 Controller에 넣어주면 됩니당

2. 전역 필터 사용하기

FilterConfig.cs
```cs
filter.Add(new ProfileAllAttribute());

Global.asax

FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

를 사용하면 굳이 Attribute를 사용할 필요 없이 바로 Filter가 적용된다.

필터 실행 순서 지정하기

[AttributeUsage(AttributeTarget.Method, AllowMultiple=true)]
public class SimpleMessageAttribute : filterAttribute, IActionFilter{
  public string Message {get;set;}
  public void OnActionExecuting(ActionExecutingContext filterContext){
  }
  public void OnActionExecuted(ActionExecutedContext filterContext){}
}

AttributeUsage Attribute의 AllowMultiple 속성을 true로 지정하고 있다.

[SimpleMessage(Message="A")]
[SimpleMessage(Message="B")]

위의 Attribute가 실행하는 순서는 랜덤이다! 하지만 이를 순서를 정해줄 수 있다.

[SimpleMessage(Message="A"), Order=1]
[SimpleMessage(Message="B"), Order=2]

라고 하면 A 먼저 실행되고 B가 실행된다. 그리고 Action이 끝날 때에는 반대로 B가 실행되고 A가 실행된다.

만약에 지정하지 않는다면 -1의 값이 할당된다.

필터 오버로딩하기

특정 필터를 전역으로나 컨트롤러 수준에서 적용하고자 하지만 특정 메서드에서는 다름 필터를 적용하고 싶은 경우도 있을 수 있다.

[AttributeUsage(AttributeTargets.Class |  AttributeTargets.Method, AllowMultiple=true)]
public class SimpleMessageAttribute : filterAttribute, IActionFilter{
  public string Message {get;set;}
  public void OnActionExecuting(ActionExecutingContext filterContext){
  }
  public void OnActionExecuted(ActionExecutedContext filterContext){}
}

로 변경해주면

[SimpleMessage(Message="A")]
public class Customercontroller : Controller {
  [SimpleMessage(Message="B")]
  public string otherAction(){}
}

를 했을 때 OtherActionMethod는 두개의 필터의 영향을 받는다.

  • 필터 재정의

만일 액션 메서드에 직접 적용된 필터의 영향만 받도록 제한하고 싶다면!

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method,Inherited = true, AllowMultiple = false)]
public class CustomOverrideActionFiltersAttribute : FilterAttribute, IOverrideFilter{
  public Type FiltersToOverride{
    get { return typeof(IActionFilter); }
  }
}

로 변경해주면

[SimpleMessage(Message="A")]
public class Customercontroller : Controller {
  [CustomOverrideActionFilters]
  [SimpleMessage(Message="B")]
  public string otherAction(){}
}

를 지정해주면 otherAction을 실행 시 SimpleMessage(Message="A") 의 Attribute 값은 실행하지 않는다!

Controller 확장성

Controller의 실행방법

요청 > Routing > Controller Factory > Controller > Action Invoker > Action Method > 동작
  • Controller Factory : 요청한 Controller의 Instance 생성
  • Action Invoker : 컨트롤러 클래스 내부에 정의되어 있는 Action Method를 찾아서 호출

Action Invoker Customizing

public class CustomActionInvoker : IActionInvoker{
  public bool InvokeAction(ControllerContext controllerContext, string actionName){
    if(actionName == "Index"){
      controllerContext.HttpContext.Response.Write("");
      return true;
    }
    else{
      return false;
    }
  }
}
public class ActionInvokerController: Controller {
  public ActionInvokerController(){
    this.ActionInvoker = new CustomActionInvoker();
  }
}

위와 같이 Action Invoker를 따로 생성하여 Customizing한 이후에 그것을 Controller 단에서 쓰는 예제이다.

Action name Customizing

[ActionName("Enumerate")]
public ViewResult List(){
  return View();
}

이면 Action의 이름을 Enumerate로 바꿀수있다.

기존의 url은 Customer/List이지만 Customer/Enumerate로 동작하며 List url이 동작한 것과 같은 결과를 출력한다.

NonAction

액션 메서드로 인식 안함, 노출이 되면 안되지만 public이어야하는 특별한 상황에서 사용

[NonAction]
public ViewResult List(){
  return View();
}

Action Method Customizing

public class LocalAttribute : ActionMethodSelectorAttribute{
  public override bool IsValidForRequest(ControllerContext c, MethodInfo m){
    return c.HttpContext.Request.IsLocal;
  }
}
[Local]
public ActionResult LocalIndex{
  return View();
}

Action Method 처리하는 방법

  1. Invoker는 Action Method 조건에 부합하는 Controller Method 목록을 가지고 처리를 시작한다.
  2. Invoker는 이름을 기반으로 대상 액션과 동일한 이름을 가지고 있거나 적절한 ActionName Attribute를 가진 메서드들만 목록에 유지한다.
  3. 호출자는 현재 요청에 대해서 false를 반환하는 Acton Method 선택자 Attribute를 갖는 모든 메서드를 배제한다.
  4. 선택자가 적용된 Action Method가 목록에 하나만 남아있다면 이 메서드가 사용된다. 하지만 선택자가 한개 이상이라면 식별 오류로 예외를 출력한다.
  5. 목록에 선택자가 적용된 액션 메서드가 존재하지 않는다면 선택자가 적용되지 않은 메서드들을 살펴본다. 만일 그러한 메서드가 하나만 존재한다면 호출되고 하나 이상 존재한다면 예외를 호출한다.

+) 404 Error 처리할때 좋을 듯!!!

알 수없는 Action 다루기

Controller안에

protected override void HandleUnknownAction(string actionName){
  Response.Write("");
}

를 넣어놓으면 존재하지 않는 Action Method를 요청하면 오류 페이지가 뜬다

Controller의 성능 향상 방법

  1. Controller에 Session을 끈다 (필요한 경우)

기본적으로 Controller는 프로그래머들이 좀 더 편하게 작업할 수 있도록 요청에 걸쳐 데이터 값들을 저장하기 위해서 사용되는 세션 상태를 제공해준다. ASP.NET은 세션 상태를 단순화 하기 위해서 주어진 세션에 대해 한 번에 오직 하나의 질의만 처리해 클라이언트가 중복 요청을 보내오면 일단 큐에 놓여지고 순차적으로 처리된다.

그러나 모든 Controller가 Session state 기능을 사용해야하는 것은 아니다. 따라서 이러한 기능을 피하고 성능을 향상시킬 수 있다.

구현하기

public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName){
  switch(controllerName){
    case "Home":
      // 읽기 전용
      return SessionStateBehavior.ReadOnly;
    default:
      // 기본 동작
      return SessionStateBehavior.Default;
  }
}
// 읽기 쓰기
// SessionStateBehavior.Required

내장된 함수 사용하기

[SessionState(SessionStateBehavior.Disable)]
public ActionResult Index(){
  return View();
}
  1. Controller를 비동기로 만든다.

ASP.NET은 클라이언트의 요청을 처리하기 위해 사용되는 .NET Thread Pool 즉 worker Thread Pool이 존재하며 각각의 Thread는 Worker Thread라고 부른다.

요청이 전달되면 pool에서 작접자 스레드가 가져와지고 요청을 처리하기 위한 작업을 할당 받은 후 처리 후 다시 pool로 반환되어 새로운 요청을 위해 대기한다.

하지만 모든 작업자 스레드가 다른 시스템이 끝나기를 기다리게 되는 상황을 직면할 수 있다. 즉 서버는 한가한 반면, 응용프로그램은 죽기 일보 직전이 상태에 놓일 수 있다.

비동기 컨트롤러

public async Task<ActionResult> Data(){

  string data = await Task<string>.Factory.StartNew(() => {
    return new RemoteService().GetRemoteData();
  });

  return View((object)data);
}
public class RemoteService{

  public async Task<string> GetRemoteDataAsync(){

  return await Task<string>.Factory.StartNew(()=>{
    Thread.Sleep(2000);
    return "";
    });

}

이렇게 하면 작업자가 GetRemoteDataSync를 끝낼때 까지 묶여 있지 않아도 원하는 결과를 얻을 수 있다.

View Engine Customizing

Shared 폴더 변경하기

{0} : View
{1} : Controller
{2} : 영역

public class CustomLocationViewEngine:RazorViewEngine{
  public CustomLocationViewEngine(){
    ViewLocationFormats = new string{}{
      "~/Views/{1}/{0}.cshtml","~/Views/Common/{0}.cshtml"
    }
  }
}
public void Application_Start(){
  ViewEngines.Engines.Clear();
  ViewEngines.Engines.Add(new CustomLocationViewEngine());
}

View 동적 데이터 보여주기

Section

section을 생성하고

@section Header{}
@section Footer{}

Section을 Rendering한다

@RenderSection("Header");
Section이 정의되었는지 확인하는 방법
  1. if문
@if(IsSectionDefined("Footer")){
  @RenderSection("Footer");
}else{
  //
}
@RenderSection("scripts",false);
// 만약 없더라도 예외가 발생하지 않는다.

Helper Method

//을 Model에서 설정하면 그 값은 Input text가 아닌 그냥 정적인 데이터 형식으로 보여짐
[HiddenInput]

// 얜 보이지도 않음
[HiddenInput(DisplayValue=false)]

Bundle

CSS Script 파일을 한개로 묶음

BundleConfig

bundle.Add(new ScriptBundle("~/bundles/clientfeaturessripts").
Include("~/Scripts/jquery-{version}.js"));

Application_Start

BundleConfig.RegisterBundle(BundleTable.Bundles);

Web.config

<namespace>
<add namespace ="bundle이 있는 곳의 namespace"/>
</namespace>

cshtml

@Style.Render("~/Content/css");
@Scripts.Render("~/bundle/clientfeaturessripts")

React

Aiden

Zoe

Gini

Clone this wiki locally