잊지 않겠습니다.

Data Binding : 규약을 이용하여 데이터 Entiry를 다루는 MVC Framework의 기능.

Model Binding
: HTML form의 submit시에 응용프로그램은 key/value pair로 폼의 데이터를 담고 있는 HTTP response를 받게 된다.
이때에, Http 요청 데이터를 Action method의 매개변수 및 사용자 정의 .NET 객체와 직접적으로 mapping하기 위한 알고리즘을 제공한다.

Model Binding 시의 데이터 적용 순서
1) Form(POST 매개변수 : FormCollection)
2) RouteData
3) QueryString

사용자 정의 형식에 대한 Model Binding
: 기본적으로 DefaultModelBinder는 {parameterName.PropertyName}으로 검색을 한다.
(* 대소문자는 가리지 않는다.)

다음과 같은 View가 존재한다고 할때에, 대응되는 ActionMethod는 다음과 같다.

New Product

<%=Html.ValidationSummary() %> <%using(Html.BeginForm()) { %> Name : <%=Html.TextBox("productValue.name") %>
Price : <%=Html.TextBox("productValue.price")%>
Description : <%=Html.TextBox("productValue.description")%>
<%} %>

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult New(Product productValue)
{
    try
    {
        productValue.CheckValidation();
    }
    catch(ProductException ex)
    {
        foreach(string key in ex.Keys)
        {
            ModelState.AddModelError(key, ex[key]);
        }                
        return View();
    }

    return RedirectToAction("NewResult",
        new { product = productValue });
}

BindAttribute의 이용
Bind Attribute는 Action Method의 매개변수 이름이 아닌 다른 이름으로 Binding을 원하거나, 특정 속성들이 모델 바인딩의 대상이 되어야하는지를 엄밀하게 제어할 필요가 있을 때 사용된다.

/// 
/// 다음 ActionMethod는 productA.*, productB.* 로 prefix된 view의 id를 이용해서 
/// 값을 Binding한다.
/// 
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult New([Bind(Prefix = "productA")] Product product1, 
    [Bind(Prefix = "productB")]Product product2)
{

}
/// 
/// 다음 ActionMethod는 Name과 Price를 Binding에 포함한다.
/// 
/// 
/// 
public ActionResult RegisterMember([Bind(Include = "Name, Price")] Product product)
{

}
/// 
/// 다음 ActionMethod는 DateOfBirthDay를 Binding하지 않는다.
/// 
/// 
/// 
public ActionResult DeregisterMember([Bind(Exclude = "DateOfBirthDay")] Product product)
{

}

[Bind(Include="Name")]
public class Product
{        
    public string Name { get; set; }
    public string Price { get; set; }
    public string Description { get; set; }
}

Array, Collection, Dictionary에 대한 Model Binding
: 동일한 이름을 갖는 여러개의 Textbox를 Render하는 View의 Controller는 다음과 같이 사용될 수 있다.

<%=Html.TextBox("movies") %>
<%=Html.TextBox("movies") %>
<%=Html.TextBox("movies") %>
public ActionResult DoSomething(IList movies) { throw new NotImplementedException(); }


사용자 정의 Entry의 Group에 대해서는 다른 방법이 필요하게 되는데, DefaultModelBinder는 ActionMethod의 사용에 C#의 문법과 동일한 명명 규약을 따르기를 요구한다. 다음과 같은 ActionMethod가 존재할 때에, 사용되는 View는 다음과 같다.

public ActionResult DoSomething(IList products)
{
    foreach(Product product in products)
    {
        System.Diagnostics.Debug.WriteLine(product.Name);
    }
    throw new NotImplementedException();
}


<%using(Html.BeginForm("DoSomething", "Products")) { %>
    <%for(int i = 0 ; i < 10 ; i++) { %>
        

<%=string.Format("Product : {0}", i) %>

<%--C#에서 사용되는 배열 문법과 동일하다.(products[0].Name, products[0].Price)--%> Name : <%=Html.TextBox("products["+i.ToString()+"].Name") %>
Price : <%=Html.TextBox("products["+i.ToString()+"].Price") %>
Description : <%=Html.TextBox("products["+i.ToString()+"].Description") %>
<%} %> <%} %>

