다음을 통해 공유


비즈니스 규칙 유효성 검사를 사용하여 모델 빌드

작성자: Microsoft

PDF 다운로드

ASP.NET MVC 1을 사용하여 작지만 완전한 웹 애플리케이션을 빌드하는 방법을 안내하는 무료 "NerdDinner" 애플리케이션 자습서 의 3단계입니다.

3단계에서는 NerdDinner 애플리케이션에 대한 데이터베이스를 쿼리하고 업데이트하는 데 사용할 수 있는 모델을 만드는 방법을 보여 드립니다.

ASP.NET MVC 3을 사용하는 경우 MVC 3 또는 MVC Music Store 시작 따라가는 것이 좋습니다.

NerdDinner 3단계: 모델 빌드

model-view-controller 프레임워크에서 "model"이라는 용어는 애플리케이션의 데이터를 나타내는 개체와 유효성 검사 및 비즈니스 규칙을 통합하는 해당 도메인 논리를 나타냅니다. 모델은 여러 가지 면에서 MVC 기반 애플리케이션의 "핵심"이며 나중에 볼 수 있듯이 근본적으로 동작을 구동합니다.

ASP.NET MVC 프레임워크는 모든 데이터 액세스 기술 사용을 지원하며, 개발자는 다양한 .NET 데이터 옵션 중에서 선택하여 LINQ to Entities, LINQ to SQL, NHibernate, LLBLGen Pro, SubSonic, WilsonORM 또는 원시 ADO.NET DataReaders 또는 DataSets를 비롯한 다양한 모델을 구현할 수 있습니다.

NerdDinner 애플리케이션의 경우 LINQ to SQL 사용하여 데이터베이스 디자인과 상당히 밀접하게 일치하는 간단한 모델을 만들고 몇 가지 사용자 지정 유효성 검사 논리 및 비즈니스 규칙을 추가합니다. 그런 다음, 나머지 애플리케이션에서 데이터 지속성 구현을 추상화하고 쉽게 단위 테스트를 수행할 수 있도록 하는 리포지토리 클래스를 구현합니다.

LINQ to SQL

LINQ to SQL .NET 3.5의 일부로 제공되는 ORM(개체 관계형 매퍼)입니다.

LINQ to SQL 데이터베이스 테이블을 코딩할 수 있는 .NET 클래스에 쉽게 매핑할 수 있는 방법을 제공합니다. NerdDinner 애플리케이션의 경우 이 애플리케이션을 사용하여 데이터베이스 내의 Dinners 및 RSVP 테이블을 Dinner 및 RSVP 클래스에 매핑합니다. Dinners 및 RSVP 테이블의 열은 Dinner 및 RSVP 클래스의 속성에 해당합니다. 각 Dinner 및 RSVP 개체는 데이터베이스의 Dinners 또는 RSVP 테이블 내에서 별도의 행을 나타냅니다.

LINQ to SQL 사용하면 데이터베이스 데이터로 Dinner 및 RSVP 개체를 검색하고 업데이트하기 위해 SQL 문을 수동으로 생성할 필요가 없습니다. 대신 Dinner 및 RSVP 클래스, 데이터베이스 간 매핑 방법 및 이들 간의 관계를 정의합니다. 그런 다음 LINQ to SQL 상호 작용하고 사용할 때 런타임에 사용할 적절한 SQL 실행 논리 생성을 처리합니다.

VB 및 C# 내에서 LINQ 언어 지원을 사용하여 데이터베이스에서 Dinner 및 RSVP 개체를 검색하는 표현 쿼리를 작성할 수 있습니다. 이렇게 하면 작성해야 하는 데이터 코드의 양이 최소화되고 실제로 클린 애플리케이션을 빌드할 수 있습니다.

프로젝트에 LINQ to SQL 클래스 추가

먼저 프로젝트 내의 "Models" 폴더를 마우스 오른쪽 단추로 클릭하고 새 항목 추가> 메뉴 명령을 선택합니다.

