NHibernate and ASP.NET MVC - CRUD Operations


참조 URL
  1. http://www.dotnetjalps.com/2013/09/asp-net-mvc-nhibernate-crud-getting-started.html
  2. http://www.dotnetjalps.com/2014/07/fluent-nhibernate-asp-net-mvc-crud.html

 


이 문서에 대하여

이 문서는 원본 문서를 읽고, 따라하고, 변경 또는 수정없이 인용한 부분이 있으며 주석이나 추가 설명에 대해 가감을 하였습니다. 이 문서와는 별개로 별도의 원본이 있음을 알려 드리며 원본에 대한 자세한 사항은 참조 URL에 있음을 알려 드립니다. 오역, 어색한 부분, 매끄럽지 않은 부분이 있을 경우 알려주시면 적극적으로 반영하도록 하겠습니다.





   실습 준비 사항


  • Visual Studio 2013
  • ASP.NET MVC5
  • LocalDB 11 




 NHibernate를 통해 ASP.NET MVC에서 간단한 CRUD를 해보도록 하겠다. 우선 준비 사항으로 데이터 베이스가 필요하여 아래와 같이 Employee테이블을 만들었다. '코드1'에서 관련 Query가 있으니 SQL Server Management Studio에서 실행하여 테이블을 생성할 수 있도록 하였다.



[그림1] Employee Table