사용자 지정 Model Binder의 지정
: 특정한 데이터에 대하여 사용자 지정 Model Binder를 사용하고 싶은 경우에는 IModelBinder interface를 상속한 사용자 지정 ModelBinder를 만들어주면 된다.



Model Binder가 사용되도록 구성하기 위해서 3가지 방법중 하나를 사용할 수 있다.
1) Model에 ModelBinderAttribute의 적용
[ModelBinder(typeof(XDocumentBinder))]
public class XDocument
{
    //...
}
2) ModelBinders.Binders.Add를 이용, 전역 ModelBinder에 등록하기
ModelBinders.Binders.Add(typeof(XDocument), new XDocumentBinder());
3) 매개변수를 Binding할때, Parameter attribute를 넣어주기
public ActionResult DoSomething([ModelBinder(typeof(XDocumentBinder))] XDocument xml)
{
    //...
}

.NET MVC Framework에서 ModelBinder Select 순위
1) Binding시에 명시적으로 지정된 Binder
2) 대상 형식을 위해 ModelBinders.Binders에 등록된 Binder
3) 대상 형식에 ModelBinderAttribute를 사용하여 할당된 Binder
4) DefaultModelBinder


Posted by Y2K
,
Javascript가 동작하지 않는 브라우져나 서버단에 치명적인 오류가 들어갈 수 있는 값의 경우에는 Server단에서의 Validation Check가 반드시 필요하게 된다. 전에 사용했던 IDataError interface의 경우에는 데이터 모델들의 처리가 되는데. 이는 일반적인 C#에서의 에러처리가 아닌 ASP .NET MVC에서의 Validateion 처리라고 조금은 한정지어서 생각할 수 있다.

생각해보면 C# 언어에 가장 맞는 Validation Check는 try~catch 를 이용한 방법이다. 이러한 방법을 이용해야지만이 만약 Entity를 다른 환경, 예를 들어 SilverLight에서 Entity를 표현하거나 WinForm으로 Entity를 표현할 때에 정확한 Validation Check가 가능하게 된다.

다음은 Product에 대한 Validation check code이다.

public class Product
{        
    public string Name { get; set; }
    public string Price { get; set; }
    public string Description { get; set; }

    public void CheckValidation()
    {
        ProductException exception = new ProductException();
        if(string.IsNullOrWhiteSpace(Name))
        {
            exception.Add("name", "Name is empty");
        }

        if(string.IsNullOrWhiteSpace(Description))
        {
            exception.Add("description", "Description is empty");
        }

        if(string.IsNullOrEmpty(Price))
        {
            exception.Add("Price", "Price is empty");
        }

        if(!exception.IsValid)
        {
            throw exception;
        }
    }
}

public class ProductException : Exception, IDictionary
{
    public bool IsValid { get { return _exceptMessages.Count == 0; } }
    private Dictionary _exceptMessages;
    public ProductException()            
    {
        _exceptMessages = new Dictionary();
    }

    #region IDictionary Members

    public void Add(string key, string value)
    {
        _exceptMessages.Add(key, value);
    }

    public bool ContainsKey(string key)
    {
        return _exceptMessages.ContainsKey(key);
    }

    public ICollection Keys
    {
        get { return _exceptMessages.Keys; }
    }

    public bool Remove(string key)
    {
        return _exceptMessages.Remove(key);
    }

    public bool TryGetValue(string key, out string value)
    {
        return _exceptMessages.TryGetValue(key, out value);
    }

    public ICollection Values
    {
        get { return _exceptMessages.Values; }
    }

    public string this[string key]
    {
        get
        {
            return _exceptMessages[key];
        }
        set
        {
            _exceptMessages[key] = value;
        }
    }

    #endregion

    #region ICollection> Members

    public void Add(KeyValuePair item)
    {
        _exceptMessages.Add(item.Key, item.Value);
    }

    public void Clear()
    {
        _exceptMessages.Clear();
    }

    public bool Contains(KeyValuePair item)
    {
        return _exceptMessages.Contains(item);
    }

    public void CopyTo(KeyValuePair[] array, int arrayIndex)
    {
        throw new NotImplementedException();
    }

    public int Count
    {
        get { return _exceptMessages.Count; }
    }

    public bool IsReadOnly
    {
        get { throw new NotImplementedException(); }
    }

    public bool Remove(KeyValuePair item)
    {
        return _exceptMessages.Remove(item.Key);
    }