Models 폴더의 스크린샷 새 항목이 강조 표시됩니다. 모델이 강조 표시되고 선택됩니다.

그러면 "새 항목 추가" 대화 상자가 표시됩니다. "데이터" 범주로 필터링하고 그 안에 있는 "LINQ to SQL 클래스" 템플릿을 선택합니다.

새 항목 추가 대화 상자의 스크린샷 데이터가 강조 표시됩니다. L I N Q to S Q L 클래스가 선택되고 강조 표시됩니다.

항목 이름을 "NerdDinner"로 지정하고 "추가" 단추를 클릭합니다. Visual Studio는 \Models 디렉터리 아래에 NerdDinner.dbml 파일을 추가한 다음, LINQ to SQL 개체 관계형 디자이너를 엽니다.

Visual Studio의 Nerd Dinner 대화 상자 스크린샷 Nerd Dinner dot d b m l 파일이 선택됩니다.

LINQ to SQL 사용하여 데이터 모델 클래스 만들기

LINQ to SQL 기존 데이터베이스 스키마에서 데이터 모델 클래스를 빠르게 만들 수 있습니다. 이렇게 하려면 서버 Explorer NerdDinner 데이터베이스를 열고 모델링할 테이블을 선택합니다.

서버 Explorer 스크린샷 테이블이 확장됩니다. 저녁 식사와 R S V P가 강조 표시되어 있습니다.

그런 다음 테이블을 LINQ to SQL 디자이너 화면으로 끌 수 있습니다. 이 작업을 수행하면 LINQ to SQL 테이블의 스키마를 사용하여 Dinner 및 RSVP 클래스를 자동으로 만듭니다(데이터베이스 테이블 열에 매핑되는 클래스 속성 사용).

Nerd Dinner 대화 상자의 스크린샷 Dinner 및 R S V P 클래스가 표시됩니다.

기본적으로 LINQ to SQL 디자이너는 데이터베이스 스키마를 기반으로 클래스를 만들 때 테이블 및 열 이름을 자동으로 "복수화"합니다. 예를 들어 위의 예제에서 "Dinners" 테이블은 "Dinner" 클래스를 생성했습니다. 이 클래스 명명은 모델이 .NET 명명 규칙과 일치하게 하는 데 도움이 되며, 일반적으로 디자이너가 이 문제를 편리하게 수정하는 것을 발견합니다(특히 많은 테이블을 추가할 때). 하지만 디자이너에서 생성하는 클래스 또는 속성의 이름이 마음에 들지 않으면 항상 재정의하고 원하는 이름으로 변경할 수 있습니다. 디자이너 내에서 엔터티/속성 이름을 인라인으로 편집하거나 속성 그리드를 통해 수정하여 이 작업을 수행할 수 있습니다.

기본적으로 LINQ to SQL 디자이너는 테이블의 기본 키/외래 키 관계도 검사하고 이를 기반으로 테이블이 만드는 다양한 모델 클래스 간에 기본 "관계 연결"을 자동으로 만듭니다. 예를 들어 Dinners 및 RSVP 테이블을 LINQ to SQL 디자이너로 끌 때 RSVP 테이블에 Dinners 테이블의 외래 키가 있다는 사실에 따라 둘 사이의 일대다 관계 연결이 유추되었습니다(디자이너의 화살표로 표시됨).

Dinner 및 R S V P 테이블의 스크린샷 화살표가 강조 표시되고 Dinner 속성 트리 및 R S V P 속성 트리를 가리킵니다.

위의 연결을 사용하면 LINQ to SQL 개발자가 지정된 RSVP와 연결된 Dinner에 액세스하는 데 사용할 수 있는 강력한 형식의 "Dinner" 속성을 RSVP 클래스에 추가합니다. 또한 Dinner 클래스에는 개발자가 특정 Dinner와 연결된 RSVP 개체를 검색하고 업데이트할 수 있는 "RSVP" 컬렉션 속성이 있습니다.