CREATE TABLE [dbo].[Employee](
	[Id] [intIDENTITY(1,1NOT NULL,
	[FirstName] [nvarchar](50NULL,
	[LastName] [nvarchar](50NULL,
	[Designation] [nvarchar](50NULLON [PRIMARY]

[코드1] Create query Employee table 





 이제 준비가 되었으면 아래와 같이 웹 프로젝트를 만들어 보자.



[그림2] ASP.NET MVC template




[그림3] ASP.NET MVC Web API 선택



NHibernate 관련 DLL을 참조 하여야 한다. 참조 방법은 PM( Tools -> NuGet Package Manager -> Package Manager Console )에서 "Install-Package NHibernate"를 입력하면 참조가 된다.
( "그림4"에서는 이미 참조가 되어 있는 관계로 처음 참조하는 화면과는 다르며 예시를 하기 위함입니다. )




[그림4] Package Manager Console




NHibernate에서 사용하는 Config 파일을 Models폴더에 추가 하자. 추가할 파일명은 "Hibernate.cfg.xml"로 한다.


<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>
    <property name="connection.provider">
      NHibernate.Connection.DriverConnectionProvider
    </property>
    <property name="connection.driver_class">
      NHibernate.Driver.SqlClientDriver
    </property>
    <property name="connection.connection_string">
      Server=(LocalDB)\v11.0;Integrated Security=true;AttachDbFileName=C:\Users\천호\NHibernateTest.mdf
    </property>
    <property name="dialect">
      NHibernate.Dialect.MsSql2012Dialect
    </property>
  </session-factory>
</hibernate-configuration>

[코드2] NHibernate.cfg.xml



이제 Employee 관련 모델 클래스를 만든다.


public class Employee
{
    public virtual int Id { getset; }
    public virtual string FirstName { getset; }
    public virtual string LastName { getset; }
    public virtual string Designation { getset; }
}

[코드3] Employee model class



이제 모델 클래스와 DB의 Table과 매칭시켜 주는 파일을 Models 폴더에 추가해야 한다. 파일명은 "Employee.hbm.xml"로 한다.


<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true" assembly="NHibernateTest" namespace="NHibernateTest.Models">
  <class name="Employee" table="Employee" dynamic-update="true" >
    <cache usage="read-write"/>
    <id name="Id" column="Id" type="int">
      <generator class="native" />
    </id>
    <property name="FirstName" />
    <property name="LastName" />
    <property name="Designation" />
  </class>
</hibernate-mapping>

[코드4] Employee.hbm.xml




NHibernate을 접근하기 위한 Helper 클래스를 만든다.


public static ISession OpenSession()
{
    var configuration = new Configuration();
    // Configue 파일에 대한 정보
    var configurationPath = HttpContext.Current.Server.MapPath(@"~\Models\hibernate.cfg.xml");
    configuration.Configure(configurationPath);
 
    // 매칭 정보에 관한 파일
    var employeeConfigurationFile = HttpContext.Current.Server.MapPath(@"~\Models\Employee.hbm.xml");
            
    configuration.AddFile(employeeConfigurationFile);
    ISessionFactory sessionFactory = configuration.BuildSessionFactory();
    return sessionFactory.OpenSession();
}

[코드5] NHibernateSession Helper 클래스


 위 코드에서 MapPath를 통해 설정 정보와 매칭 정보가 있는 파일 정보를 세션 객체를 초기화 하는 작업이다. 이제 기본적인 준비 작업은 마무리가 되었으며 Controller에서 CRUD를 해보도록 하겠다.



public ActionResult Index()
{
	using (ISession session = NHibertnateSession.OpenSession())
	{
		var employees = session.Query<Employee>().ToList();
		return View(employees);
	}
}
 
public ActionResult Create()
{
	return View();
}
 
 
[HttpPost]
public ActionResult Create(Employee emplolyee)
{
	try
	{
		using (ISession session = NHibertnateSession.OpenSession())
		{
			using (ITransaction transaction = session.BeginTransaction())
			{
				session.Save(emplolyee);
				transaction.Commit();
			}
		}
 
		return RedirectToAction("Index");
	}
	catch (Exception exception)
	{
		return View();
	}
}
 
public ActionResult Edit(int id)
{
	using (ISession session = NHibertnateSession.OpenSession())
	{
		var employee = session.Get<Employee>(id);
		return View(employee);
	}
 
}
 
 
[HttpPost]
public ActionResult Edit(int idEmployee employee)
{
	try
	{
		using (ISession session = NHibertnateSession.OpenSession())
		{
			var employeetoUpdate = session.Get<Employee>(id);
 
			employeetoUpdate.Designation = employee.Designation;
			employeetoUpdate.FirstName = employee.FirstName;
			employeetoUpdate.LastName = employee.LastName;
 
			using (ITransaction transaction = session.BeginTransaction())
			{
				session.Save(employeetoUpdate);
				transaction.Commit();
			}
		}
		return RedirectToAction("Index");
	}
	catch
	{
		return View();
	}
}
 
public ActionResult Details(int id)
{
	using (ISession session = NHibertnateSession.OpenSession())
	{
		var employee = session.Get<Employee>(id);
		return View(employee);
	}
}
 
public ActionResult Delete(int id)
{
	using (ISession session = NHibertnateSession.OpenSession())
	{
		var employee = session.Get<Employee>(id);
		return View(employee);
	}
}
 
 
[HttpPost]
public ActionResult Delete(int idEmployee employee)
{
	try
	{
		using (ISession session = NHibertnateSession.OpenSession())
		{
			using (ITransaction transaction = session.BeginTransaction())
			{
				session.Delete(employee);
				transaction.Commit();
			}
		}
		return RedirectToAction("Index");
	}
	catch (Exception exception)
	{
		return View();
	}
}

[코드6] EmployeeController




이제 View 페이지를 만든다.


@model NHibernateTest.Models.Employee
 
@{
    ViewBag.Title = "Create";
}
 
<h2>Create</h2>
 
 
@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()
    
    <div class="form-horizontal">
        <h4>Employee</h4>
        <hr />
        @Html.ValidationSummary(true)
 
        <div class="form-group">
            @Html.LabelFor(model => model.FirstNamenew { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.FirstName)
                @Html.ValidationMessageFor(model => model.FirstName)
            </div>
        </div>
 
        <div class="form-group">
            @Html.LabelFor(model => model.LastNamenew { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.LastName)
                @Html.ValidationMessageFor(model => model.LastName)
            </div>
        </div>
 
        <div class="form-group">
            @Html.LabelFor(model => model.Designationnew { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Designation)
                @Html.ValidationMessageFor(model => model.Designation)
            </div>
        </div>
 
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}
 
<div>
    @Html.ActionLink("Back to List""Index")
</div>
 
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

[코드7] Create




@model IEnumerable<NHibernateTest.Models.Employee>
 
@{
    ViewBag.Title = "Index";
}
 
<h2>Index</h2>
 
<p>
    @Html.ActionLink("Create New""Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.FirstName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.LastName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Designation)
        </th>
        <th></th>
    </tr>
 
@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.FirstName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.LastName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Designation)
        </td>
        <td>
            @Html.ActionLink("Edit""Edit"new { id=item.Id }) |
            @Html.ActionLink("Details""Details"new { id=item.Id }) |
            @Html.ActionLink("Delete""Delete"new { id=item.Id })
        </td>
    </tr>
}
 
</table>

[코드8] Index




@model NHibernateTest.Models.Employee
 
@{
    ViewBag.Title = "Edit";
}
 
<h2>Edit</h2>
 
 
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    
    <div class="form-horizontal">
        <h4>Employee</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.Id)
 
        <div class="form-group">
            @Html.LabelFor(model => model.FirstNamenew { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.FirstName)
                @Html.ValidationMessageFor(model => model.FirstName)
            </div>
        </div>
 
        <div class="form-group">
            @Html.LabelFor(model => model.LastNamenew { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.LastName)
                @Html.ValidationMessageFor(model => model.LastName)
            </div>
        </div>
 
        <div class="form-group">
            @Html.LabelFor(model => model.Designationnew { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Designation)
                @Html.ValidationMessageFor(model => model.Designation)
            </div>
        </div>
 
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}
 
<div>
    @Html.ActionLink("Back to List""Index")
</div>
 
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

[코드9] Edit




@model NHibernateTest.Models.Employee
 
@{
    ViewBag.Title = "Details";
}
 
<h2>Details</h2>
 
<div>
    <h4>Employee</h4>
	<hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.FirstName)
        </dt>
 
        <dd>
            @Html.DisplayFor(model => model.FirstName)
        </dd>
 
        <dt>
            @Html.DisplayNameFor(model => model.LastName)
        </dt>
 
        <dd>
            @Html.DisplayFor(model => model.LastName)
        </dd>
 
        <dt>
            @Html.DisplayNameFor(model => model.Designation)
        </dt>
 
        <dd>
            @Html.DisplayFor(model => model.Designation)
        </dd>
 
    </dl>
</div>
<p>
    @Html.ActionLink("Edit""Edit"new { id = Model.Id }) |
    @Html.ActionLink("Back to List""Index")
</p>

[코드10] Details




@model NHibernateTest.Models.Employee
 
@{
    ViewBag.Title = "Delete";
}
 
<h2>Delete</h2>
 
<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Employee</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.FirstName)
        </dt>
 
        <dd>
            @Html.DisplayFor(model => model.FirstName)
        </dd>
 
        <dt>
            @Html.DisplayNameFor(model => model.LastName)
        </dt>
 
        <dd>
            @Html.DisplayFor(model => model.LastName)
        </dd>
 
        <dt>
            @Html.DisplayNameFor(model => model.Designation)
        </dt>
 
        <dd>
            @Html.DisplayFor(model => model.Designation)
        </dd>
 
    </dl>
 
    @using (Html.BeginForm()) {
        @Html.AntiForgeryToken()
 
        <div class="form-actions no-color">
            <input type="submit" value="Delete" class="btn btn-default" /> |
            @Html.ActionLink("Back to List""Index")
        </div>
    }
</div>

[코드11] Delete




이제 모든 준비가 되었으니 확인해 보도록 한다.



이 프로젝테에서 사용하는 DB는 LocalDB V11 을 사용하였다. Hibernate.cfg.xml 파일에서  "AttachDbFileName" 부분을 자신의 환경에 맞게 수정해야 한다.




[그림2] List





[그림3] Edit





Source Download

NHibernateTest.zip





Model Binding using IModelBinder and DefaultModelBinder in ASP.NET MVC


참조 URL
  1. http://www.codeproject.com/Tips/806415/Model-Binding-using-IModelBinder-and-DefaultModelB

 


이 문서에 대하여

이 문서는 원본 문서를 읽고, 따라하고, 변경 또는 수정없이 인용한 부분이 있으며 주석이나 추가 설명에 대해 가감을 하였습니다. 이 문서와는 별개로 별도의 원본이 있음을 알려 드리며 원본에 대한 자세한 사항은 참조 URL에 있음을 알려 드립니다. 오역, 어색한 부분, 매끄럽지 않은 부분이 있을 경우 알려주시면 적극적으로 반영하도록 하겠습니다.



ASP.NET MVC에서 기본으로 제공되는 모델 바인더가 있어서 컨트롤러에서 클래스로 변환되어 사용할 수 있도록 해주고 있다. 일반적인 케이스에서는 잘 동작하고 있지만 모델과 HTML의 Name이 일치하지 않을경우 커스텀 모델 바인더를 통해 해결 할 수 있는 방법을 제공해 주고 있다. 'IModelBinder' 인터페이스를 상속받아 구현된 클래스를 통해 이와같은 기능을 대체할 수 있도록 한다. 


public interface IModelBinder
{
    object BindModel(ControllerContext controllerContextModelBindingContext bindingContext);
}

[코드1] IModelBinder interface



 IModelBinder 인터페이스는 object를 반환하는 BindModel이 선언되어 있어서 이 메소드만 구현하면 원하는 방법으로 바인딩을 할 수 있다. 우선 모델 클래스를 선언하는 것부터 시작하자!


public class Person
{
    [Required]
    [MaxLength(50ErrorMessage = "Full name should be within 50 character")]
    public string full_name { getset; }
 
    [Range(1880)]
    [Required(ErrorMessage = "Please provide age")]
    public Int32 Age { getset; }
}

[모드2] Person Model class



 위와 같이 모델 클래스를 선언하고 IModelBinder을 상속받아 아래와 같이 Form객체에서 값을 가져와 Person객체를 반환하는 코드를 작성하자.



public class PersonModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContextModelBindingContext bindingContext)
    {
        var request = controllerContext.HttpContext.Request;
 
        string first_name = request.Form.Get("first_name");
        string middle_name = request.Form.Get("middle_name");
        string last_name = request.Form.Get("last_name");
        int Age = Convert.ToInt32(request.Form.Get("age"));
        return new Person { full_name = first_name + middle_name + last_nameAge = Age };
    }
}

[코드3] PersonModelBinder class




 이제 웹페이지에서 확인할 수 있도록 UI 관련 코드를 작성하자. 아래와 같이 CustomodelbinderController 클래스를 작성하고 PostData View 페이지를 추가하자.



public class CustomodelbinderController : Controller
{
    //
    // GET: /Customodelbinder/
    public ActionResult Index()
    {
        return View();
    }
 
    public ActionResult PostData()
    {
        return View();
    }
 
    [HttpPost]
    public void PostData([ModelBinder(typeof(PersonModelBinder))] Person person)
    {

    }

[코드4] CustomodelbinderController class


 위 코드에서 주의할 부분이 PostData의 입력 인자에서 Parameter Attribute를 선언하여 명시적으로 PersonModelBinder를 사용하도록 선언을 해야 한다.



@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        @{Html.BeginForm("PostData""Customodelbinder");
        <table>
            <tr>
                <td>First Name : </td>
                <td>@Html.TextBox("first_name")</td>
            </tr>
            <tr>
                <td>Middle Name : </td>
                <td>@Html.TextBox("middle_name")</td>
            </tr>
            <tr>
                <td>Surname :</td>
                <td> @Html.TextBox("last_name")</td>
            </tr>
            <tr>
                <td>Age:</td>
                <td> @Html.TextBox("age"</td>
            </tr>
            <tr>
                <td></td>
                <td>
                    <input type="submit" name="Save" value="Save" />
                </td>
            </tr>
        </table>
        }
    </div>
</body>
</html>

[코드5] CustomodelbinderController 의 PostData View 페이지



이제 완료가 되었으니 "http://localhost:????/Customodelbinder/PostData"와 같은 주소로 지금까지의 코드를 확인해 보도록 하자.



[그림1] 입력 화면







[그림2] PersonModelBinder 값 확인




[그림3] Controller에서 값 확인



 위와 같이 ModelBinder 단과 Controller단에서 기대하는 값을 확인 할 수 있다.





사용자가 수정한 커스텀 바인더를 ASP.NET MVC에서 인식하여 사용할 수 있는 방법이 아래오 같이 두가지가 있다.


  1. Parameter Attribute

  2. Global.asax에서 ModelBinders에 등록


첫번째는 위 "코드4"에서 언급이 되었듯 

public void PostData([ModelBinder(typeof(PersonModelBinder))] Person person)

이 사용할 수 있고 아래와 같이 추가하여 사용할 수 있다.

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        GlobalConfiguration.Configure(WebApiConfig.Register);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
 
        ModelBinders.Binders.Add(typeof(Person), new PersonModelBinder()); // <- 추가 코드
    }
}

[코드6] Global.asax









여담으로 IModelBinder 인터페이스 대신에 DefaultModelBinder 클래스를 상속받아 같은 기능을 실현 할 수 있다.

public class PersonDefaultModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContextModelBindingContext bindingContext)
    {
        var request = controllerContext.HttpContext.Request;
 
        string first_name = request.Form.Get("first_name");
        string middle_name = request.Form.Get("middle_name");
        string last_name = request.Form.Get("last_name");
        int Age = Convert.ToInt32(request.Form.Get("age"));
        return new Person { full_name = first_name + middle_name + last_nameAge = Age };
    }
}





Source Download


IModelBinderWebApplication.zip







 ASP.NET MVC에서 WebActivator를 사용해 보자

- Bootstraper 기능

 이 포스트에 있는 내용이 언제나 확실한 정답은 아닙니다. 진실이라고 생각해 왔던 전제가 시간의 지남에 따라 들어나지 않았던 다른 이면 때문에 좋은 방향으로 이끌어 낼 수 있는 역할로 변환 되는게 역사적으로도 많은 증명 있었습니다. 그렇지만 저는 현재 상황에서 최선의 답을 찾고자 노력하였으며 이 글을 읽는 다른 분들에게 다음 길을 갈 수 있도록 도와주는 디딤돌이 되고자 노력하고자 포스팅을 통해 공유하고자 하는 것입니다. 그리고 프로그래머라는 타이틀을 달고 살아야 한다면 "왜"라는 의문을 항상 가지고 다니면서 자신의 위치에 안주하지 않고 항상 노력하는 모습으로 살아 가고자 합니다. 언제든 지적이나 오류가 있으면 피드백 부탁 드리겠습니다.

ing™       



 WebActivator는 ASP.NET이 IIS에서 로드가 되기 전에 미리 필요한 초기화 작업을 개발자의 명시없이 진행할 수 있는 방안을 제시해 주고 있다. 더군다나 다른 개발자가 배포한 참조 DLL에서 초기화를 해야 하는 경우에도 WebActivator가 암시적으로 초기화를 할 수 있도록 할 수 있다. 우선 사용하기 위해서는 Nuget에서 WebActivator로 검색해서 설치를 하도록 하자. 아래 그림을 참고로 설치를 하자


[그림1] WebActiviator 검색

(Install 버튼을 눌러 설치를 한다.)


[그림2] WebActivator 설치



 이제 프로젝트에 WebActivator이 설치가 되었다. 이제 프로젝트 아무데서나 BootStrapper.cs(필자가 임의로 만들었다) 파일을 생성해 아래 코드와 같이 입력해 보자.


[코드1] Activator 실행 코드


 위 소스코드에서 1, 2, 3번줄을 살펴 보자. 

- [assembly: WebActivator.PreApplicationStartMethod(typeof(TDDMVCTest.App_Start.BootStrapper), "PreStart")] : 최초 Web Server가 실행 할 때  Application Start가 되기 전에 실행 됨

- [assembly: WebActivator.PostApplicationStartMethod(typeof(TDDMVCTest.App_Start.BootStrapper), "PostStart")] : 최초 Web Server가 실행 되고 나서 Application End가 되고 나서 실행 됨

- [assembly: WebActivator.ApplicationShutdownMethod(typeof(TDDMVCTest.App_Start.BootStrapper), "Shutdown")] : Web Server가 종료되기 전에 실행 됨


 클래스 위에서 위와 같이 선언하여 사용할 수 있으며 자신이 작성한 코드의 초기화나 종료를 암시적으로 실행시킬 수 있으며 다른 개발자가 배포한 모듈에서 필요한 초기화를 WebActivator를 통해서도 초기화를 진행할 수 있다. 이 기능을 이용해서 Global.asax 파일이나 Web.Config 파일에 집중되어 있던 초기화 작업에 대해서 암시적으로 실행할 수 있는 길이 열렸다. 자세한 사용 기능을 "코드1"에 있는 주석을 읽어 보도록 하자.


소스 코드 자체에 주석과 직관적인 코딩으로 충분히 파악이 가능할 것으로 예상하므로 별도의 설명을 생략하도록 하겠습니다. 포스트의 내용이 장황한 설명 보다는 주석과 소스코드 자체 만으로도 이해할 수 있도록 하기 위해 노력하였습니다. 실 개발에서도 적용할 수 있도록 간단하면서도 현실적인 예제 프로그램을 통해 각 소스를 만들고 이해 시키고자 하였으며 실무에 필요한 개발요구 사항들을 해결 하는데 도움이 되고자 노력하였습니다. 그리고 소스와 같이 있는 주석을 이용해 nDoc이나 별도의 자동 Document 제작 유틸로 API 문서를 만드는 데에도 도움이 되었으면 한다. 
※ DOC에 대한 프로그램 정보 Util link

ing™       


C# ASP.NET MVC 세션 동시 접근 테스트

http://msdn.microsoft.com/ko-kr/library/system.web.sessionstate.sessionstatebehavior.aspx


 ASP.NET MVC에서 서버 객체에서 세션 값을 동시적으로 접근 하였을 경우 처리하는 방법에 대해 알아 보자. 일반적인  컨트롤러에서 세션에 접근하면 thread unsafe 상태에서 세션의 값을 수정하게 되어 있다. 예로 "코드1"번 처럼 같은 사용자의 세션에서 접근하여 값을 수정하거나 삭제를 할 수 있다.

//세션에 저장
Session["testSerialCallValue"= "Value";

[코드1] 세션 객체 수정


 위와 같은 코드에서는 ajax를 통해 동시 다발적으로 처리를 수행 하는 케이스에서는 기대하는 수행 결과가 나오지 않을 수도 있다. 다음과 같은 코드가 오류가 발생할 수 있는 케이스이다.

Session["incValue"= ((int.Parse(Session["incValue"].ToString())) + 1).ToString();

[코드2] 세션 객체 수정 ( 오류가 발생 할 수 있는 케이스 )


 이와 같은 케이스에서는 Controller 클래스에서 아래와 같은 Attribute를 추가해 줌으로써 해결이 가능 하다.

[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]

[코드3] 세션 객체 컨트롤 Attribute


 이제 키워드를 알았으니 예제를 통해 전체 흐름을 알아 보자.( SessionStateBehavior 알아 보기 )



<script type="text/javascript">
    var start = '';
    $(function () {
        //ajax cache 옵션 해제
        //$.ajaxSetup({ async: false });    //ajax 1.9.1에서 fale로 지정하면 server controller에 상관 없이 순차적으로 ajax calling 진행
        start = new Date().getTime();
 
        //SessionStateBehavior.ReadOnly 
        //동시 호출 테스트 - 병렬 수행 케이스
        for (var i = 1; i <= 3; i++) {
            $.ajax('SessionConcurrentParallel/GetParallelCall' + i, {
                cache: false
            }).done(logParallel);
        }
 
        //SessionStateBehavior.Required
        //동시 호출 테스트 - 순차 수행 케이스
        for (var i = 1; i <= 3; i++) {
            $.ajax('SessionConcurrentSerial/GetSerialCall' + i, {
                cache: false
            }).done(logSerial);
        }
    });
 
    function logParallel(data, stateText, responseObject) {
        console.log(['Parallel', data, stateText, responseObject, start, new Date().getTime()]);
    }
 
    function logSerial(data, stateText, responseObject) {
        console.log(['Serial', data, stateText, responseObject, start, new Date().getTime()]);
    }
</script>

[코드4] 웹 페이지에서 ajax를 통해 동시적으로 controller 호출 하기


 "코드4"에서와 같은 코드로 첫번째 ajax 호출은 서버 컨트롤러에서 병렬로 Session 객체에 접근할 수 있는 액션을 호출 하고, 두번째 ajax 호출은 Session 객체를 순차적으로만 접근 하도록 되어 있는 컨트롤러의 액션을 호출 하도록 구성 하였다. 이제 이 코드에 상응하는 컨트롤러를 구성해 보자.

[SessionState(System.Web.SessionState.SessionStateBehavior.Required)]
[OutputCache(NoStore = trueDuration = 0VaryByParam = "*")]
public class SessionConcurrentSerialController : Controller
{
    #region Serial Call processing test
    public JsonResult GetSerialCall1()
    {
        //세션에 저장
        Session["testSerialCallValue"= "Value";
        Thread.Sleep(3000);
        return Json(new { obj = "SeriallCall1 Value #1" }, JsonRequestBehavior.AllowGet);
    }
 
    public JsonResult GetSerialCall2()
    {
        //세션에 저장
        Session["testSerialCallValue"= "Value";
        Thread.Sleep(3000);
        return Json(new { obj = "SeriallCall1 Value #2" }, JsonRequestBehavior.AllowGet);
    }
 
    public JsonResult GetSerialCall3()
    {
        //세션에 저장
        Session["testSerialCallValue"= "Value";
        Thread.Sleep(3000);
        return Json(new { obj = "SeriallCall1 Value #3" }, JsonRequestBehavior.AllowGet);
    }
    #endregion
}

[코드5] 순차적 Session 객체 접근


[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
[OutputCache(NoStore = trueDuration = 0VaryByParam = "*")]
public class SessionConcurrentParallelController : Controller
{
    //
    // GET: /SessionConcurrent/
 
    public ActionResult Index()
    {
        Session["testValue"= "TempValue";
        Session["incValue"= ((int.Parse(Session["incValue"].ToString())) + 1).ToString();
 
        return View();
    }
 
    #region Parallel Call processing test
    public JsonResult GetParallelCall1()
    {
        //세션에서 읽기
        var x = Session["testParallelCallValue"];
        Thread.Sleep(3000);
        return Json(new { obj = "GetParallelCall1 Value #1" }, JsonRequestBehavior.AllowGet);
    }
 
    public JsonResult GetParallelCall2()
    {
        //세션에서 읽기
        var x = Session["testParallelCallValue"];
        Thread.Sleep(3000);
        return Json(new { obj = "GetParallelCall1 Value #2" }, JsonRequestBehavior.AllowGet);
    }
 
    public JsonResult GetParallelCall3()
    {
        //세션에서 읽기
        var x = Session["testParallelCallValue"];
        Thread.Sleep(3000);
        return Json(new { obj = "GetParallelCall1 Value #3" }, JsonRequestBehavior.AllowGet);
    }
    #endregion
}

[코드6] 병렬로 Session 객체 접근


 컨트롤러 선언시 클래스 선언부에서 "SessionState"의 Attribute를 선언하여 Session 접근 유형을 패턴을 정의 하였으며 Index 액션과 연결된 웹 페이지에서 ajax를 통해 테스트할 수 있는 뷰 페이지를 연결 하였다.


 이제 아래 그림을 통해 결과를 확인해 보도록 하자.


[그림1] 컨트롤러의 액션 로딩 타임


 "그림1"에서와 같이 Parallel 형식으로 선언된 컨트롤러의 액션 호출은 일률적인 시간으로 완료가 되었으나 Serial 형식으로 접근하도록 선언된 컨트롤러 액션은 순차적으로 처리가 되어 총 처리 시간이 3s * 3회인 9초가 걸린 결과가 나왔다. 세션을 사용할 때 동시성 처리에 대한 처리를 Attribute를 통해서 컨트롤 할 수 있도록 해주는 방안을 ASP.NET MVC에서 제공해 주고 있는 것이다. 다만 예외적으로 "그림2"와 같은 결과가 나올 수 있다.


[그림2] 컨트롤러 액션 로딩 타임 - ajax request limit에 따른 결과


 "그림2"와 같은 결과가 나오게 되는 케이스는 브라우저마다 한번에 ajax콜을 할 수 있는 connection 수가 제한 되어 있기 때문이다. 브라우저(버전에 따라 다름) 마다 2 ~ 6개 정도를 지원하도록 되어 있어서 측정 수치는 달라질 수 있다. 필자와 같은 케이스로 결과값이 나오기를 바란다면 최신 브라우저로 테스트를 해 보기를 바란다.



 이 포스트에 있는 내용이 언제나 확실한 정답은 아닙니다. 진실이라고 생각해 왔던 전제가 시간의 지남에 따라 들어나지 않았던 다른 이면 때문에 좋은 방향으로 이끌어 낼 수 있는 역할로 변환 되는게 역사적으로도 많은 증명 있었습니다. 그렇지만 저는 현재 상황에서 최선의 답을 찾고자 노력하였으며 이 글을 읽는 다른 분들에게 다음 길을 갈 수 있도록 도와주는 디딤돌이 되고자 노력하고자 포스팅을 통해 공유하고자 하는 것입니다. 그리고 프로그래머라는 타이틀을 달고 살아야 한다면 "왜"라는 의문을 항상 가지고 다니면서 자신의 위치에 안주하지 않고 항상 노력하는 모습으로 살아 가고자 합니다. 언제든 지적이나 오류가 있으면 피드백 부탁 드리겠습니다.

ing™       


C# WriteSubstitution - OutputCache


웹을 개발하는데 있어서 캐시를 적절히 사용하면 웹 서버의 성능을 비약적으로 끌어 올릴 수 있다.

 

아래와 같이 특정 액션이 자원을 많이 소모하여 얻어낸 결과를 사용자가 요청할 때마다 얻어와야 하는 곳에서 OutputCache Attribute를 사용함으로써 선택적으로 캐시를 사용할 수 있다.

위와 같이 설정이 되면 10초 동안 서버에서는 캐시 된 정보를 반환하도록 되어 있다.

 

그렇지만 캐시가 되어 있는 상황에서도

일부 데이터는 갱신되어야 하는 시나리오가 있을 수 있을 것이다.

이럴 때 사용할 수 있는 방안 중에 Response.WriteSubstitution를 소개하도록 하겠다.

 

@ViewBag.Time : 서버에서 캐시 된 값을 Duration 시간 동안 출력한다.

@{ Response.WriteSubstitution((context) => DateTime.Now.ToString()); } : 캐시 된 Content에서도 요청 시 마다 새로운 값을 출력이 되도록 해주는 메소드 ( Action 메소드를 통해 delegate를 구현하였다. )

 

위와 같이 설정하고 아래 화면에서 확인해 보도록 하자.

첫번째 형광색 표시가 WriteSubstitution으로 출력된 값이고

두번째 형광색 표시가 캐시 된 값을 보여주고 있다.


BootStrapper.cs


 

ASP.NET MVC를 실행 할 때 Global.asax 파일에서 일부 설정 파일을 적용토록 하는 작업을 해 보았을 것이다.

ASP.NET MVC 4에서는 기본적으로는 아래와 같이 간소화 되어 Global.asax의 Application_Start()로 만들어 두었다.

파일명 : Global.asax

protected void Application_Start()
{
     AreaRegistration.RegisterAllAreas();

     WebApiConfig.Register(GlobalConfiguration.Configuration);
     FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
     RouteConfig.RegisterRoutes(RouteTable.Routes);
     BundleConfig.RegisterBundles(BundleTable.Bundles);
     AuthConfig.RegisterAuth();

}

위에서 Web API 설정, 필터 설정, 라우트 설정 등등 여러 가지 설정을 MVC Framework에 제공하는 기능으로 대체 하여 MVC의 기본 Configuration을 지정하였다.

그리고 무엇보다 중요한 기능은 외부 라이브러리를 추가하여(DLL 참조) 사용할 때 그 라이브러리의 환경 변수를 개발자가 설정하지 않고 기본 값을 자체적으로 설정하여 Default Behavior를 갖도록 할 수 있다는 것이다.

 

그렇지만 사용자가 임의의 수정하거나 변경을 하기 위해서는 Global.asax파일이 지저분 해지도록 할 수 밖에 없지만

지금 소개하는 WebActivator 라이브러리를 이용하면 파일 구조나 깊이에 상관없이 설정 또는 Business logic을 할 수 있다.

 

이제 본격적으로 소개하도록 하겠다.

“WebAativator”를 Nuget에서 검색하여 프로젝트에 추가한다.

그런 다음 아래와 같이 소스를 추가한다.

파일명 : /App_Start/BootStrapper.cs

( 임의로 BootStrapper.cs로 명명 하였다 )
오른쪽 그림과 같이 파일 추가                                                       

[assembly: WebActivator.PreApplicationStartMethod(typeof(TDDMVCTest.App_Start.BootStrapper), "PreStart")]
[assembly: WebActivator.PostApplicationStartMethod(typeof(TDDMVCTest.App_Start.BootStrapper), "PostStart")]
[assembly: WebActivator.ApplicationShutdownMethod(typeof(TDDMVCTest.App_Start.BootStrapper), "Shutdown")]

namespace TDDMVCTest.App_Start
{
    public static class BootStrapper
    {
        private static IWindsorContainer container;

        /// <summary>
        /// 최초 Web Server가 실행 할 때  Application Start가 되기 전에 실행 됨
        /// </summary>
        public static void PreStart() {

            //Business logic or configuration job
            var temp = string.Empty;
        }

        /// <summary>
        /// 최초 Web Server가 실행 되고 나서 Application End가 되고 나서 실행 됨
        /// </summary>
        public static void PostStart()
        {

            //Business logic or configuration job
            var temp = string.Empty;
        }

        /// <summary>
        /// Web Server가 종료되기 전에 실행 됨
        /// </summary>
        public static void Shutdown()
        {

            //Business logic or configuration job
            var temp = string.Empty;
        }
    }
}

위 실행 순서는 IHttpModule 상속하여 구현된 ActivationManager.cs에서 실행을 하며 전체 DLL을 읽어 들여 실행이 되게 하는 구조로 되어 있다.

- 클래스 명이나

- 네임 스페이스가 다름

- 외부 라이브러리로 선언

위와 같아도 Web Server가 실행이 되면 실행이 되는 구조다.

 

Tip !

    WebActivator.ActivationManager.RunPreStartMethods();
    WebActivator.ActivationManager.Run();
    WebActivator.ActivationManager.RunPostStartMethods();
    WebActivator.ActivationManager.RunShutdownMethods();

    위 메소드 실행으로 Activator 단계를 임의로 실행 할 수 있다.

    궁금하면 한번 테스트 해보기 바란다.

 

Tip !

    바로 이전 IoC 컨테이너 속성을 Activator을 이용해 초기화를 할 수도 있을 것이다. 바로 아래에 예시를 달도록 하겠다.

   2013/01/25 - [ASP.NET MVC] - ASP.NET MVC 4에서 Windsor (Castle)로 IoC로 Controller 생성 - 참조


using System;
using System.Web.Mvc;
using TDDMVCTest.Core;
using Castle.Windsor;
using Castle.Windsor.Installer;

[assembly: WebActivator.PreApplicationStartMethod(typeof(TDDMVCTest.App_Start.BootStrapper), "PreStart")]
[assembly: WebActivator.PostApplicationStartMethod(typeof(TDDMVCTest.App_Start.BootStrapper), "PostStart")]
[assembly: WebActivator.ApplicationShutdownMethod(typeof(TDDMVCTest.App_Start.BootStrapper), "Shutdown")]

namespace TDDMVCTest.App_Start
{
    public static class BootStrapper
    {
        private static IWindsorContainer container;

        /// <summary>
        /// 최초 Web Server가 실행 할 때  Application Start가 되기 전에 실행 됨
        /// </summary>
        public static void PreStart() {
            container = new WindsorContainer().Install(FromAssembly.This());

            var controllerFactory = new WindsorControllerFactory(container.Kernel);
            ControllerBuilder.Current.SetControllerFactory(controllerFactory);
        }

 

       ……..

    }

}


ASP.NET MVC 4에서 Windsor (Castle)로 IoC로 Controller 생성


Visual studio에서 Nuget을 통해 Windsor을 프로젝트에 추가한다.

위 그림은 Online에서 ‘Windsor’로 검색한 결과다.

 

Tip! - Windsor ver 3.1.0 – 2012.08.15

 

정상적으로 References에 참조 등록이 된 것을 확인하고

아래 파일을 추가한다.

 

파일명 : WindsorControllerFactory.cs
(DefaultControllerFactory를 상속받아 Windsor을 이용해 객체를 생성하는 기능을 구현한다)

    public class WindsorControllerFactory : DefaultControllerFactory
    {
        private readonly IKernel kernel;

        public WindsorControllerFactory(IKernel kernel)
        {
            this.kernel = kernel;
        }

        public override void ReleaseController(IController controller)
        {
            kernel.ReleaseComponent(controller);
        }

        protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
        {
            if (controllerType == null)
            {
                throw new HttpException(404, string.Format("The controller for path '{0}' could not be found.", requestContext.HttpContext.Request.Path));
            }

            return (IController)kernel.Resolve(controllerType);
        }
    }

 

파일명 : ControllersInstaller.cs
(어떤 Assembly에서 어떤 객체를 IoC 해줄지를 등록한다)

    public class ControllersInstaller : IWindsorInstaller
    {

        public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
        {
            container.Register(Classes.FromThisAssembly()
                                        .BasedOn<IController>()
                                        .LifestyleTransient());
        }
    }

 

위의 파일을 만든 다음

Global.asax 파일을 아래와 같이 수정한다.

파일명 : Global.asax.cs
( 컨테이너를 MVC Framework에 끼워 넣는다 )

    public class MvcApplication : System.Web.HttpApplication
    {
        private static IWindsorContainer container;

        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            AuthConfig.RegisterAuth();

            container = new WindsorContainer().Install(FromAssembly.This());

            var controllerFactory = new WindsorControllerFactory(container.Kernel);
            ControllerBuilder.Current.SetControllerFactory(controllerFactory);
        }
    }

 

위와 같이 WindsorControllerFactory를 Global.asax 파일에 연결 시켰으면 이제 실행해 보도록 하자.

 

Ctrl + F5 –> 실행

위와 같이 화면을 정상적으로 보여주고 있다.

 

이제 이 프로젝트는 IoC 컨테이너(Windsor)를 통해 Controller 객체를 생성하여 실행이 되는 구조로 바뀐 것이다.

Web API - JSONP 호출 반환


Web API는 기본적으로 JSONP를 지원하지 않는다.


JSONP를 지원하기 위해서는 Fomatter를 추가하여 아래와 같이 해결 할 수 있습니다.


설정 파일


[코드] JSONP로 변환해주도록 설정 함




Business Logic


[코드] JSONP로 변환해주는 코드



HTML 코드


[코드] HTML에서 Javascript로 호출하여 확인하는 코드




위와 같이 세팅하면

Asp.net MVC 4에서 JSONP 형식으로 리턴이 된다.




+ Recent posts