    #endregion

    #region IEnumerable> Members

    public IEnumerator> GetEnumerator()
    {
        return _exceptMessages.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return _exceptMessages.GetEnumerator();
    }

    #endregion
}
Product을 사용하는 Controller의 예
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult New(Product productValue)
{
    try
    {
        productValue.CheckValidation();
    }
    catch(ProductException ex)
    {
        foreach(string key in ex.Keys)
        {
            ModelState.AddModelError(key, ex[key]);
        }                
        return View();
    }

    return RedirectToAction("NewResult",
        new { product = productValue });
}

Posted by Y2K
,
View는 WebApplication의 Request가 Response로 바뀌는 BlockBox가 된다.

MVC에서 View는 완전한 출력을 담당하는 책임을 가지고 있다. 단지 Controller의 출력만을 담당하며, 간결한 표현 로직을 사용하여 그 출력을 최종 HTML로 Render하는 임무를 가진다. 그렇지만, 표현로직과 업무로직사이의 경계는 매우 모호하다. 업무로직과 표현 로직에 대한 것은 조금은 주관적이라고 생각이 되기 때문에.. 최대한 Controller에서 동작을 하고 표현 부분에 대해서는 로직의 최대한도로 줄이는 것을 목표로 하는 것이 가장 좋다.
또한, View의 문제는 Test에 있다. Html을 테스트하는 것은 GUI 프로그램에서 사용자 액션을 테스트하는 것과 동일하다. 테스트 할 방법도 마땅하지 않으며, 표시된 HTML을 TEXT단위로 테스트 하는 것은 공백 문제부터 시작해서 많은 어려움이 있기 때문에, 많은 책들의 저자와 비슷하게 VIEW는 봐서 잘 되는 것이 최고다. 라는 생각이 든다. 딱히 테스트 할 방법도 안보이는 것이 사실이다.;

WebForm View Engine
: MVC Framework는 WebFormViewEngine이라는 class로 구성된 View Engine을 사용하고 있다. 이 ViewEngine은 기존의 ASP .NET WebForm Engine을 기반으로 구축되어있다. 그러나 WebFormViewEngine에서 View는 단순한 HTML Template이다. 이는 Controller에서 받은 특정 데이터인 ViewData와 강력히 형식화된 Model 객체를 기반으로 만들어진 HTML 문자를 표현하는 기능만을 가지게 된다. 또한 내부적으로 사용되는 기반 기술은 ASP .NET WebForm 서버 페이지 기술과 동일하다.만약에 자체적인 View Engine을 만들고 싶다면, IViewEngine과 IView interface를 상속받아 구현하면 된다.

View의 데이터 전송
: ViewData라는 Dictionary<string, object> 형식의 데이터 또는 강력한 형식의 Model을 이용하는 방법이 있다. 두 방법의 차이는 Controller에서는 전혀 알 필요가 없다. 단지 Controller에서 데이터 형식의 Model을 전송해서 View로 전달 될때, 강력한 Model 형식의 View의 경우에는 형변환이 가능한지만을 알아보게 된다. 따라서 이 방법은 매우 유용한 것이 된다.

ViewData.Eval()의 이용
: ViewData.Eval()은 대소문자를 가리지 않고, 값이 없는 경우에 String.Empty를 리턴하는 사용하기 편한 함수이다. 또한 Eval함수는 .으로 구분되는 토큰 명명법을 사용하고 있기 때문에 Model의 속성또한 표현할 수 있다. ViewData에 key를 Name으로 넣었는지, name으로 넣었는지. 아니면 값이 없는 경우에 NULL처리를 해주는 것을 빼먹지 않았는지 신경쓰지 않고 사용하기에 매우 좋은 함수이다. 또한 Html Helper method에서 Html.TextBox("comment")를 사용하는 경우, 이 코드는 다음과 같다. <input name="comment" id="comment" type="text" value="<%=Html.Encode(ViewData.Eval("comment"))%>"/>. 편하게 작성도 가능하고, 값이 없는 경우에 나오는 모든 에러처리를 해주기 때문에 간결한 코드 작성이 가능하다.
Posted by Y2K
,
모든 Controller는 IController interface를 구현한다.

* Mvc.Controller의 특징
1. ActionMethods
  : Controller의 동작이 여러개의 method로 분할된다. 각 Action method는 서로다른 URL로 노출되며,
  들어오는 요청에서 추출된 매개변수를 가지고 있다.