아래에서 새 RSVP 개체를 만들고 Dinner의 RSVP 컬렉션에 추가할 때 Visual Studio 내에서 intellisense의 예를 볼 수 있습니다. LINQ to SQL Dinner 개체에 "RSVP" 컬렉션을 자동으로 추가하는 방법을 확인합니다.

Visual Studio 내의 intellisense 스크린샷 R S V P가 강조 표시됩니다.

Dinner의 RSVP 컬렉션에 RSVP 개체를 추가하여 데이터베이스의 Dinner 행과 RSVP 행 간의 외래 키 관계를 연결하도록 LINQ to SQL 알려 줍니다.

R S V P 개체 및 Dinner의 R S V P 컬렉션 스크린샷

디자이너가 테이블 연결을 모델링하거나 이름을 지정하는 방식이 마음에 들지 않으면 재정의할 수 있습니다. 디자이너 내에서 연결 화살표를 클릭하고 속성 그리드를 통해 속성에 액세스하여 이름을 바꾸거나 삭제하거나 수정합니다. 하지만 NerdDinner 애플리케이션의 경우 기본 연결 규칙은 빌드 중인 데이터 모델 클래스에 대해 잘 작동하며 기본 동작만 사용할 수 있습니다.

NerdDinnerDataContext 클래스

Visual Studio는 LINQ to SQL 디자이너를 사용하여 정의된 모델 및 데이터베이스 관계를 나타내는 .NET 클래스를 자동으로 만듭니다. 솔루션에 추가된 각 LINQ to SQL 디자이너 파일에 대해 LINQ to SQL DataContext 클래스도 생성됩니다. LINQ to SQL 클래스 항목의 이름을 "NerdDinner"로 지정했기 때문에 만든 DataContext 클래스를 "NerdDinnerDataContext"라고 합니다. 이 NerdDinnerDataContext 클래스는 데이터베이스와 상호 작용하는 기본 방법입니다.

NerdDinnerDataContext 클래스는 데이터베이스 내에서 모델링한 두 개의 테이블을 나타내는 두 가지 속성인 "Dinners"와 "RSVP"를 노출합니다. C#을 사용하여 해당 속성에 대해 LINQ 쿼리를 작성하여 데이터베이스에서 Dinner 및 RSVP 개체를 쿼리하고 검색할 수 있습니다.

다음 코드에서는 NerdDinnerDataContext 개체를 인스턴스화하고 이에 대해 LINQ 쿼리를 수행하여 나중에 발생하는 Dinners 시퀀스를 가져오는 방법을 보여 줍니다. Visual Studio는 LINQ 쿼리를 작성할 때 전체 intellisense를 제공하며, 이 쿼리에서 반환된 개체는 강력한 형식이며 intellisense도 지원합니다.

Visual Studio의 스크린샷 설명이 강조 표시되어 있습니다.

Dinner 및 RSVP 개체를 쿼리할 수 있는 것 외에도 NerdDinnerDataContext는 이후에 검색하는 Dinner 및 RSVP 개체에 대한 변경 내용을 자동으로 추적합니다. 명시적 SQL 업데이트 코드를 작성하지 않고도 이 기능을 사용하여 변경 내용을 데이터베이스에 쉽게 다시 저장할 수 있습니다.

예를 들어 아래 코드에서는 LINQ 쿼리를 사용하여 데이터베이스에서 단일 Dinner 개체를 검색하고 Dinner 속성 중 두 개를 업데이트한 다음 변경 내용을 데이터베이스에 다시 저장하는 방법을 보여 줍니다.

NerdDinnerDataContext db = new NerdDinnerDataContext();

// Retrieve Dinner object that reprents row with DinnerID of 1
Dinner dinner = db.Dinners.Single(d => d.DinnerID == 1);

// Update two properties on Dinner 
dinner.Title = "Changed Title";
dinner.Description = "This dinner will be fun";