2. ActionResult
  : Action의 의도된 결과를 나타내는 개체를 선택해서 반환할 수 있다. 결과를 지정하는 것과 실행하는
  것이 분리되기 때문에 자동화된 테스트가 상당히 간단하게 구성될 수 있다.
3. Filter
  : 재사용 가능한 동작들을 Filter로 캡슐화할 수 있다.

* Controller의 입력 처리
Mvc.Controller에서 사용 가능한 속성들
1. Request.QueryString : 요청과 함께 전송된 GET 변수들
2. Request.Form : 요청과 함께 전송된 POST 변수들
3. Request.Cookie : 요청과 함께 전송된 Cookie
4. Request.HttpMethod : 요청을 위해 사용된 Http method
5. Request.Headers : 요청과 함께 전송된 HTTP Header
6. Request.Url : 요청된 Url
7. Request.UserHostAddress : 요청을 보내온 사용자의 Ip Address
8. RouteData.Route : 요청에 대해 선택된 RouteTable.Routes Entry
9. RouteData.Values : 현재의 Route 매개 변수(URL에서 추출된 값이거나 기본 값)
10. HttpContext.Application : Application state 저장소
11. HttpContext.Cache : Application cache 저장소
12. HttpContext.Items : 현재 요청에 대한 상태저장소
13. User : 로그인 한 사용자의 인증정보
14. TempData : Session 내에서 이전 HTTP 요청을 처리하는 동안에 저장된 임시 Data

* Controller의 출력 처리
a. View를 Rendering하는 것으로 HTML 반환
b. HTTP 재전송 호출(311, 312 code 반환)
c. 응답의 출력 스트림에 다른 데이터 전송(xml, json, file)

* Action method 안에서의 직접적으로 Response를 다루는 일은 절대로 하지 않아야지 된다. 대신에 특정한 응답을 나타낼 수 있는 ActionResult를 상속한 객체를 반환하도록 한다.

* ActionResult 형식들
1. ViewResult : 지정된 View Template이나 기본 View Template을 Rendering 한다. [Mvc.Controller.View() method]
2. PartialViewResult : 지정된 Partial View Template이나 기본 Partial View Template을 Rendering한다.(Mvc.Controller.PartialView()]
3. RedirectToRouteResult : Routing 구성 설정에 따라 URL을 생성하면서 302 재전송을 하게 한다.[Mvc.Controller.RedirectToAction()]
4. RedirectResult : 임의의 URL로 HTTP 302 재전송을 하게 한다. RedirectToRouteResult와 다른 점은 RedirectResult는 외부의 URL을 사용가능하다는 차이점을 가지고 있다.
5. ContentResult : 브라우저로 text 데이터를 반환할 수 있다. 선택적으로 Content Header를 지정 가능하다.
6. FileResult : 이진 데이터를 브라우저로 직접 전달한다.
7. Jsonresult : .NET 객체를 Json 포멧으로 직렬화하고 이를 응답으로 전송한다.
8. JavascriptResult : 브라우저에 의해 실행될 수 있는 javascript 소스코드의 일부를 전송한다.
9. HttpUnauthorizedResult : Http 응답코드를 401로 설정한다.
10. EmptyResult : 아무 일도 하지 않는다.

사용자 지정 ActionResult를 만들고 싶은 경우(Image를 가공해서 반환한다던지 등의 Action이 가능하다)에는 ActionResult를 상속받아서 ExecuteResult를 재정의해서 사용하면 된다. 다음은 그 예제 코드이다.


public class WatermarkedImageResult : ActionResult
{
    public string ImageFileName { get; private set; }
    public string WatermarkText { get; private set; }

    public WatermarkedImageResult(string imageFileName, string watermarkText)
    {
        ImageFileName = imageFileName;
        WatermarkText = watermarkText;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        using(var image = Image.FromFile(ImageFileName))
        using(var graphics = Graphics.FromImage(image))
        using(var font = new Font("Arial", 10))
        using(var memoryStream = new MemoryStream())
        {
            var textSize = graphics.MeasureString(WatermarkText, font);
            graphics.DrawString(WatermarkText, font, Brushes.White, 10, image.Height - 10 - textSize.Height);

            image.Save(memoryStream, ImageFormat.Png);
            var response = context.RequestContext.HttpContext.Response;
            response.ContentType = "image/png";
            response.BinaryWrite(memoryStream.GetBuffer());
        }
    }
}