// Persist changes to database
db.SubmitChanges();

위의 코드에서 NerdDinnerDataContext 개체는 검색한 Dinner 개체의 속성 변경 내용을 자동으로 추적했습니다. "SubmitChanges()" 메서드를 호출하면 데이터베이스에 적절한 SQL "UPDATE" 문을 실행하여 업데이트된 값을 다시 유지합니다.

DinnerRepository 클래스 만들기

소규모 애플리케이션의 경우 컨트롤러가 LINQ to SQL DataContext 클래스에 대해 직접 작동하고 컨트롤러 내에 LINQ 쿼리를 포함하도록 하는 것이 좋습니다. 하지만 애플리케이션이 커지면 유지 관리 및 테스트가 번거로워집니다. 또한 여러 위치에서 동일한 LINQ 쿼리를 복제할 수도 있습니다.

애플리케이션을 더 쉽게 유지 관리하고 테스트할 수 있는 한 가지 방법은 "리포지토리" 패턴을 사용하는 것입니다. 리포지토리 클래스는 데이터 쿼리 및 지속성 논리를 캡슐화하고 애플리케이션에서 데이터 지속성의 구현 세부 정보를 추상화합니다. 애플리케이션 코드를 더 깨끗하게 만드는 것 외에도 리포지토리 패턴을 사용하면 나중에 데이터 스토리지 구현을 더 쉽게 변경할 수 있으며 실제 데이터베이스 없이도 애플리케이션을 단위 테스트하는 데 도움이 될 수 있습니다.

NerdDinner 애플리케이션의 경우 아래 서명을 사용하여 DinnerRepository 클래스를 정의합니다.

public class DinnerRepository {

    // Query Methods
    public IQueryable<Dinner> FindAllDinners();
    public IQueryable<Dinner> FindUpcomingDinners();
    public Dinner             GetDinner(int id);

    // Insert/Delete
    public void Add(Dinner dinner);
    public void Delete(Dinner dinner);

    // Persistence
    public void Save();
}

참고: 이 챕터의 뒷부분에서는 이 클래스에서 IDinnerRepository 인터페이스를 추출하고 컨트롤러에서 종속성 주입을 사용하도록 설정합니다. 하지만 먼저 간단한 작업을 시작하고 DinnerRepository 클래스로 직접 작업하겠습니다.

이 클래스를 구현하려면 "Models" 폴더를 마우스 오른쪽 단추로 클릭하고 새 항목 추가> 메뉴 명령을 선택합니다. "새 항목 추가" 대화 상자에서 "클래스" 템플릿을 선택하고 파일 이름을 "DinnerRepository.cs"로 지정합니다.

Models 폴더의 스크린샷 새 항목 추가가 강조 표시됩니다.

그런 다음 아래 코드를 사용하여 DinnerRepository 클래스를 구현할 수 있습니다.

public class DinnerRepository {
 
    private NerdDinnerDataContext db = new NerdDinnerDataContext();

    //
    // Query Methods

    public IQueryable<Dinner> FindAllDinners() {
        return db.Dinners;
    }

    public IQueryable<Dinner> FindUpcomingDinners() {
        return from dinner in db.Dinners
               where dinner.EventDate > DateTime.Now
               orderby dinner.EventDate
               select dinner;
    }

    public Dinner GetDinner(int id) {
        return db.Dinners.SingleOrDefault(d => d.DinnerID == id);
    }

    //
    // Insert/Delete Methods

    public void Add(Dinner dinner) {
        db.Dinners.InsertOnSubmit(dinner);
    }

    public void Delete(Dinner dinner) {
        db.RSVPs.DeleteAllOnSubmit(dinner.RSVPs);
        db.Dinners.DeleteOnSubmit(dinner);
    }

    //
    // Persistence 

    public void Save() {
        db.SubmitChanges();
    }
}

DinnerRepository 클래스를 사용하여 검색, 업데이트, 삽입 및 삭제

DinnerRepository 클래스를 만들었으므로 다음과 같은 일반적인 작업을 보여 주는 몇 가지 코드 예제를 살펴보겠습니다.

예제 쿼리

아래 코드는 DinnerID 값을 사용하여 단일 Dinner를 검색합니다.

DinnerRepository dinnerRepository = new DinnerRepository();

// Retrieve specific dinner by its DinnerID
Dinner dinner = dinnerRepository.GetDinner(5);

아래 코드는 예정된 모든 저녁 식사를 검색하고 반복합니다.

DinnerRepository dinnerRepository = new DinnerRepository();

// Retrieve all upcoming Dinners
var upcomingDinners = dinnerRepository.FindUpcomingDinners();

// Loop over each upcoming Dinner and print out its Title
foreach (Dinner dinner in upcomingDinners) {
   Response.Write("Title" + dinner.Title);
}

예제 삽입 및 업데이트

아래 코드에서는 두 개의 새 저녁 식사를 추가하는 방법을 보여 줍니다. 리포지토리에 대한 추가/수정은 "Save()" 메서드가 호출될 때까지 데이터베이스에 커밋되지 않습니다. LINQ to SQL 데이터베이스 트랜잭션의 모든 변경 내용을 자동으로 래핑하므로 모든 변경 내용이 발생하거나 리포지토리에서 저장할 때 변경 내용이 수행되지 않습니다.

DinnerRepository dinnerRepository = new DinnerRepository();

// Create First Dinner
Dinner newDinner1 = new Dinner();
newDinner1.Title = "Dinner with Scott";
newDinner1.HostedBy = "ScotGu";
newDinner1.ContactPhone = "425-703-8072";

// Create Second Dinner
Dinner newDinner2 = new Dinner();
newDinner2.Title = "Dinner with Bill";
newDinner2.HostedBy = "BillG";
newDinner2.ContactPhone = "425-555-5151";

// Add Dinners to Repository
dinnerRepository.Add(newDinner1);
dinnerRepository.Add(newDinner2);

// Persist Changes
dinnerRepository.Save();

아래 코드는 기존 Dinner 개체를 검색하고 해당 개체의 두 속성을 수정합니다. 리포지토리에서 "Save()" 메서드가 호출되면 변경 내용이 데이터베이스에 다시 커밋됩니다.

DinnerRepository dinnerRepository = new DinnerRepository();

// Retrieve specific dinner by its DinnerID
Dinner dinner = dinnerRepository.GetDinner(5);

// Update Dinner properties
dinner.Title = "Update Title";
dinner.HostedBy = "New Owner";

// Persist changes
dinnerRepository.Save();

아래 코드는 저녁 식사를 검색한 다음 RSVP를 추가합니다. 이 작업은 LINQ to SQL 만든 Dinner 개체의 RSVP 컬렉션을 사용하여 수행합니다(데이터베이스에 있는 둘 사이에 기본 키/외래 키 관계가 있기 때문). 이 변경 내용은 리포지토리에서 "Save()" 메서드가 호출될 때 새 RSVP 테이블 행으로 데이터베이스에 다시 유지됩니다.

DinnerRepository dinnerRepository = new DinnerRepository();

// Retrieve specific dinner by its DinnerID
Dinner dinner = dinnerRepository.GetDinner(5);

// Create a new RSVP object
RSVP myRSVP = new RSVP();
myRSVP.AttendeeName = "ScottGu";

// Add RSVP to Dinner's RSVP Collection
dinner.RSVPs.Add(myRSVP);

// Persist changes
dinnerRepository.Save();

삭제 예제

아래 코드는 기존 Dinner 개체를 검색한 다음 삭제되도록 표시합니다. 리포지토리에서 "Save()" 메서드가 호출되면 삭제를 데이터베이스에 다시 커밋합니다.

DinnerRepository dinnerRepository = new DinnerRepository();