* TempData와 Session과의 비교
기본적으로 TempData의 저장소는 Session이 된다. 그렇기 때문에 TempData를 사용하고 싶으면 반드시 Session을 enable 시켜야지 된다. 하지만 TempData의 고유한 특징은 이것이 매우 작은 데이터라는 것이다. 각 entry는 오직 한번의 이전 요청을 저장하고 난 다음 없어진다. 처리 후에 자동으로 청소가 되니 RedirectToAction()에 걸쳐 개체를 유지하는데 적합하다.

* 재 사용가능한 Filter 처리하기
Filter를 이용해서 Controller와 ActionMethod에 추가적인 동작을 지정할 수 있다.
Filter는 요청 처리 PipeLine에 별도의 단계를 추가하는 .NET Attribute로 Action method의 실행 전후에, ActionResult가
실행 전후에 추가적인 로직을 삽입할 수 있다.

Action method의 실행 전후는 IController.Execute method의 실행 전, 후를 의미하며,
ActionResult의 실행 전후는 ActionResult.ExecuteResult method의 실행 전, 후를 의미한다.
따라서 실행 순서는 OnActionExecuting, OnActionExecuted, OnResultExecuting, OnResultExecuted 순서로 실행된다.

ex) HandleError : HandleErrorAttribute는 Controller의 Action method가 실행될 때에 예외가 발생되면 HandleError에 지정된 View가 Render된다.이때에 web.config에 customError mode가 on이 되어있어야지 된다. 이때 지정된 View는 HandleErrorInfo data model이 전달되며, 이는 불려진 Controller와 ActionName, Exception의 정보를 담게 된다. 이 상태는 Error를 처리된 상태로 만들게 되며 따라서 Http 응답코드는 에러가 아닌 200이전달되게 된다.


Controller Test Code
* Controller의 Unit Code는 의미있는 단위 테스트를 작성하기 위해서 많은 사람들이 AAA Pattern을 따른다. Arrange-Act-Assert로 이루어지는
이 방법은 대부분의 Controller 테스트에 유용하다.

[TestClass]
public class HomeControllerTest
{
    [TestMethod]
    public void CheckViewName()
    {
        HomeController controller = new HomeController();
        ViewResult viewResult = controller.Index();

        Assert.IsNotNull(viewResult);
        Assert.AreEqual(viewResult.ViewName, "index");
    }

    [TestMethod]
    public void CheckViewData()
    {
        HomeController controller = new HomeController();
        ViewResult viewResult = controller.Index();

        Assert.IsNotNull(viewResult);
        Assert.AreEqual(6, viewResult.ViewData["age"]);
    }

    [TestMethod]
    public void CheckRedirection()
    {
        HomeController controller = new HomeController();
        RedirectToRouteResult result = controller.Index();

        Assert.IsNotNull(result);
        Assert.AreEqual("RedirectActionName", result.RouteValues["action"]);
    }
}
Posted by Y2K
,
DomainModel은 모두다 구성이 마쳐진 상태로 가정하고 MVC에 구성하는 방법에 대해서 알아본다.

먼저, web.config에 configSections에 castle속성과 IRepository에 대한 속성을 등록하고, httpModules에 Lifestyle에 대한 module을 등록한다.


	
		
Data Source=HOME-PC\SQLEXPRESS;Initial Catalog=SportsStore;Integrated Security=True;Pooling=False

다음, WindsorControllerFactory를 생성한다. WindsorControllerFactory는 DefaultControllerFactory를 상속하며, ControllerInstance를 저장하고 반환하는데 사용된다.

public class WindsorControllerFactory : DefaultControllerFactory
{
    private WindsorContainer container;

    public WindsorControllerFactory()
    {
        container = new WindsorContainer(new XmlInterpreter(new ConfigResource("castle")));
        var controllerTypes = from t in Assembly.GetExecutingAssembly().GetTypes() 
                              where typeof(IController).IsAssignableFrom(t) select t;
        foreach(Type t in controllerTypes)
        {
            container.AddComponentWithLifestyle(t.FullName, t, LifestyleType.Transient);
        }
    }

    protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
    {
        return (IController)container.Resolve(controllerType);
    }
}
마지막으로 Controller의 생성자를 이용해서 사용될 DomainModel의 interface를 등록하는 것으로 완료할 수 있다.
public class ProductController : Controller
{
    private IProductRepository _productRepository;
    public ProductController(IProductRepository productRepository)
    {
        _productRepository = productRepository;
    }


    public ViewResult List()
    {
        return View(_productRepository.Products.ToList());
    }
}

상당히 간단한데 왜 외워지지가 않는 것일까.; Castle에 대해서는 두번째 포스트인데 포스트 내용이 비스무리하다. -_-
Posted by Y2K
,
.NET Framework 4.0Beta2 로 작성된 application들은 NUnit에서 테스트가 불가능하다. NUnit는 .NET 2.x대의 base assembly만이 테스트가 가능하도록 작성되어 있기 때문이다.

그래서 MS의 xUnit test 를 이용해서 RouteData Test Code를 작성해보면 다음과 같다.
먼저, 가장 기본적인 RouteCollection을 생성하고, 테스트할 MVC application의 Route를 등록시킨다.
그리고 RouteCollection.GetRouteData(HttpBaseContext)를 이용해서 들어온 HttpBaseContext에 대한 RouteData를 확인한다.


[TestMethod]
public void TestMethod1()
{
    RouteCollection routeConfig = new RouteCollection();
    RouteApp.MvcApplication.RegisterRoutes(routeConfig);    
    RouteData routeData = routeConfig.GetRouteData(HttpBaseContext);
}


문제는 HttpBaseContext를 생성하는 것인데. Mock을 이용하면 간단하게 해결 할 수 있다.
다음 코드는 Mock을 이용해서 HttpBaseContext를 생성하는 코드이다.


private static Mock MakeMockHttpContext(string url)
{
    var mockHttpContext = new Mock();

    var mockRequest = new Mock();
    //HttpContext에 Request 설정
    mockHttpContext.Setup(x => x.Request).Returns(mockRequest.Object);
    //HttpRequest의 relative url로 사용자 지정 url 설정
    mockRequest.Setup(x => x.AppRelativeCurrentExecutionFilePath).Returns(url);

    var mockResponse = new Mock();
    //HttpContext에 Response 설정
    mockHttpContext.Setup(x => x.Response).Returns(mockResponse.Object);
    //HttpResponse에서 사용자 Url을 그냥 pass시키도록 ApplyAppPathModifier 수정
    mockResponse.Setup(x => x.ApplyAppPathModifier(It.IsAny())).Returns(x => x);            

    return mockHttpContext;
}


최종적으로 완성된 코드는 다음과 같다.

[TestMethod]
public void TestMethod1()
{
    RouteCollection routeConfig = new RouteCollection();
    RouteApp.MvcApplication.RegisterRoutes(routeConfig);

    var mockHttpContext = MakeMockHttpContext("~/");
    RouteData routeData = routeConfig.GetRouteData(mockHttpContext.Object);

    Assert.IsNotNull(routeData.Route);            
    Console.WriteLine(routeData.Values.ToString());
}
Posted by Y2K
,
: RouteBase, Route, RouteCollection

응용프로그램은 하나의 System.Web.Routing.RouteTable.Routes를 가지며, 응용프로그램의
실제 동작하는 Routing 구성 설정을 갖는다.

ASP.NET MVC에서 구성되는 Global.asax에서 application_start()로 구현이 된다.


Route의 동작

1. Url 요청
2. UrlRoutingModule module 호출(System.Web.Routing)
3. System.Web.Routing.RouteTable.Routes 안에서 요청과 일치하는 RouteBase 객체 반환
4. RouteData 구조체 확인
   1) RouteHandler : 요청을 처리할 객체 (ASP .NET MVC에서는 MvcRouteHandler의 instance가 된다.)
   2) RouteValueDictionary : 요청에서 추출된 중괄호 매개변수의 이름과 값의 dictionary
   3) Route : RouteBase에 대한 참조 객체
   4) DataTokens : Routing Entry에 의해 제공되는 모든 추가적인 구성 설정 옵션의 Dictionary
5. RouteData의 RouteHandler 호출
   : RouteHandler에 RouteData, Http Header, Cookie, Auth, Query string, post data를 포함한 모든 HttpContextBase전달

* Route Entry의 순서는 매우 중요하다. System.Web.Routing.RouteTable.Routes는 정렬된 목록이 제공되며,
  이는 Global.asxs 파일안에 정의되어있는 순서대로 구성이 되게 된다. 보다 명확한 매치에 해당되는 Route를
  먼저 등록시켜야지 된다.

* Server의 HDD에 있는 경우에는 언제나 그 정적인 파일이 우선적으로 호출된다. Routing 구성 설정보다
  우선권을 갖게 된다.


기본 Routing 작성


Route defaultRoute = new Route("{controller}/{action}/{id}", new MvcRouteHandler())
{                
    Defaults = new RouteValueDictionary(new { controller = "Home", action = "index", id = "" })
};
routes.Add("default", defaultRoute);

routes.Add(new Route("Catalog/{color}", new MvcRouteHandler())
{
    Defaults = new RouteValueDictionary(
        new { controller = "Products", action = "List", color=(string)null }
        )                
});


제약사항이 있는 Routing 작성
: Constraint를 이용. 제약 사항을 추가적으로 만들어주기 위해서는 System.Web.Routing.IRouteConstraint를
  상속받는 Class를 제공해서 RouteValueDictionary에 넣어주면 된다.


routes.Add(new Route("Article/{id}", new MvcRouteHandler())
{
    Defaults = new RouteValueDictionary(new { controller = "Articles", action = "Show" }),
    Constraints = new RouteValueDictionary(
            new
            {
                id = @"\d{1,6}", //ID 값이 항시 정규표현식 안에 일치하는지 제약조건 강제화.
                httpMethod = new HttpMethodConstraint("GET"), //항시 GET method로만 call이 가능
                userAgent = new UserAgentConstraint("iPhone") //iPhone web agent로만 접근 가능 
            }
        )
});

public class UserAgentConstraint : IRouteConstraint
{
    private string _requestSubString;
    public UserAgentConstraint(string requestSubString)
    {
        _requestSubString = requestSubString;
    }

    #region IRouteConstraint Members

    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if(httpContext.Request.UserAgent == null)
            return false;
        return httpContext.Request.UserAgent.Contains(_requestSubString);
    }

    #endregion
}