// Retrieve specific dinner by its DinnerID
Dinner dinner = dinnerRepository.GetDinner(5);

// Mark dinner to be deleted
dinnerRepository.Delete(dinner);

// Persist changes
dinnerRepository.Save();

모델 클래스와 유효성 검사 및 비즈니스 규칙 논리 통합

유효성 검사 및 비즈니스 규칙 논리 통합은 데이터와 함께 작동하는 모든 애플리케이션의 핵심 부분입니다.

스키마 유효성 검사

LINQ to SQL 디자이너를 사용하여 모델 클래스를 정의할 때 데이터 모델 클래스에 있는 속성의 데이터 형식은 데이터베이스 테이블의 데이터 형식에 해당합니다. 예를 들어 Dinners 테이블의 "EventDate" 열이 "datetime"인 경우 LINQ to SQL 만든 데이터 모델 클래스는 "DateTime" 형식(기본 제공 .NET 데이터 형식)입니다. 즉, 코드에서 정수 또는 부울을 할당하려고 하면 컴파일 오류가 발생하며 런타임에 유효하지 않은 문자열 형식을 암시적으로 변환하려고 하면 오류가 자동으로 발생합니다.

또한 LINQ to SQL 문자열을 사용할 때 자동으로 SQL 값 이스케이프를 처리합니다. 이를 사용하면 SQL 삽입 공격으로부터 보호할 수 있습니다.

유효성 검사 및 비즈니스 규칙 논리

스키마 유효성 검사는 첫 번째 단계로 유용하지만 거의 충분하지 않습니다. 대부분의 실제 시나리오에서는 여러 속성에 걸쳐 코드를 실행하고 모델의 상태를 인식할 수 있는 보다 풍부한 유효성 검사 논리를 지정하는 기능이 필요합니다(예: 생성/업데이트/삭제 중이거나 "보관됨"과 같은 도메인별 상태 내에 있는 경우). 모델 클래스에 유효성 검사 규칙을 정의하고 적용하는 데 사용할 수 있는 다양한 패턴과 프레임워크가 있으며, 이를 지원하는 데 사용할 수 있는 몇 가지 .NET 기반 프레임워크가 있습니다. ASP.NET MVC 애플리케이션 내에서 거의 모든 애플리케이션을 사용할 수 있습니다.

NerdDinner 애플리케이션을 위해 Dinner 모델 개체에 IsValid 속성과 GetRuleViolations() 메서드를 노출하는 비교적 간단하고 직선적인 패턴을 사용합니다. IsValid 속성은 유효성 검사 및 비즈니스 규칙이 모두 유효한지 여부에 따라 true 또는 false를 반환합니다. GetRuleViolations() 메서드는 규칙 오류 목록을 반환합니다.

프로젝트에 "partial 클래스"를 추가하여 Dinner 모델에 IsValid 및 GetRuleViolations()를 구현합니다. 부분 클래스를 사용하여 VS 디자이너에서 유지 관리하는 클래스(예: LINQ to SQL 디자이너에서 생성된 Dinner 클래스)에 메서드/속성/이벤트를 추가하고 도구가 코드와 엉망이 되지 않도록 방지할 수 있습니다. \Models 폴더를 마우스 오른쪽 단추로 클릭한 다음 "새 항목 추가" 메뉴 명령을 선택하여 프로젝트에 새 partial 클래스를 추가할 수 있습니다. 그런 다음"새 항목 추가" 대화 상자에서 "클래스" 템플릿을 선택하고 이름을 Dinner.cs로 지정할 수 있습니다.

Models 폴더의 스크린샷. 새 항목 추가가 선택되었습니다. 저녁 식사 점 c s는 새 항목 추가 대화 상자에 작성됩니다.

"추가" 단추를 클릭하면 Dinner.cs 파일이 프로젝트에 추가되고 IDE 내에서 열립니다. 그런 다음, 아래 코드를 사용하여 기본 규칙/유효성 검사 적용 프레임워크를 구현할 수 있습니다.

public partial class Dinner {

    public bool IsValid {
        get { return (GetRuleViolations().Count() == 0); }
    }

    public IEnumerable<RuleViolation> GetRuleViolations() {
        yield break;
    }

    partial void OnValidate(ChangeAction action) {
        if (!IsValid)
            throw new ApplicationException("Rule violations prevent saving");
    }
}

public class RuleViolation {

    public string ErrorMessage { get; private set; }
    public string PropertyName { get; private set; }

    public RuleViolation(string errorMessage, string propertyName) {
        ErrorMessage = errorMessage;
        PropertyName = propertyName;
    }
}

위의 코드에 대한 몇 가지 참고 사항은 다음과 같습니다.

  • Dinner 클래스 앞에는 "부분" 키워드(keyword) 있습니다. 즉, 이 클래스에 포함된 코드는 LINQ to SQL 디자이너에서 생성/유지 관리하는 클래스와 결합되고 단일 클래스로 컴파일됩니다.
  • RuleViolation 클래스는 규칙 위반에 대한 자세한 정보를 제공할 수 있도록 프로젝트에 추가할 도우미 클래스입니다.
  • Dinner.GetRuleViolations() 메서드를 사용하면 유효성 검사 및 비즈니스 규칙이 평가됩니다(곧 구현할 예정). 그런 다음 규칙 오류에 대한 자세한 정보를 제공하는 RuleViolation 개체 시퀀스를 다시 반환합니다.
  • Dinner.IsValid 속성은 Dinner 개체에 활성 RuleViolations가 있는지 여부를 나타내는 편리한 도우미 속성을 제공합니다. 언제든지 Dinner 개체를 사용하여 개발자가 사전에 확인할 수 있으며 예외가 발생하지 않습니다.
  • Dinner.OnValidate() partial 메서드는 LINQ to SQL 제공하는 후크로, Dinner 개체가 데이터베이스 내에서 유지될 때마다 알림을 받을 수 있습니다. 위의 OnValidate() 구현은 Dinner가 저장되기 전에 RuleViolations가 없도록 합니다. 잘못된 상태이면 예외가 발생하므로 LINQ to SQL 트랜잭션을 중단합니다.

이 방법은 유효성 검사 및 비즈니스 규칙을 통합할 수 있는 간단한 프레임워크를 제공합니다. 이제 아래 규칙을 GetRuleViolations() 메서드에 추가해 보겠습니다.

public IEnumerable<RuleViolation> GetRuleViolations() {

    if (String.IsNullOrEmpty(Title))
        yield return new RuleViolation("Title required","Title");

    if (String.IsNullOrEmpty(Description))
        yield return new RuleViolation("Description required","Description");

    if (String.IsNullOrEmpty(HostedBy))
        yield return new RuleViolation("HostedBy required", "HostedBy");

    if (String.IsNullOrEmpty(Address))
        yield return new RuleViolation("Address required", "Address");

    if (String.IsNullOrEmpty(Country))
        yield return new RuleViolation("Country required", "Country");

    if (String.IsNullOrEmpty(ContactPhone))
        yield return new RuleViolation("Phone# required", "ContactPhone");

    if (!PhoneValidator.IsValidNumber(ContactPhone, Country))
        yield return new RuleViolation("Phone# does not match country", "ContactPhone");

    yield break;
}

C#의 "수익률 반환" 기능을 사용하여 모든 RuleViolations의 시퀀스를 반환합니다. 위의 처음 6개 규칙은 Dinner의 문자열 속성이 null이거나 비어 있을 수 없으므로 적용하기만 하면 됩니다. 마지막 규칙은 좀 더 흥미롭고, ContactPhone 번호 형식이 Dinner의 국가/지역과 일치하는지 확인하기 위해 프로젝트에 추가할 수 있는 PhoneValidator.IsValidNumber() 도우미 메서드를 호출합니다.