* 현재 사용의 매개변수는 재사용될 수 있다는 점을 염두해둬야지 된다.이 때 고려될 것은 매개변수보다
  앞쪽에 나오는 매개변수에 대한 값들만이 재사용된다.
  routes.MapRoute(null, "{controller}/{action}/{color}/{page}");  의 경우에,
  <%=Html.ActionLink("ClickMe", "List", "Catalog", new {page=789}, null)%>로 구성이 될 경우에는 color
  값은 ActionLink가 호출이 되는 현재 요청의 color 값이 사용이 된다. 그러나,
  <%=Html.ActionLink("ClickMe", "List", "Catalog", new {color="black"},null%>로 구성이 될 경우에는
  page값이 재사용되지 못하고 route entry는 일치되지 못한다.


Posted by Y2K
,
설치 이후.. 이상할 정도로 UI가 전보다 빠르다.;

Win7에 최적화가 되어있나? 라는 생각도 들고. UI쪽에 실버라이트와 DirectX 기술이 들어가있다는 말은 들었는데. 그것 때문인가 라는 생각도 들고.;

무엇보다 설치 이후에 충격적인 내용은..;;

다음과 같은 코드 실행의 결과가 아래와 같이 나왔다는 점이다.;


class Program
{
static void Main(string[] args)
{
string runtimeEnvironment = RuntimeEnvironment.GetRuntimeDirectory();
Console.WriteLine(runtimeEnvironment);
}
}


CLR의 버젼이 완전히 바뀌었다.;;; 3.x대를 완전히 넘어가서 바로 4.0.21 대로 진입.
.NET Framework의 새로운 장이 열렸다고 해도 과언이 아닌 변화가 이루어졌다;;

Powershell과의 통합은 어찌된걸까?; 다른 .NET과의 통합은? ;;;
Posted by Y2K
,
1. IIS
* IIS의 pipe line mode
> ISAPI mode : class mode라고도 불리우며, 이는 file의 확장자를 기준으로 ISAPI 확장에 의하여 처리된다.
> Integrated mode : .NET이 pipe line의 일부이기 때문에 특정 file의 확장자와 관련된 어떠한 ISAPI mode가 필요하지 않는다.

System.Web.Routing.UrlRoutingModule 에 의하여 Url의 Routing이 처리가 된다.
이는 Url의 모든 파일이나 내용이 .NET에 의하여 관리되는 것을 의미한다.

2. Routing
System.Web.Routing.UrlRoutingModule은 요청을 받는 즉시 System.Web.Routing을 실행한다.
Routing의 역활은 먼저, Disk의 파일과의 연관성을 찾아내고 파일과의 연관성이 없는 경우, URL 패턴을 인식하고 분석하여 내부 Component가 사용할 수 있는 Request context 데이터 구조를 구성한다. 파일과의 연관성이 발견되는 경우, 파일에 대하여 실행이 되게 된다. 정적 resource 파일들(*.jpg와 같은 그림 파일이나 html 파일들)에 대하여 제공 및 aspx 파일에 대한 WebForm 실행이 이루어진다.
Routing 구성은 System.Web.Routing.RouteTable이라는 static collection안에 놓여진다. 그 Collection안의 entry는 서로 다른 URL pattern을 표현하는데 이 부분의 경우에는 Global.asax 파일안에 RegisterRoutes() method로 사용자가 구성이 가능하다.

3. Controller & Action
기본적으로 MvcRouteHandler라는 Routing 처리기를 이용해서 구성이 되며, Routing 결과에 따라 ControllerFactory에서 Controller를 생성하게 된다. 이 과정에서 MvcRouterHandler는 MVC Controller를 찾아내고, Action method의 매개변수를 제공한다. 이 과정은 Assembly reflection을 이용하고 있으며 모든 IController interface를 가지고 이름이 *Controller로 끝이 나는 모든 public class를 ControllerFactory에 등록하고, 들어온 URL 결과에 따라 특정 Controller를 실행하고, 그에 맞는 Action method를 실행한다. 따라서, Controller에서 직접 Response를 제어할 수 있으나, MVC에서는 이를 권장하지 않는다. MVC에서는 계획된 출력을 의미하는 ActionResult 객체를 반환하는 것이 권장된다.

4. ActionResult & View
Controller에서 제공된 ActionResult를 이용하여 View를 Render하는 과정이다. 기본적으로 View의 Render는 IViewEngine을 구현하는 .NET class를 통해서 구현되는데 기본적으로 WebFormViewEngine을 사용한다. 이름을 보면 알 수 있듯이 이는 기존의 WebForm이 Render 될 때 사용되는 View Engine과 동일하다. 그러나 MVC의 '관계의 분리' 개념에 따라 View는 HTML을 생성하는 것 이외에는 어떠한 일을 하지 않는다.
 

Posted by Y2K
,
.NET MVC 및 모든 Component Model에서 Entry의 에러를 표시하는데 사용되는 Interface.
인터페이스는 각 Property에 대한 값을 검사할 수 있도록 this[columnName]에 대한 정의와 객체의 에러 상태를 알아볼 수 있는 Error 라는 string property를 지원한다.

여기에서 각 에러값은 null이 반환될 때, 에러가 없다고 가정이 되며, String.Empty가 반환되는 경우에도 에러라고 인식하게 된다.(ASP.NET MVC)

사용예는 다음과 같다.

[Table(Name="Product")]
public class Product : IDataErrorInfo 
{
    [Column(IsPrimaryKey=true, IsDbGenerated=true, AutoSync=AutoSync.OnInsert)]
    public int ProductId { get; set; }
    [Column]
    public string Name { get; set; }
    [Column]
    public string Description { get; set; }
    [Column]
    public decimal Price { get; set; }
    [Column]
    public string Category { get; set; }

    #region IDataErrorInfo Members

    public string Error
    {
        get { return null; }
    }

    public string this[string columnName]
    {
        get
        {
            if((columnName == "Name") && string.IsNullOrEmpty(Name))
                return "Please enter a product name";
            if((columnName == "Description") && string.IsNullOrEmpty(Description))
                return "Please enter a description";
            if((columnName == "Price") && (Price <= 0))
                return "Price must not be negative or zero";
            if((columnName == "Category") && string.IsNullOrEmpty(Category))
                return "Please specify a category";
            return null;
        }
    }

    #endregion
}
Posted by Y2K
,