를 사용할 수 있습니다. 이 휴대폰 유효성 검사 지원을 구현하기 위한 NET의 정규식 지원. 다음은 국가/지역별 Regex 패턴 검사를 추가할 수 있도록 프로젝트에 추가할 수 있는 간단한 PhoneValidator 구현입니다.

public class PhoneValidator {

    static IDictionary<string, Regex> countryRegex = new Dictionary<string, Regex>() {
           { "USA", new Regex("^[2-9]\\d{2}-\\d{3}-\\d{4}$")},
           { "UK", new Regex("(^1300\\d{6}$)|(^1800|1900|1902\\d{6}$)|(^0[2|3|7|8]{1}[0-9]{8}$)|(^13\\d{4}$)|(^04\\d{2,3}\\d{6}$)")},
           { "Netherlands", new Regex("(^\\+[0-9]{2}|^\\+[0-9]{2}\\(0\\)|^\\(\\+[0-9]{2}\\)\\(0\\)|^00[0-9]{2}|^0)([0-9]{9}$|[0-9\\-\\s]{10}$)")},
    };

    public static bool IsValidNumber(string phoneNumber, string country) {

        if (country != null && countryRegex.ContainsKey(country))
            return countryRegex[country].IsMatch(phoneNumber);
        else
            return false;
    }

    public static IEnumerable<string> Countries {
        get {
            return countryRegex.Keys;
        }
    }
}

유효성 검사 및 비즈니스 논리 위반 처리

위의 유효성 검사 및 비즈니스 규칙 코드를 추가했으므로 Dinner를 만들거나 업데이트하려고 할 때마다 유효성 검사 논리 규칙이 평가되고 적용됩니다.

개발자는 아래와 같은 코드를 작성하여 Dinner 개체가 유효한지 사전에 확인하고 예외 없이 모든 위반 목록을 검색할 수 있습니다.

Dinner dinner = dinnerRepository.GetDinner(5);

dinner.Country = "USA";
dinner.ContactPhone = "425-555-BOGUS";

if (!dinner.IsValid) {

    var errors = dinner.GetRuleViolations();
    
    // do something to fix the errors
}

Dinner를 잘못된 상태로 저장하려고 하면 DinnerRepository에서 Save() 메서드를 호출할 때 예외가 발생합니다. 이는 LINQ to SQL Dinner의 변경 내용을 저장하기 전에 Dinner.OnValidate() 부분 메서드를 자동으로 호출하고 Dinner.OnValidate()에 코드를 추가하여 저녁 식사에 규칙 위반이 있는 경우 예외를 발생시킬 수 있기 때문에 발생합니다. 이 예외를 catch하고 수정할 위반 목록을 사후적으로 검색할 수 있습니다.

Dinner dinner = dinnerRepository.GetDinner(5);

try {

    dinner.Country = "USA";
    dinner.ContactPhone = "425-555-BOGUS";

    dinnerRepository.Save();
}
catch {

    var errors = dinner.GetRuleViolations();

    // do something to fix errors
}

유효성 검사 및 비즈니스 규칙은 UI 계층이 아닌 모델 계층 내에서 구현되므로 애플리케이션 내의 모든 시나리오에서 적용되고 사용됩니다. 나중에 비즈니스 규칙을 변경하거나 추가하고 Dinner 개체에서 작동하는 모든 코드를 적용할 수 있습니다.

애플리케이션 및 UI 논리 전체에서 이러한 변경 내용이 파급되지 않고 한 곳에서 비즈니스 규칙을 유연하게 변경할 수 있는 것은 잘 작성된 애플리케이션의 표시이며 MVC 프레임워크가 권장하는 이점입니다.

다음 단계

이제 데이터베이스를 쿼리하고 업데이트하는 데 사용할 수 있는 모델이 있습니다.

이제 프로젝트에 HTML UI 환경을 빌드하는 데 사용할 수 있는 일부 컨트롤러와 뷰를 추가해 보겠습니다.