검색결과 리스트
..'SilverLight Story'/'Shorty' Tutorials에 해당되는 글 23건
- 2009/10/22 [Silverlight] 버전 관리 팁
- 2009/07/13 [Silverlight 3] Sketch Flow (스케치플로우) / 기대가 너무 컸던건가..
- 2009/07/02 [.NET RIA Services] #8. 메타데이터 클래스를 쓰자
- 2009/06/29 [MVVM + Command Pattern] TreeView를 사용해보자! [3] - Deleting
- 2009/06/17 [MVVM + Command Pattern] TreeView를 사용해보자! [2] - Commanding
- 2009/06/16 [.NET RIA Services] #7. 구성과 전체 구조의 이해
- 2009/06/16 [MVVM + Command Pattern] TreeView를 사용해보자! [1] - MVVM and Editing (4)
- 2009/06/12 [.NET RIA Services] #6. 커스텀 메서드와 서비스 오퍼레이션
- 2009/06/09 [.NET RIA Services] #5. 엔티티 수정법 (1)
- 2009/05/19 [.NET RIA Services] #4. 엔티티 쿼리
글
쇼티입니다. 간단하게 버전체크하는 방법을 알려드리려고 해요.
Silverlight.js라는 파일이 기본 샘플프로젝트에 포함이 되어있죠. 이 js 파일이 사실 굉장히 유용한 함수들이 많지요.
(http://msdn.microsoft.com/ko-kr/library/cc838126(VS.95).aspx)
물론 버전관리하는 함수도 있습니다. 바로 isInstalled 라는 함수입니다.
(bool) = Silverlight.isInstalled(버전번호)
이렇게 들어가구요. 이 버전 번호는 각각,
| 버전 | MIME TYPE | 버전 번호 | 설치관리자 URL |
| Silverlight 1.0 | application/x-silverlight | 1.0 | |
| Silverlight 2.0 | application/x-silverlight-2, | 2.0.31005 | |
| Silverlight 3.0 | application/x-silverlight-2, | 3.0.40620 |
이렇습니다. 그래서, isInstalled('2.0'), isInstalled('3.0') 이라고 넣어주는것이 아니라,
isInstalled('2.0.31005'), isInstalled('3.0.40620') 으로 확실히 넣어주어여 bool 값이 제대로 찍힙니다.
그리고 버전에 관계없이 실버라이트 설치 여부는 isInstalled(null) 로 식별이 가능하니 이것도 참고해주시구요.
그리고 MIME TYPE은 <object> 태그에서의 마임입니다. 절대 iis의 마임타입이 아닙니다.
iis의 마임타입은 'application/x-silverlight-app' 입니다.
혹시 실버라이트 배포하실때 2104 에러가 났다면 십중팔구 마임타입 설정 안되있는 것입니다. 해당 xap가 들어있는 디렉토리
에 MIME 타입을 걸어주세요.
참고되시길 바랍니다. ^^
설정
트랙백
댓글
글
쇼티예요. 이번 릴리즈 된 실버라이트 3에는 Blend에 Sketch Flow라는 기능이 추가가 되었습니다.
이게 무엇이냐 하면, 기업에서 쓰는 워크플로우를 실버라이트 버전으로 구성한 것이라고 할 수 있겠습니다.
(자세히는 모르겠는데, 일반적으로 이걸로 서비스를 할 수는 없어보이는군요)
일단 무엇인지를 알아보기 위해서 튜토리얼을 간단하게 해보겠습니다.
(출처 : 튜토리얼 참고는 이곳에서 했습니다)
일단 blend 3에서 프로젝트를 새로 만드시려 하시면 다음과 같은 메뉴를 볼 수 있습니다.
WPF로도 SketchFlow 버전이 있습니다만. 실버라이트 3를 선택해보도록 하겠습니다.
만드시면.. 일단 프로젝트 내용을 보면.
Fonts는, 스케치플로우에 필요한 폰트들이 들어가있는 곳입니다. 이건, 스케치플로우 전용 컨트롤들이 여기에 걸려있는듯
보입니다. 새로 추가할 수 있는지는 모르겠습니다.
Screen 1은, 가장 맨 처음에 띄울 장면, Sketch.Flow는 이 스크린들이 어떤 루트로 가는지를 설정해둔 파일이구요.
SketchStyles.xaml은, 이 스케치플로우를 돌릴때 필요한 스타일들을 정의해둔 곳입니다.
그니까 이런 식으로 사용이 가능합니다. 기획자가 어떤 프로젝트에 대한 기획을 한다고 해봅시다.
그리고, 맨 처음에 PPT로 제안서를 제출하죠. 이때 이 제안서를 만드는 과정이라 보시면 됩니다.
홈페이지를 예로 들면, '홈', '회사 소개', '제품 소개', '컨택트 어스' 이런 식으로 메뉴가 쭉 있습니다.
그리고, '회사소개'에는 '연혁', 'CEO 인사말', '조직구성' 등등이 들어가겠지요. 이렇게 구성을 하는것입니다.
위 표를 보시면, 맨 처음에 Start입니다. 여기 다음은 WelCome이고, 그다음에 그 부속으로 Customers, Products, Orders가
있고, 이 다음단계는 공통으로 CheckCustomerReport가 있고, 그 다음으로 CheckOut이 있습니다.
그래서 흔히 기업에서 이야기하는 '워크플로우'를 구성하는 것이 목적입니다.
워크플로우를 구성하면서 가장 중요한것은 뭐가 있을까요. 'FeedBack'이 제일 중요할 것입니다.
이 제안서를 나만 볼려고 만드는게 아니라, 여러사람에게 보여주고 '뭐가 잘못됐다' '이건 이렇게 했으면 좋겠다' 라고,
자문을 구해서, 그걸 수정해서 최종적으로 OK 사인을 받고 일정을 잡고... 이런 식이죠.
일단 밑에 Screen 1이라는 것이 보일 것입니다. 이것의 이름을 Start로 바꾸고, 여기에 마우스를 대면 밑에 메뉴가 뜨는데,
여기서 create a connected screen을 누릅니다.
한번 해보시면 대충 방식을 아실 듯 보입니다. 이렇게 해서, 이 전의 그림처럼 플로우를 구성해줍니다.
(보시면, 이 스크린 하나 만들때마다 xaml은 계속 생기는 것을 볼 수 있지요)
여기까지 누르시고 f5로 빌드 + 실행을 해보시면 이와 같이 나옵니다.
기본 스케치플로우의 아웃풋입니다. 좌측의 Navigate는, 이 상태에서 현재 갈 수 있는 곳을 쭉 표시해줍니다.
지금 현재는 Start의 화면이기때문에, 나오는 것이 Welcome밖에 없겠지만 아마 Welcome을 선택한 후에는
예의 3가지가 뜨겠지요.
그리고 프로그레시브 바로 이 배율을 지정해줄 수 있고.. 밑에는 피드백을 위한 도구들이 있습니다.
이정도입니다. 이제부터는 여러분들이 화면을 구성하시고, 디자인하셔서 이것을 서버에 올려놓고 피드백을 다른 분들
에게 받으시면 됩니다.
중간에 한가지 팁이 나오는데요. 만약 한 화면에 중복된 컴포넌트를 쓰고 싶다라고 하시면, 이걸 따로 빼는 방법이 있어요.
이렇게, 자주 쓰이겠다 싶은 구성물들을 그룹으로 묶어주고 오른쪽 버튼으로 Make into Component Screen을 누르면,
플로우 맵에 다음과 같이 나타나네요.
그래서, 다음과 같이 이 컴포넌트가 필요한 스크린에 전부 걸 수 있습니다.
그다음에 스테이트를 줄 수 있고, 네비게이트를 줄 수 있습니다.
그리고, 이제 이것을 서버에 올려놓으면 피드백을 받을 수 있는데요. 피드백 주는 방법은 간단합니다.
그냥 그리고 저장해두면 됩니다.
필요한 부분을 표시를 하고, 왼쪽 아래와 같이 브리핑을 할 수도 있습니다.
그래서 브리핑부분 위에 '폴더' 모양 비스무리하게 되어있는 곳을 클릭하면 'Export'를 선택할 수 있는데,
여기서 이름과, 파일로 저장이 가능합니다.
이렇게 되면, 블랜드로 열었을 때, 피드백 탭을 선택하여 상대가 피드백을 지정한 부분이 보이게 되는거죠.
이것이 대강의 스케치플로우의 흐름입니다만. 글쎄요.
스타일러스(전자펜 같은거)가 지원이 되는지는 잘 모르겠습니다만, 그닥 고객들이 선뜻 쓰려 하지 않을꺼같다는 느낌이
드네요. 왠지 '기획'이라는걸 하면서도 프로그래밍을 해야된다는 느낌이랄까요.
이 기능은 아마도 개발자보다는 PM이나, 그쪽에서 많이 쓸 듯 싶은데, 좀 많이 복잡하네요.
블렌드에서 스케치플로우를 쓰는건 무리수가 아니었나 하는 생각이 듭니다.
그리고 원터치로 서버 변수를 지정해주면 자동으로 올릴 수 있는 기능이.. 혹시 있는지는 모르겠습니다만.
이거 바꾸고 계속 서버(IIS 등)로 옮겨줘야 한다는 것이 또한 애로사항일 지도 모르겠습니다.
취지는 좋았지만, 좀 더 '보여주기' 식이 아닌, '주 고객층이 누구인지를 생각하는' 모델이 되었으면 하는
아쉬움이 있습니다.
그냥 'PPT'로 그리고 말죠..
설정
트랙백
댓글
글
이제 메타데이터에 대한 이야기를 해보려 합니다. 3장에서 잠시 메타데이터에 대해서 알아보았었는데,
이번 8장과 9장은 이 메타데이터를 다루는 방법에 대해서 자세히 알아보겠습니다.
Entity가 Partial Class라는 점을 이용합니다. 분리형 클래스로 하나는 엔티티, 하나는 메타데이터.. 이렇게 서로 합쳐서
속성을 가미한 클래스가 탄생(?)을 하는거죠.
메타데이터는 왜 사용을 할까요. 이 컬럼의 속성을 따로 관리하는 클래스를 서버단에서 만드는데 그 이유가 있지요.
Generated Code에서 알맞은 속성을 부여하다가, 리뉴얼되서 그 속성이 망가지는 경우도 있을 수 있겠고..
따로 속성을 관리하는 클래스를 만든다는 측면에서. 유지보수도 쉽겠죠.
이 장에서는 어떻게 만드는지. 만 간략하게 보고, 다음장에서 이것을 이용하여 실제로 유효성 체크하는 법을 알아보도록
하겠습니다.
[ 준비 사항 ]
1. 일단 ADO.NET Entity Framework 같은, 엔티티가 하나 있어야죠 (대상이 있어야 그 대상의 속성도 만들테니까..)
2. 메타데이터로까지 확장시켜야 할 엔티티클래스와 메타데이터 클래스는, Partial class여야 합니다.
(엔티티와 메타데이터를 합쳐서 Generated Code를 만든다는거죠)
두가지가 있습니다. 하나는 자동으로 생성되는거, 하나는 일일이 다 치는거(-_-).
이와 비슷한걸 3장에서 해봤기때문에, 그냥 눈으로 보셔도 됩니다.
[ 방법 / 자동 ]
1. 웹 프로젝트에 Domain Service Class를 추가합니다.
2. 밑에 Generate associated classes for metadata 를 체크합니다.
(이때 미리 생성해둔 Entity가 있어야합니다)
다 됐습니다. (...) 하나는 DomainService Class이고, 하나는 Metadata Class입니다.
[ 방법 / 수동 ]
이번엔 수동으로 하는 방법을 알아보겠습니다. 가령 엔티티가 이렇다고 해보죠.
(이 엔티티는 물론 미리 생성되어있어야 합니다)
1. 일단 "ProductMetadata.cs"라는 파일를 만듭니다.
2. 이 파일 안에 ProductMetadata라는 클래스를 만듭니다.
3. 그다음에 Product "Partial" class를 만듭니다. 엔티티와 같은 이름의 부분클래스를 만드는 것이지요.
엔티티 클래스와 비교해보시면서 만드세요. 네임스페이스, 엔티티 클래스 네임. 모든것이 같아야 합니다.
4. 이 Product partial class 속성을 MetadataType이라고 하세요. 그리고 2번에 만들어두었던 클래스를 typeof로
넘기세요.
[MetadataType(typeof(ProductMetadata))]
이런 느낌으로 하시면 됩니다. 즉, 메타데이터 타입을 설정하고, 메타데이타로 사용할 클래스를 넘긴다는 뜻입니다.
5. 이제 이 ProductMetadata 클래스 안에 여러분들이 필요한 컬럼의 유효성이나, 속성을 설정하시면 되겠습니다. 이
Product 엔티티에 보면, ProductName이라는 속성이 있는데, 여기에 속성을 줘보도록 하죠.
(스샷에서는 Name에 줬습니다만.. 뭐 상관없습니다.)
이런 느낌으로 하시면 됩니다.
|
namespace NorthwindExample { using System; using System.ComponentModel.DataAnnotations; using System.Data; // The MetadataTypeAttribute allows us to associated the // ProductMetadata type with our Product entity type. [MetadataType(typeof(ProductMetadata))] public partial class Product { } // This class is used to provide additional metadata. public partial class ProductMetadata { // Adding additional validation metadata to the // Product.ProductName property by placing it here. [Required] [RegularExpression("[A-Z][A-Za-z0-9]*")] [StringLength(32)] public string ProductName; } |
둘다 해보시면 아시겠지만, 자동으로 하면 현재 이 Product의 필드가 전부 기록이 됩니다. 그래서 필요한것들을 골라서
속성을 지정하고 나머지는 지우고.. 그러셔도 되죠.
사실 익숙해지시면 그냥 새로 하나 만드는 방법이 나을꺼같아보여요. 생각보다 코딩해야될 내용이 별로 없습니다 ^^
지금까지, 간단하게 엔티티 속성에 메타데이터를 삽입하는 방법에 대해 알아보았습니다.
다음 포스트에서는 이 메타데이터로 실제로 어떻게 유효성을 체크하는지를, 예제와 함께 알아보겠습니다.
감사합니다. ^^
설정
트랙백
댓글
글
쇼티예요.
바로 전 포스트에서는 리스트뷰에 아이템을 삽입하는 법을 알아보았었습니다. 그럼 이번엔
삭제하는 방법을 알아보도록 하겠습니다.
거두절미하고 일단 예제~!
| <slt:TreeView.ItemTemplate> <slt:HierarchicalDataTemplate ItemsSource="{Binding SubTopics}"> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Name}" VerticalAlignment="Center" /> <Button ToolTipService.ToolTip="Delete Topic" Margin="2,0,0,0" input:CommandService.Command="DeleteTopic" input:CommandService.CommandParameter="{Binding}"> <Image Source="/EditableTreeView;component/Resources/delete.png" Stretch="None" /> </Button> </StackPanel> </slt:HierarchicalDataTemplate> </slt:TreeView.ItemTemplate> |
1장이었죠? 거기서 썼던 예제를 이렇게 변형해보았습니다.
보시면 일반 트리뷰에 아이템 템플릿, 즉 이 '리스트뷰'에 나타나는 객체는 TextBlock과 Button입니다.
쉽게 말해서, 이렇게 만들겠다는 이야기예요.
저 - 표시가 있는 버튼을 눌렀을 경우, DeleteTopic이라는 커맨드가 실행이 되게끔 하는 것입니다.
그리고, 비하인드 코딩은 이렇게.. 여전히 PageViewModel.cs에서 코딩을 해주시면 됩니다.
| private void OnDeleteTopicExecuted(object sender, ExecutedEventArgs e) { HelpTopic topic = e.Parameter as HelpTopic; bool isRemoved = topic.RemoveFromStructure(HelpTopics, item => { return item.SubTopics; }); if (!isRemoved) { throw new InvalidOperationException(string.Format("Topic '{0}' couldn't be removed.", topic.Name)); } } |
2장에서 하신대로 똑같이, 이벤트 핸들러 함수를 잡아주시고, 이렇게 코딩하시면 됩니다.
e.Parameter에서, 부모값(그 단락의 최상위 제목)을 참조하여 제거하는 것이지요.
(소스에 보시면 LinqExtensions.cs라는 파일에 RemoveFromStructure라는 함수가 정의되어있습니다. 이 분이 편의를 위해서
만든 것으로 보이네요.. ^^)
이제 제거가 완료되었습니다. 그런데, 살짝.. 이게 하나하나 다 - 표시가 보이는건 좀 지저분해 보이죠.
이것을 딱 '선택한' 부분에만 나타날 수 있도록 하려면 어떻게 해야 할까요..
전체 소스에서는 App.xaml에서 이것들을 정의하고 있습니다. (근데 스타일 양이 적진 않네요.. 밑의 소스를 참고해주세요)
여기서는 간단히 그 과정만 한번 보도록 하겠습니다.
| <StackPanel Orientation="Horizontal"> <ContentPresenter x:Name="content" Cursor="{TemplateBinding Cursor}" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="Left" Margin="{TemplateBinding Padding}" /> <Button x:Name="commands" ToolTipService.ToolTip="Delete Topic" Margin="4,0,0,0" Opacity="0" input:CommandService.Command="DeleteTopic" input:CommandService.CommandParameter="{Binding}"> <Image Source="/EditableTreeView;component/Resources/delete.png" Stretch="None" /> </Button> </StackPanel> |
지금 이것은 그 App.xaml에 들어가있는 이 리스트뷰 각각의 아이템을 스타일로 다시 구성해둔 것입니다.
살짝, '어디서나 쓸수 있는' 스타일은 아니게 되는것이 조금 문제같긴 하지만.. 아무튼 이렇게 해서 일단
버튼의 Opacity를 0으로 맞춰준 후에
| <DoubleAnimation Storyboard.TargetName="commands" Storyboard.TargetProperty="Opacity" Duration="0" To="1" /> |
마우스가 이 해당 아이템에 올라갔을 때, 버튼을 보이게 하는 구조로 나가면 되는 것이지요.
(MouseOver 같은 State를 감지하면 될 듯 싶습니다)
소스에서는 이 스타일을 TreeVireItemContainerStyle이라는 이름으로 정의했습니다.
| <slt:TreeView VerticalAlignment="Stretch" Grid.Row="1" ItemsSource="{Binding HelpTopics}" ItemContainerStyle="{StaticResource TreeVireItemContainerStyle}"> <slt:TreeView.ItemTemplate> <slt:HierarchicalDataTemplate ItemsSource="{Binding SubTopics}"> <TextBlock Text="{Binding Name}" VerticalAlignment="Center" /> </slt:HierarchicalDataTemplate> </slt:TreeView.ItemTemplate> </slt:TreeView> |
몇가지 주의할 점은,
이 TextBlock의 중복은 일종의 트릭입니다. 보시면 기본 ItemTemplate에도 TextBlock이 있고, 스타일에도 Button과 함께
TextBlock이 있습니다. 이는 '마우스 오버' 시에 Rectangle을 하이라이트 시키기 위해서입니다. 스타일에 보시면 이 TextBlock 을 포함하는 content라고 이름지어진 ContentPresenter가 있는데, 이 것과 겹치는 효과를 내서 테두리 및 둘러싼
사각형에 하이라이트 효과를 주는 것이지요.
그리고, 아마.. 이 원 소스의 Toolkit은 구버전이라 아마 네임스페이스 에러가 날 것입니다. (XAML에서)
이 현상은 첫번째 포스트를 참고하셔서 바꾸시면 되겠습니다. ^^ 레퍼런스를 새로 추가하셔서
(System.Windows.Controls.Toolkit), slt의 네임스페이스를 바꿔주시고, HierarchicalData가 이 툴킷의
(System.Windows)에 있습니다.
그러면 이렇게 됩니다.
깔끔해졌죠.. ^^ 여기까지입니다. 다음은 수정하는 방법에 대해서 해보겠습니다.
감사합니다.
(소스는 이곳에서 다운받으실 수 있습니다.)
설정
트랙백
댓글
글
쇼티예요.
이번엔 저번에 이어서 Command를 내리는 방법을 살펴볼 예정입니다. MVVM 패턴에서는
'액션이 가능한 엘리먼트의 액션과 연결하여 어떤 일련의 행동들을 정의하는' 방법이 있습니다.
.... 뭔 말인지 모르겠습니다. 쉽게 풀어보자면.
'액션이 가능한 엘리먼트(Actionable Element)'라는 것은 '유저가 일으킬 수 있는 이벤트가 있고, 그 이벤트가 발생하면 일련의 동작을 취할 수 있는 엘리먼트'를 말합니다.
버튼의 경우를 보죠. 사용자가 이 버튼을 클릭합니다. 그러면 발생되는 이벤트는? 그렇져. Click 이벤트지요.
우리가 이 이벤트가 일어났을 때, 콜백 함수를 만들 수가 있었죠? 그래서.
클릭되는 모든 것들은 '액션이 가능한 엘리먼트' 라고 할 수가 있겠네요.
그럼 텍스트박스는 어떨까요. 이거 액션이 가능한가요? 이것도 맞지요.
여러가지 이벤트를 발생시킬 수 있지요. 엔터가 눌렸을 때라던지..
리스트박스는 어떨까요. SelectionChanged 이벤트. 그렇죠?
그럼 그 '액션'이라는걸 다시 볼께요. 아주 간단한 이벤트로. 또, 가장 만만한(-_-)걸로. 버튼을 예로 들어보겠습니다.
우리가 일반적으로 Button을 XAML에 정의합니다.
| <Button Click="Button_Click" /> |
그리고 비하인드 코드에 다음과 같이 클릭이벤트에 대한 핸들러 함수를 쓰겠죠?
| private void Button_Click(object sender, RoutedEventArgs e) { // 어쩌고 저쩌고 } |
이걸 다음과 같이 바꿉니다.
| <Button Command="커맨드명" /> |
그리고 이벤트 핸들러 함수가 xaml.cs가 아닌, ViewModel로 지정되어있는 cs에 쓰게 된다면, 똑같은 역할을
하는거겠죠.
실제로, 후에 공용소스를 보시면 아시겠지만, 이 버튼에 커맨드를 달아놓고, 이 버튼 자체에 Command를 주면, 자동적으로
이 Command가 '언제' 발생될지에 대해서 정의하는 부분이 있습니다. 그래서, 이경우에 Click을 했을 때,
커맨드 명에 따라 주어진 행동을 하게 되는 것이지요.
이렇게 되면, 핸들러 함수때문에, 어쩔 수 없이 버튼 달아놓고 클릭이벤트를 xaml.cs에 쓰는 경우를 막을 수 있겠죠.
애초에 목표가 xaml.cs를 썰렁하게 하자. 라고 하면, 꽤 좋은 성과가 있는 것입니다.
이 Commanding이 WPF와 Silverlight가 다른 점 중에 하나인데요. 사실 WPF에는 이런 Commanding 기술이 많습니다
(라고 합니다..;; 해보지 않아 잘은 모르겠네요). 그러나 실버라이트는 거의 이런 기능이 없죠. ICommand라는 인터페이스
가 있긴 한데, 너무나 부실합니다.
그러다가 최근의 실버라이트3에서는 이 기능이 결국 정식으로 서비스가 된다 하죠. Attached 'Behavior'라는 기능으로..
그럼. 어떻게 하는가. 예제를 통해 알아보도록 하겠습니다. 전 포스트에서 썼던 프로젝트를 그대로 가져와볼께요.
이 기능을 하려면, 네. 누가 만든 코드를 가져와야되죠..; 실버라이트에서 정식으로 제공하는 기능은 아니기때문에.
CodeGuru에서 SLExtention이라는 아주 멋진 프로젝트가 있습니다. 굉장히 잘 알려진 예제 소스입니다.
이 압축을 푸시고 나면, SLExtentions라는 디렉토리 안에 Input이라는 디렉토리가 있습니다. 여기 cs파일이 7,8개정도
있는데, 이것을 다 우리의 프로젝트로 옮겨주고, 안에 있는 네임스페이스를 프로젝트명과 맞게 변경을 합니다.
새 아이템을 추가하셔서 Command.cs라는 파일을 만들고, 이렇게 코딩을 합니다.
| public static class Commands { static Commands() { AddTopic = new Command("AddTopic"); DeleteTopic = new Command("DeleteTopic"); } public static Command AddTopic { get; private set; } public static Command DeleteTopic { get; private set; } } |
이 Command는, 추가한 SLExtentions 중에 Command.cs를 쓰는 것이기때문에, 색이 안변한다. 싶으시면
네임스페이스를 한번 확인해주세요.
대충 보면 컬럼을 새로 만들고, 지우고.. 하는 두개의 커맨드같애보여요.
그리고, xaml.cs로 버튼을 두개 추가합니다. 적당한 곳에 추가를 해주세요.
| <StackPanel Orientation="Horizontal"> <Button Content="Add Topic" input:CommandService.Command="AddTopic" /> <Button Content="Delete Topic" input:CommandService.Command="DeleteTopic" /> </StackPanel> |
보시면, CommandPattern을 보시면서 많이 보던 용법이 나오죠. input:CommandService...
이게 여기서 나오는것입니다. 당연히 XAML 내의 input으로, 아까 그 SLExtentions를 끌어왔던 디렉토리를 잡아주셔야하구요.
우리가 앞에서 AddTopic이라는 명령을 추가했기 때문에, 첫째버튼이 동작을 하고,
DeleteTopic이라는 명령을 추가했기 때문에, 두번째 버튼이 동작을 하는 것이죠.
그리고, ViewModel에 이벤트 핸들러 처리를 해줍니다.
| (PageViewModel.cs 의 생성자 부분이나.. 원하는 곳에) Commands.AddTopic.Executed += new EventHandler<ShortyCPattern.Input.ExecutedEventArgs> (AddTopic_Executed); Commands.DeleteTopic.Executed += new EventHandler<ShortyCPattern.Input.ExecutedEventArgs> (DeleteTopic_Executed); |
| (생성된 이벤트 핸들러 함수에) private void OnAddTopicExecuted(object sender, ExecutedEventArgs e) { HelpTopics[0].SubTopics.Add(new HelpTopic() { Name = "Custom Topic" }); } private void OnDeleteTopicExecuted(object sender, ExecutedEventArgs e) { if (HelpTopics[0].SubTopics.Count == 0) { return; } HelpTopics[0].SubTopics.RemoveAt(HelpTopics[0].SubTopics.Count - 1); } |
실행시켜서 버튼을 눌러보면, 잘 되는 것을 알 수 있습니다. HelpTopics[0]에 추가했기 때문에, 첫번째 아티클의 하부
HelpTopics들에게만 더하고 빼기가 적용이 되겠군요. 여기까지, Page.xaml.cs에는 InitializeComponent() 말고는
없죠.. ^^
델같은 경우, 저렇게 Count == 0인지 체크해서 return 을 호출하기때문에 스택 언더플로우는 일어나지 않겠습니다.
비슷하게, CanExecuted 이벤트도 저렇게 이벤트 핸들러로 잡아주고, 이 값을 쓰는 방법도 있습니다.
| private void DeleteTopic_CanExecute(object sender, CanExecuteEventArgs e) { e.CanExecute = HelpTopics[0].SubTopics.Count > 0; } |
이건 필요할 때 쓰실 수 있겠죠. 소스에서는
| Commands.DeleteTopic.RaiseCanExecute(null); |
조금, 복잡하긴 하지만, xaml.cs에 아무것도 쓰지 않는다는 것은 곳, 이 모듈을 다른쪽에 옮기기에도 용이하다는 이야기니까,
유용하게 쓰실 수 있을 것입니다.
다음 장에서는, Delete 기능을 좀더 꾸며보도록 하겠습니다.
감사합니다. 소스는 이곳에서 직접 받으실 수 있습니다.
설정
트랙백
댓글
글
Domain Service Class에서 각 Operation들을 사용할 때의 규칙을 정리해 보겠습니다.
[ Insert ]
- void 형이어야 할 것. 첫번째 파라미터로 Entity에 해당하는 객체가 올 것
- 접두어가 Insert / Add / Create 중 하나일 것
public void InsertEmployee(Employee newEmployee) {...}
- 단, 앞에 [Insert] 속성을 붙이면 함수 이름은 관계없음
[Insert]
public void YourFavoriteMethodName(Employee newEmployee) {...}
[ Update ]
- void 형이어야 할 것. 첫번째 파라미터, 혹은 첫번째와 두번째 파라미터로 Entity에 해당하는 객체가 올 것
- 접두어가 Update / Change / Modify 중 하나일 것
public void UpdateEmployee(Employee changedEmployee, Employee
originalEmployee)
- 단, 앞에 [Update] 속성을 붙이면 함수 이름은 관계없음
[Update]
public void YourFavoriteMethodName(Employee changedEmployee, Employee
originalEmployee)
[ Delete ]
- void 형이어야 할 것. 첫번째 파라미터, 혹은 첫번째와 두번째 파라미터로 Entity에 해당하는 객체가 올 것
- 접두어가 Delete / Remove 중 하나일 것
public void DeleteEmployee(Employee currentEmployee, Employee
originalEmployee)
- 단, 앞에 [Delete] 속성을 붙이면 함수 이름은 관계없음
[Delete]
public void YourFavoriteMethodName(Employee currentEmployee, Employee
originalEmployee)
[ Query ]
- 리턴형은 IEnumerable<T>나 IQueryable<T>. 이때 T는 엔티티여야 함
- 접두어가 Get / Fetch / Find / Query / Retrieve / Select 중에 하나일 것
public IQueryable<Employee> GetEmployee() {...}
- Generated Code 단으로 가면 이름이 바뀔 가능성이 있음. 즉 GetEmployee()라고 Domain Service Class에서 썼다면,
이는 Employee 엔티티를 '얻는' 개념. Generated로 가는 클라이언트에선 Employee를 '읽는' 개념. 이렇게 바뀌긴 하지만
그냥, 바뀐 이름으로 호출하면 끝나는 문제
- [Query] 속성을 붙여서 명확히 표시해줄 수 있음 (꼭 필요하진 않아요)
[Query]
public IQueryable<Employee> GetEmployee() {...}
- 쿼리를 멀티로 짤 수도 있지만, 조심해야합니다. 예를 들어 GetEmployee와 FetchEmployee로 두 오퍼레이션을 짰다면,
클라이언트단에선 전부 LoadEmployee()로 변할 수 있고, 이 과정에서 에러가 날 수가 있습니다.
- 속성에 PreserveName = true로 하면, 클라이언트단으로 갈 때 메서드 이름이 바뀌지 않음
[Query(PreserveName=true)]
public IQueryable<Employee> GetEmployee() {...}
[ Custom ]
- void형이어야 할 것. 첫번째 인자로 Entity형이 와야하고, Insert, Update, Delete 형태의 로직이 아니어야 할 것
public void ApproveEmployee(Employee changedEmployee, ...)
- [Custom] 속성을 붙여서 명확히 표시해줄 수 있음 (꼭 필요하진 않아요)
[Custom]
public void ApproveEmployee(Employee changedEmployee, ...)
[ Resolve ]
- bool형이어야 할 것. 인자는 세개의 Entity 필요.
- 접두어는 무조껀 Resolve여야 할 것
public bool ResolveEmployee(PurchaseOrder currentEmployee, PurchaseOrder originalEmployee, PurchaseOrder storeEmployee, bool deleteOperation)
- Update 의 로직구현이 필요합니다. 별도의 속성은 없습니다.
[ Service Operation ]
- [ServiceOperation] 속성을 반드시 붙여야 함. 그 이외에는 제약사항 없음.
[ServiceOperation]
public byte[] GetProductImage(){...}
[ Opt-Out ]
- Omit~♡
[IgnoreOperation]
public void InsertEmployee(Employee newEmployee) {...}
- 클라이언트 코드로 전달되지 않는다.
----------------------------------------------------------------------------------------------------------------
지금까지 일반적인 오퍼레이션의 사용법에 대해서 알아보았습니다. 그닥 특별한 내용이 없네요.
다음 포스팅은 '메타데이터를 사용하는 방법' 에 대해서.입니다.
감사합니다.
설정
트랙백
댓글
글
쇼티예요.
MVVM, Command Pattern. 이런것들 다 한번씩은 들어보셨을 꺼예요.
왠지, 코드를 쪼개야할것 같은 느낌이 들고, 왠지 이렇게 짜면 나중에 편할 것 같은 그런 느낌 있죠?
그런데, 참 개념잡기가 힘들지 않으셨나요. 처음부터 어떻게 하는거다라고 설명을 해주는 포스트나 강의가 있었음 좋겠는데,
다~ 완성된 소스 가지고서 이건 어떻다 저건 뭐다 참 강력한 기능이다 .... 도대체 뭐가 어떻고 저게 뭐며.. 왜 강력한지..
모 따라해볼라고 본문 열심히 읽고 따라해볼라다가, 막혀서 '뭐지?' 하고 링크걸려있는 소스를 받아보면..
본문에 없는 코드가 왜 그리 많은지..
(특히 Nikhil씨.. 난 당최 이아저씨가 무슨 말을 하는지 이해를 하지 못하겠어어어억!!!)
저도 마찬가지입니다. 이거, 개념이 어떤지 정확하게 전 잘 모르겠습니다. 앞으로 많이 배워야 할 부분이라고 생각하구요.
이 포스팅도 그 개념에 기인합니다. 그런거 몰라요. 하지만 이렇게 대충 짠다.. 라고 조금이라도 감을 잡으셨다면!
성공했다 볼 수 있겠죠.. ^^
또, 이런걸 한번 해보시고, 관련 포스팅을 보신다면. 훨씬 이해가 쉽게 되는 그런 부분도 있구요.
그냥 무작정. 왜그런지도 잘 모르겠어요. 그냥 하라는대로 해봅시다 ^^
MVVM은, 말그대로 Model + View + ViewModel의 디자인패턴을 가지고 있습니다.
그냥.. 데이타 담는 통(Model) + 데이타 보여주는 보드(View) + 컨트롤하는 분필(ViewModel) 정도로
이해하시면 될 것 같습니다. 이렇게 나누어서 코딩하는 디자인 패턴을 MVVM이라 하지요 보통..
Command Pattern은, 한마디로 Command로 제어하는 패턴입니다.
다음 포스팅부터 Command를 사용하여 제작하는 방법을 따라해보실 것입니다. ^^
자 일단, Toolkit을 준비하시구요. 쓸 컨트롤은 TreeView입니다. 네..
이렇게 한번 만들어볼꺼예요. 새 프로젝트를 생성하셔서, Reference에 Toolkit을 추가해주세요.
코드의 목표는. 최종적으로 Page.Xaml.cs에 가급적이면 쓰지 않는 것을 목표로 한다는군요.
이게 왜냐면, 이 모듈을 다른곳을 옮길 때, Page.xaml.cs에서 무언가를 쓰게 되면, 모듈을 이전시킬 때
생각할게 더 많아지기 떄문이지요..
일단 데이타를 만들어보겠습니다. HelpTopic.cs를 프로젝트에 추가해줍니다.
더보기
그냥, 이름과 자기 자신을 참조하는 ObservableCollection이라는 제네릭형 리스트 변수를 만들었습니다.
트리뷰니까요. 그 하위에도 똑같은게 있을 꺼니까요.
그다음 또 Class 하나를 만드셔서, 다음과 같이 코딩 해줍니다.
더보기
일명 '노가다' 라고 불리는 그런 코드예요.. 물론 현업에서는, 이렇게 써선 안되겠지만, 그냥 예제로 든 것이기때문에.. ^^
앞에서 정의한 HelpTopic을 가지고, 트리뷰의 구조를 만들고 있다고 보시면 되겠습니다.
여기까지가, Model 개념의 코딩입니다. 재료를 다 만들어두는거죠.
이 ObservableCollection은 MVVM의 중추가 되는 객체입니다. 이 클래스는 일반 Generic 클래스에 INotifyPropertyChanged
를 자동으로 구현한 클래스이기 때문이지요. 따라서, 이 프로퍼티가 바뀐걸 알려주고, 어떤 행동을 취할 수 있게끔 자동으로
해주기때문에, 그만큼 코드의 양도 줄어들고, 유지보수하기도 편합니다.
이제 이걸 만들었으면 써야지요. PageViewModel.cs라고 클래스를 하나 만드시고 다음을 코딩해주세요.
더보기
이 부분이 ViewModel에 해당합니다. 이 ViewModel은 HelpTopic이라는 프로퍼티가 하나 있군요.
지금은 이거밖에 필요가 없으니까요. 트리뷰에 표시될 내용은, 그거 하나면 충분합니다.
하나 눈여겨보실 점은 set이 private로 되어있다는 것인데, ViewModel 안에서만 Set을 해줄 수 있게 하려는 의도이지요.
그리고, 지금은 그냥 연습하는 차원이라 생성자에 GetHelpTopics()를 호출해서, 그냥 데이터를 시작과 동시에 가져왔습니다.
현업에서는, WebClient를 이용해서 다른 방법을 사용해야겠지요.
이 ViewModel은 INotifyPropertyChanged 인터페이스를 구현합니다. 그래서 이 프로퍼티가 바뀌었을 때(set) OnProperty
Changed 함수를 호출해주게끔 하고, 그 해당 함수에서 Invoke 등으로 이벤트를 강제발생 시키는것이죠.
앞에서 ObservableCollection도 그런 역할을 한다고 했지만, 그건 단일 Class 차원에서의 이벤트 리스너이고,
ViewModel에서 할때는.. 직접 이렇게, 구현을 해주어야하죠.
그럼 이제 View 부분을 꾸며보도록 하겠습니다. 다음과 같이 합니다.
더보기
(역자 주 : 지금 TreeView와 HierarchicalDataTemplate이 같은 네임스페이스인데, 최근의 툴킷은 둘의 네임스페이스가
다르다더군요. 여기를 참고하셔서 네임스페이스를 다르게 해주세요.. ^^)
데이타 바인딩에 대한 자료는 많이 나왔었습니다. 그리드 자체의 DataContext를 PageViewModel로 박아버리고,
TreeView는 HelpTopics로, 그 안의 HierarchicalDataTemplate는 SubTopics, 그 안의 TextBlock에는 Name을 각각
바인딩 시키면 됩니다.
이렇게 나오셨다면, 첫번째 포스팅을 종료하신 것입니다. 아직 수정이나 제거 동작은 시작하지 않았습니다.
코딩을 좀 무식하게 한것 말고는 없지만, MVVM 패턴이 무언지를 간단하게 알아보았습니다.
(저번에 우리 카페 스터디때 데이타바인딩 하면서 INotifyPropertyChanged를 사용했던 스터디 일차가 있었는데,
결국 그게 MVVM 패턴의 초석이었던 것이지요..)
제가 생각하기에는 그렇습니다. MVC 패턴이랑 다른게 무엇인가.
MVC의 C. 즉 Control은, 데이터 자체를 제어하는 그 부분에서 끝나지만, MVVM의 VM. ViewModel은 이렇게, 일정한 역할을
하고 나서, 나중에 XAML, 즉 View에서 {Binding} 등으로 자동으로 가져갈 수가 있고, 자료에 해당하는 Model 부분을 끌어와서 쓰고 있기 때문에, 데이터 제공 역할과 데이터 제어, 가공 측면이 동시에 이루어지고 있기 때문이라는 생각이 들어요.
굳이 Silverlight나 WPF만 있는 개념은 아니거든요..
다음 포스팅에서는 Command를 이용하여 수정하는 방법을 따라해보도록 하겠습니다.
감사합니다.
설정
트랙백
댓글
글
앞장에서 기본 CRUD를 구성할 때는, 기본적으로 'enable Editing'이라는 스위치를 사용하면 그냥 다 알아서 코딩이
되는 방법을 알아보았습니다.
그런데, 이 기본 CRUD는 그것을 사용하면 되지만, 뭔가 좀 디테일하게 데이타와 연관된 다른 작업을 할 수는 없을까요?
이번 시간에는 그것을 가능하게 해주는 두가지 기술에 대해 알아보려 합니다.
말 그대로 유저에게 필요한 로직을 생성해서, 사용하는 방법을 이번 시간에 알아볼텐데요. 일단 세팅부터 하죠.. ^^
1) 실버라이트 어플리케이션을 만듭니다. (이름은 UpdateSample로 해볼까요.. 아 전 포스트에도 이 이름이었나요?)
2) AdventureWorks DB 를 AppData 폴더에 카피합니다. (mdf 파일)
(그냥 테스트용도라면 꼭 여기에 넣을 필요는 없습니다. 바로 참조해도 로컬에선 관계없으니까요)
3) 웹 프로젝트에 Linq to SQL Classes 아이템을 추가합니다. 그리고, 서버 익스플로러에서 Product 테이블을 추가
합니다.
4) 일단 빌드합니다.
5) 이제 '웹 프로젝트'에 DomainService Class 를 추가합니다 (Add New Items). 이 클래스는, data Model. 즉 아까 mdf
파일을 연결해두었던 것과 연관지은 'Business Object'를 만들려 합니다. 다음 순서를 따라주세요.
1. "Catalog"고 이름을 짓습니다. (DomainService Class 추가할 때)
2. Product 엔트리를 체크하시고, (하나밖에 없죠)"Enable Editing"이 체크가 되어있는지를 확인합니다.
3. "Generate associated classes for metadata" 체크
6) 또 빌드합니다.
7) 그리고 XAML을 다음과 같이 꾸며줍니다.
더보기
8) XAML.CS 부분을 다음과 같이 꾸밉니다.
using System.Windows.Ria.Data;
using CustomSample.Web;
namespace CustomSample
{
public partial class MainPage : UserControl
{
Catalog catalog = new Catalog();
public MainPage()
{
InitializeComponent();
productsGrid.ItemsSource = catalog.Products;
catalog.LoadProducts(Catalog.ProductsQuery.Where(p => p.ListPrice > 3000), null);
}
}
}
대충 보면, ListPrice가 3000보다 큰 것을 출력하게끔 하는. 별거 아닌 구조입니다.
그럼 여기에 두 함수를 더 만들어보겠습니다.
하나는, 지금 접속한 유저의 IP주소와, 선택한 물품에 대해 할인금액을 적용하는 것을 해볼 것이고.
또 하나는, 별도의 함수를 만들어서 (물론 Domain Service Class에..) 'log.txt'라는 파일로 저장을 하고 싶다라고
해보겠습니다.
[ 커스텀 메서드 ]
이 커스텀 메서드는 별도로 필요한 엔티티 관련 함수가 있을때 굉장히 유용하겠죠.
뭐, 로그텍스트를 쓴다던지, 데이타에 대한 레포팅을 한다던지 할때 유용할 수 있겠습니다.
이 커스텀 메서드는 기본 CRUD 처럼 변경 트래킹 작용을 하며, 실질적인 데이타 반영을 잠시 지연시킬 수 있습니다.
CRUD가 SubmitChanges를 호출하기 전에는 실제 데이타에 업데이트가 안되는 것처럼요.
변경 트래킹은 앞포스트에서 봤었지만, 변경사항을 잠시 DomainContext에 저장하는 것을 말합니다.
앞 포스트에선 이것을 이용해서 Submit도 시키고, Reject도 시키고 그랬었죠?
이 커스텀 메서드를 만들때는 두가지 방법이 있습니다.
1. 함수구성을 리턴형은 void로 하고, 첫번째 파라미터로 엔티티 형이 오고, Create / Update / Delete 로직이 없게 하는 방법
2. 함수에 [Custom]이라는 속성을 주는 방법
두가지를 전부 해보겠습니다. 일단 그 웹 프로젝트에 생성해둔 Domain Service Class 의 Catalog 클래스 안에 다음과 같이
코드를 삽입합니다.
[Custom]
public void DiscountProduct(Product product, int percentage)
{
this.Context.Products.Attach(product);
product.ListPrice = product.ListPrice * (1 - percentage / 100m);
string logMessage = String.Format("{0} has been marked down {1}% on {2}",
product.Name, percentage, DateTime.Now);
string logFilePath = HttpContext.Current.Server.MapPath("log.txt");
using (StreamWriter streamWriter = new StreamWriter(logFilePath, true))
{
streamWriter.WriteLine(logMessage);
streamWriter.Flush();
streamWriter.Close();
}
}
일반 ASP.NET에서 파일을 쓰는 그 구조입니다. 대충 product의 Name, ListPrice 등을 사용하는 듯 보이네요.
이렇게 하고 빌드를 하면, 실버라이트의 Generated Code에 이 함수가 생깁니다. 그럼 이제 이 함수를 mainpage.xaml.cs에서 사용해보도록 하겠습니다.
public MainPage()
{
//Some Initializaiton logic...
discountButton.Click += new RoutedEventHandler(discountButton_Click);
}
void discountButton_Click(object sender, RoutedEventArgs e)
{
Product selectedProduct = (Product)productsGrid.SelectedItem;
int percentage = int.Parse(discountPercent.Text);
catalog.DiscountProduct(selectedProduct, percentage);
catalog.SubmitChanges();
}
실행해보세요. 그러면, 이제 원하는 상품을 선택하고 Discount 버튼을 누르게 되면 Price 컬럼의 가격이 낮춰지면서,
ClientBin 디렉토리 안의 log.txt에 그... 할인 내역이 기록이 될겁니다.. ^^
[ 서비스 오퍼레이션 ]
그 다음은 IP를 받는 작업을 해야죠. IP 주소를 받을때도 함수를 하나 지정한다고 가정을 해보죠.
데이타 제어가 필요한가요? 변경점 트래킹이 필요한가요? 그냥 클라이언트 IP만 가져오는 것이죠.
즉, 서비스 오퍼레이션은 그냥 간단하게, 데이타 로직과는 별개로 다른것들을 간단하게 호출하고 싶을 때 사용합니다.
[ServiceOperation]이라는 속성을 붙여주면 되고, 뭐 리턴형이나 인자값 등의 제약은 없습니다.
DomainServiceClass에 다음을 추가해주고,
[ServiceOperation]
public string GetIP()
{
return HttpContext.Current.Request.UserHostAddress;
}
그리고 다음과 같이 mainpage.xaml.cs에 코딩해줍니다.
public MainPage()
{
//Some initialization logic...
catalog.GetIPCompleted += new EventHandler<InvokeEventArgs>(catalog_GetIPCompleted);
catalog.GetIP();
}
void catalog_GetIPCompleted(object sender, InvokeEventArgs e)
{
userIP.Text = (string)e.ReturnValue;
}
특이점이 있네요. '비동기' 호출이라는 것입니다. (Completed 콜백함수가 있지요)
이벤트 핸들러를 추가해주어야하고, 이 콜백 함수를 구현해주어야 합니다.
커스텀 메서드는 일단 엔티티가 인자로 들어가야하죠. 그에 반해, 그냥 간단히 IP를 가져온다던지, 다른 처리를 한다던지.
할때 사용할 수 있는 것이 시스템 오퍼레이션입니다. ^^
이제 실행시켜보시면, 원하는 것들이 모두 구현되어있는 것을 볼 수 있습니다.
----------------------------------------------------------------------------------------------------------------
기본 CRUD 외에, 개발자 자신이 고객의 요구조건에 맞게 함수를 설정하고 사용할 수 있다는 점에서,
이 커스텀 메서드와 시스템 오퍼레이션은 꽤나 유용하게 사용할 수 있을 듯 합니다.
다음은, 전반적인 구성요소에 대한 브리핑에 대한 내용입니다. 다시 '그냥 그렇다더라' 하는 포스트가 될듯 싶습니다. ^^
감사합니다. 다음 포스트에서 뵈요~
설정
트랙백
댓글
글
오랫만이예요~
이번에는, 예제를 하나 작성해두고 이론을 이해해보죠. 기본 CRUD를 이해하기 위한 예제를 작성해보려 합니다.
다음 순서를 따라주세요. (이름은 자유로 하셔도 되요)
1) 새 실버라이트 어플리케이션을 만드시고, 이름은 "UpdateSample"으로 하시고, Link to ASP.NET Project 체크되셨는지
확인하고.. 생성합니다.
2) 실버라이트 프로젝트에 'System.Windows.Controls.Data' 레퍼런스가 참조되어있는지 확인해주세요.
3) App_Data 디렉토리 안에 기존의 AdventureWorks DB를 다운받아, 안에 넣어놓습니다.
4) '웹 프로젝트'에서 Linq to SQL Classes를 추가합니다 (Add New Items). 그리고 이 Data Connection은 3번 과정에서
놓아둔 AdventureWorks의 mdf 파일에서. Product 테이블만 추가해보도록 하겠습니다.
5) 일단 솔루션을 빌드합니다.
6) 이제 '웹 프로젝트'에 DomainService Class 를 추가합니다 (Add New Items). 이 클래스는, data Model. 즉 아까 mdf
파일을 연결해두었던 것과 연관지은 'Business Object'를 만들려 합니다. 다음 순서를 따라주세요.
1. "Catalog"고 이름을 짓습니다. (DomainService Class 추가할 때)
2. Product 엔트리를 체크하시고, (하나밖에 없죠)"Enable Editing"이 체크가 되어있는지를 확인합니다.
3. "Generate associated classes for metadata" 체크
Enable Editing은 조금 있다 보시겠지만, 기본 CRUD 작업을 하는 함수들이 전부 생성을 하게 되며,
Generate associated classes for metadata는, 앞 포스트에서 보았던 metadata를 미리 생성해주는 역할을 합니다.
7) 빌드해서 이상이 없는지 확인합니다.
8) 그럼 이제, 실버라이트 UI를 만들어보겠습니다. MainPage.xaml에 다음과 같이 만들어줍니다.
|
[MainPage.XAML] xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" Width="Auto" Height="Auto"> <Grid x:Name="LayoutRoot" Background="White"> <data:DataGrid Height="Auto" Width="Auto" x:Name="productsGrid"></data:DataGrid> </Grid> </UserControl> |
[MainPage.XAML.CS]
using UpdateSample.Web;
using System.Windows.Ria.Data;
namespace UpdateSample {
public partial class MainPage : UserControl {
Catalog _catalog = new Catalog();
public MainPage() {
InitializeComponent();
productsGrid.ItemsSource = _catalog.Products;
var query = from p in _catalog.GetProductsQuery()
where p.ProductSubcategoryID == 1
select p;
_catalog.Load(query);
}
}
}
수정 (2009.8.18 : LoadProduct -> Load로 함수변경. 쿼리사용하는 것으로 변경)
아주 기본적인, 쿼리가 DataGrid에 나올 것입니다. 이 DataGrid에 Product의 엔티티가 바인딩되는 순간부터는, 이 엔티티가
비동기적인 쿼리에 의해 변경되는 그 족족, 자동으로 업데이트가 되게 됩니다.
그럼, 새로운 컨셉은 무엇이 있는지를 살펴보죠.
[ DomainService Update Methods ]
말씀드렸지만, DomainService Class는 Business적인 코드들이 자동 생성되는 클래스입니다. 이 클래스를 보시면, 종전과는
달리 살짝 메서드가 '늘었다'는게 느껴지실꺼예요.
[EnableClientAccess()]
public class Catalog : LinqToSqlDomainService<AdventureWorksDataContext> {
public IQueryable<Product> GetProducts() {
return this.Context.Products;
}
public void InsertProduct(Product product) {
this.Context.Products.InsertOnSubmit(product);
}
public void UpdateProduct(Product currentProduct, Product originalProduct) {
this.Context.Products.Attach(currentProduct, originalProduct);
}
public void DeleteProduct(Product product) {
this.Context.Products.Attach(product, product);
this.Context.Products.DeleteOnSubmit(product);
}
}
이 메서드들은 그 이름이 아주 설명적이죠.. 아주 그냥.. 이해하기도 쉽게 만들어두었습니다.
이 문서에서는 네이밍을 굉장히 강조하더군요. 보통 다음과 같은 규칙을 사용합니다.
Insert 동작 - "Insert", "Add", "Create" 등의 접두어를 사용합니다.
Update동작 - "Update", "Change", "Modify" 등의 접두어를 사용합니다.
Delete 동작 - "Delete", "Remove" 등의 접두어를 사용합니다.
Query 동작 - "Get", "Fetch", "Find", "Query", "Retrieve", "Select" 등의 접두어를 사용합니다.
Resolve동작 - "Resolve" 의 접두어를 사용합니다.
쭉 나열은 해두었는데, 해석이 어렵진 않으실꺼예요.
당연한 이야기겠지만 이런 DomainService Class에 대한 내용들이, 무엇을 클라이언트코드에 Generated 시킬 것이
냐를 결정하게 되는 것이죠.
[ 엔티티 컨테이너, 변경점 감지 ]
이 자료들, 즉 엔티티들은 Domain Context라는 곳에 저장이 되고, 이와 연관된 Entity Container에 의해 변경점이
기록되게 됩니다. 그래서 Entity Container는 변경점을 감지하고, 이를 알려주는 역할을 하지요. Domain Context는
항상 하나의 Entity Container를 가지고 있고, 도움이 되는 API들을 몇개 가지고 있습니다. 굉장히 유용하지요.
예제를 만들어보겠습니다.
위의 소스를 사용하셔서, MainPage.xaml을 다음과 같이 고칩니다.
|
<UserControl x:Class="UpdateSample.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" Width="Auto" Height="Auto"> <Grid x:Name="LayoutRoot" Background="White"> <StackPanel Orientation="Vertical" Height="Auto" Grid.Column="0"> <StackPanel Orientation="Horizontal"> <Button x:Name="saveButton" Width="75" Height="30" Content="Save" Margin="5" Click="saveButton_Click"/> <Button x:Name="rejectButton" Width="75" Height="30" Content="Reject" Margin="5" Click="rejectButton_Click"/> <TextBlock x:Name="changeText" VerticalAlignment="Center" Width="Auto"/> </StackPanel> <data:DataGrid Height="Auto" Width="Auto" x:Name="productsGrid"></data:DataGrid> </StackPanel> </Grid> </UserControl> |
그냥.. 스택패널에 버튼 두개, 텍스트블럭을 올려놓은 것이 전부이구요.
다음은 Mainpage.xaml.cs입니다.
public MainPage() {
InitializeComponent();
UpdateHasChanges();
productsGrid.RowEditEnded +=
new EventHandler<DataGridRowEditEndedEventArgs>(productsGrid_RowEditEnded);
catalog.Submitted += new EventHandler<SubmittedChangesEventArgs>(catalog_Submitted);
productsGrid.ItemsSource = catalog.Products;
catalog.LoadProducts(
Catalog.ProductsQuery.Where(p => p.ProductSubcategoryID == 1), null);
}
void catalog_Submitted(object sender, SubmittedChangesEventArgs e) {
UpdateHasChanges();
}
void productsGrid_RowEditEnded(object sender, DataGridRowEditEndedEventArgs e) {
// every time a row is updated, update the change state
UpdateHasChanges();
}
private void UpdateHasChanges() {
// get the current changeset from the EntityContainer
EntityChangeSet changeSet = catalog.Entities.GetChanges();
changeText.Text = changeSet.ToString();
// update the button states
bool hasChanges = catalog.HasChanges;
saveButton.IsEnabled = hasChanges;
rejectButton.IsEnabled = hasChanges;
}
private void rejectButton_Click(object sender, RoutedEventArgs e) {
catalog.RejectChanges();
UpdateHasChanges();
}
private void saveButton_Click(object sender, RoutedEventArgs e) {
catalog.SubmitChanges();
UpdateHasChanges();
}
빌드하시고, 실행을 하셔보면 한번에 알아차리실 수 있을거예요. 예를 들어서 1행의 어떤 칼럼을 변경했다 라고 하면
자동적으로 modified = 1로 찍히는걸 보실 수 있고, 이는 EntityChangeSet의 GetChanges() 함수로 그냥 감지가
되어버리죠. 이것을 SubmitChanges() 함수로 보내버리면, 따로 뭘 작성할 필요 없이. 바로 업데이트가 이루어지는
과정이지요. 이 EntityChangeSet은 안에 파라미터를 사용하여, 위의 예와 같이 텍스를를 사용하는 않고, 바로 원하는 어떤
동작을 하게끔 코딩할 수도 있습니다.
즉.
EntityChangeSet changeSet = catalog.Entities.GetChanges();
이 줄 하나로, 이 엔티티의 Insert랄지, Update랄지, Delete.. 이런것들이 GetChanges() 함수 하나로 감지가 끝난다는 것이죠.
아. 실수 했다. 잘못 고쳤다.. 아 어뜩해어뜩해 큰일났네.. 그러면,
private void rejectButton_Click(object sender, RoutedEventArgs e) {
catalog.RejectChanges();
UpdateHasChanges();
}
다시 돌려버리면 되는것이지요.
RejectChanges()를 하면, 그동안 고쳤던것들이 전부 취소되고 원래의 자료들이 뜨게 됩니다.
똑같이, 저 자료에서 Save 버튼을 누르면, Reject가 아닌 SubmitChanges()가 실행되어, 변경사항이 DB 등에 반영이 되겠죠.
Update나 Delete도 비슷합니다. EntityList.Add(catalog.Products.Add) 를 호출하셔서 버튼이나 그런 곳에 넣어주시면
되겠습니다.
이 ChangeSet이 서버로 보내지면, Domain Service에서는
Submit, AuthorizeChangeSet, ValidateChangeSet, ExecuteChangeSet, PersistChangeSet, ResolveChangeSet 등의
함수를 이용하여 연산을 수행하게 되지요. 여기는 그냥 자동적으로 해주는 것이기때문에 크게 신경쓰지 않으셔도
되겠습니다. Virtual 로 잡혀있고, 오버라이딩 가능합니다.
[ 에러 처리 ]
에러 처리를 해야할 필요성이 있습니다. SubmitChanges()로 DB에 넣었는데. 잘 되었나 안되었나는 실제로 표시가
안될 거예요.. 그래서, 이 SubmittedChangesEventArgs의 인자로 넘어온 것을 체크하여 어떤, 처리를 할 필요가 있습니다.
예문에서, catalog_Submitted 핸들러 함수를 다음과 같이 고쳐보죠.
void catalog_Submitted(object sender, SubmittedChangesEventArgs e) {
if (e.Error != null) {
string message = string.Empty;
if (e.EntitiesInError != null) {
Entity entityInError = e.EntitiesInError.First();
if (entityInError.Conflict != null) {
EntityConflict conflict = entityInError.Conflict;
foreach (EntityConflictMember cm in conflict.MemberConflicts) {
message += string.Format(
"Member '{0}' in conflict: Current: {1}, Original: {2}, Store: {3}",
cm.PropertyName, cm.CurrentValue, cm.OriginalValue, cm.StoreValue);
}
} else if (entityInError.ValidationErrors.Count > 0) {
message += "\r\n" + entityInError.ValidationErrors.First().Message;
}
}
MessageBox.Show(message, "Submit Failed", MessageBoxButton.OK);
}
else {
UpdateHasChanges();
}
}
에러가 있으면, 그 메세지를 생성하는 작업을 하고, 에러가 없으면, 그냥 기존대로 UpdateHasChanges()를 실행합니다.
.... 음.. 에러가 없겠군요. 테스트를 해보기 위해 에러를 억지로 만들어보겠습니다.
서버단의 DomainServiceClass 안의 UpdateProduct를 다음과 같이 해볼께요.
빌드를 하시면 자동적으로 실버라이트 측, 즉 클라이언트 코드의 Generated_Code 가 리로드가 될것입니다.
(이 현상은 앞에서 매우 많이 설명드렸습니다. 자동 리뉴얼..)
특별한건 없고.. ListPrice가 0으로 들어오면 "0보다는 커야한다." 라는 에러를 던지는 것으로 해보겠습니다.
public void UpdateProduct(Product currentProduct, Product originalProduct) {
if (currentProduct.ListPrice == 0) {
throw new ValidationException("List price must be greater than zero!");
}
this.Context.Products.Attach(currentProduct, originalProduct);
}
실행을 해보고, ListPrice에 0을 넣어보면 에러가 발생이 되겠죠.
[ 병행 충돌 ]
위의 에러처리 코드를 보면 conflict 를 체크하는 부분이 있었죠. 병행 충돌에 대한 처리인데요. 실제로 이런 것들을 만들면
굉장히 빈번하게 부딪치는 문제입니다. 예를 들어서, 한 쪽에서 어떤 컬럼에 대한 값을 수정했는데 다른 쪽에서 수정되기 전의
데이터를 가지고 다른 컬럼을 수정하려고 했을때, 변경점을 제대로 감지할 수 있을까요? 원본과 변화된 것들을 비교한다고 봤을 때. 분명히 하나만 변했는데, 가보니까 2개가 바뀌어있습니다. 어? 이게 뭐야??... 그래서 conflict로 걸러내는거죠.
에러 실험은, App를 두개를 띄워서 하나는 ListPrice를 변경해보고 (Save 버튼까지 누르시구요), 남은 하나는 기존의 것이 띄워져있는 상태에서 Color를 변경한다고 해보면 conflict 에러가 뜰 것입니다.
어떻게 해야될까요. 풀면 되지요.
Domain Service Class 안의 Catalog 클래스에, 다음의 함수를 추가합니다.
public bool ResolveProduct(Product currentProduct, Product originalProduct, Product storeProduct, bool deleteOperation)
{
return this.Resolve(currentProduct, originalProduct, storeProduct, ResolveOption.KeepChanges);
}
내부적으로 LTS(Linq to SQL) Resolve Helper 메서드를 호출하여 충돌을 풀어줍니다. 브렉포인트를 저쪽에 걸어보시면,
충돌이 걸렸을 때 이쪽으로 이동하는 것을 볼 수 있습니다. 자동으로, 기존에 다른 쪽에서 바꾼 것까지 한꺼번에 리프레시를 해주는 것을 볼 수 있습니다.
[ 트랜잭션 ]
트랜잭션입니다. 기본적으로 FrameWork 내에서는 아무 트랜잭션에 대한 정보도 제공이 안되지만, Update 프로세싱 중에
어떤 처리를 하게 할 수는 있습니다.
이것을 구현하려면, Domain Service Class에 Submit을 오버라이딩해서, 호출을 구현하면 됩니다.
public override void Submit(ChangeSet changeSet) {
using (var tx = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted })
) {
// call to base to process the changeset within
// our transaction scope
base.Submit(changeSet);
// complete the transaction
tx.Complete();
}
}
이렇게 되면 모든 changeSet의 일련의 작업들이 트랜잭션에 관리되게 됩니다.
[ ID 관리]
EntityContainer는 클라이언트에 Entity를 로드시킬 때마다, ID를 체크 및 기록(Tracking) 해야합니다. 보통 GetIdentity()
메서드를 이용하여 인스턴스의 ID를 도출하는데요.
public override object GetIdentity()
{
return this._productID;
}
이 함수가 바로 그 일을 하는 함수 되겠습니다.
기본적으로는, 그 인스턴스가 로컬에서 생기게 되면, 인스턴스 하나로 처리가 되는 싱글 인스턴스가 됩니다. 동시의 두가지
가 원칙적으로는 안되지요. 그런데 이것도 조절이 가능합니다.
MergeOption.KeepChanges는 기존 데이터베이스와 변경점을 합쳐서 로드해줍니다.
변경점을 무시한 채로 로드를 하려면 MergeOption.OverwriteCurrentValues를 쓰시면 됩니다.
기본값은 MergeOption.KeepCurrentValues입니다.
[ IEditableObject / IChangeTracking / IEditableCollection ]
마지막으로 Entity 제어에 핵심이 되는 클래스들이 상속받는 인터페이스들을 알아보죠.
1) IEditableObject
namespace System.ComponentModel {
public interface IEditableObject {
void BeginEdit();
void CancelEdit();
void EndEdit();
}
Entity가 기본적으로 상속받는 인터페이스입니다.
BeginEdit()가 불리면, Entity는 그 시점에서 "SnapShot(현재상태 저장)" 이 이루어지고, 이어서 CancelEdit()가 불리면, 모든
동작을 중단하고 다시 대기합니다. EndEdit()는 변경이 완료되었을 때 불립니다.
DataForm같은 Data Control은 Entity와 함께 이 인터페이스에게 영향이 미쳐지게 되죠.
내부적으로 불려지는 것이기때문에, 굳이 신경쓸 필요는 없어보여요.
2) IChangeTracking / IRevertableChangeTracking
Entity, EntityList, EntityContainer가 상속받는 인터페이스들입니다.
namespace System.ComponentModel {
public interface IChangeTracking {
bool IsChanged { get; }
void AcceptChanges();
}
public interface IRevertibleChangeTracking : IChangeTracking {
void RejectChanges();
}
}
이 인터페이스들은 클라이언트가 changeSet에 대한 처리를 진행할 때 사용됩니다. 간단하죠.
DataForm이 IChangeTracking.IsChanged를 사용하여, 변경이 됐는지 안됐는지를 구합니다.
Entity나 EntityList, EntityContainer 메서드를 보면 HasChanges 라는 멤버가 있는데, 이 인터페이스의 영향입니다.
3) IEditableCollection / IEditableCollection<T>
EntityList가 상속받는 인터페이스입니다. DomainDataSource같은 컨트롤이나 DataForm등은 EntityList와 함께 이 인터페이스
의 영향을 받게 됩니다.
namespace System.ComponentModel {
public interface IEditableCollection {
bool CanAdd { get; }
bool CanEdit { get; }
bool CanRemove { get; }
void Add(object item);
void BeginEdit(object item);
void CancelEdit(object item);
object CreateNew();
void EndEdit(object item);
void Remove(object item);
}
}
그러니까, edit 처럼 어떤 함수들 (DomainService Class에서 정의되어있는, Edit나 Update나.. 그런것들) 을 제공할 것인지
결정하는 인터페이스 되겠습니다.
----------------------------------------------------------------------------------------------------------------
이번 장은 좀 길었습니다.
글쎄요. 개인적으로 트랜잭션을 중요하게 생각하는데, 트랜잭션을 이용하여 롤백을 하는 과정에 대해서는
나와있지 않네요. 혹시나 자료 검색하다가 나오게 되면 업데이트 하겠습니다.
여기서는, CRUD를 사용하는 법, 그리고 변경점을 감지하여 자동으로 DB에 싣는 법, 그리고 에러 처리하는 부분만 아시면
실제 사용하시는데 큰 무리는 없다고 생각이 됩니다.
트랜잭션이 좀 많이 아쉽긴 하네요.. 읽어주셔서 감사합니다.
다음에는 커스텀메서드와 서비스 오퍼레이션을 사용하는 방법에 대해서 알아보겠습니다.
설정
트랙백
댓글
글
이번에는 쿼리가 어떤 식으로 정의가 되는지를 보겠습니다. 역시.. 그냥 사용하는 방법을 알려 하시는 분들은.
건너 뛰어도 되는 포스트가 될지도 모르겠네요.. ^^
어떻게 쿼리가 정의가 되고, 사용되며, 어떻게 데이터가 걸러지는지.. 머 그런것들을 볼겁니다.
쿼리 메서드는 보통 다음과 같은 특징이 있습니다.
1) Entity 관련 T의 IEnumerable<T> 아니면 IQueryable<T> 를 리턴형으로 합니다.
2) 파라미터가 없거나, 몇개를 받을 수 있습니다 (... 당연한거 아닌가요.. 해석을 잘못한건가..;;)
3) 보통 함수 이름 접두어로 Get, Fetch, Find, Query, Retrieve, Select 등의 단어가 오는 경우가 많습니다. 보통 쿼리 성격에
따라서 구분되어 사용되겠지요..
예를 들어 이 경우는 Employees 테이블의 AdventureWorks의 Domain Service에 대한 쿼리가 되겠죠.
프록시 클래스는 아니고, 우리 앞에서 Domain Service Class (서버 단의 코드) 에 대한 내용입니다.
만약에 필터링을 해야한다, 무슨 조건을 주고, 그에 맞는 테이블을 가져와야 한다. 라고 하면 파라미터를 받아야겠지요.
근데 주의할 것은, 이 도메인 오퍼레이션은 오버로딩이 안된다는 점입니다. 즉 GetEmployees라는 조건의 함수로 파라미터 0개, 파라미터 2개.. 이렇게 멀티플로 이름을 줄 수는 없다는 것이지요. 이름을 각각 다르게 주어야합니다.
(프록시 클래스에서는 자동으로 오버로딩을 시킵니다)
프록시 클래스에서는 이 함수들이 '비동기식'으로 만들어집니다. 즉, 어플리케이션이 중간에 블록되지 않고, 받는 중에도 다른
작업들이 계속 돌고 있다는 것이지요.
아무튼 빌드를 하게 되면.
오버로드로 세 종류의 유형으로 만들어지는데요.
1) 도메인 서비스의 쿼리와 동일한 파라미터의 함수
2) 추가적인 쿼리와 UserState 파라미터를 사용하는 함수
3) 추가적인 쿼리와 UserState 파라미터, mergeOption을 사용하는 함수
밑의 소스를 보세요. 이 세가지 유형으로 보통 만들어집니다. 그냥 도메인 서비스 아이템 끌어다놓고 빌드했다면, 추가적인
쿼리가 없기 때문에 LoadProduct() 한개만 만들어지겠지만요. Domain Service Class의 유형에 따라 이런 식으로 구성이
보통 됩니다.
userState 파라미터는 어떤 객체가 비동기 쿼리 메서드를 통해 전달되도록 하는 인자이고, mergeOption은 데이터를 어떻게
가져올 것인지를 제어하는 옵션입니다. (...이게 무슨소리일까요..)
그리고, 보통은 이렇게 앞에서 말씀드린 접두어로 함수 이름이 지정이 된다 했지요. 근데 이게 싫다, 그냥 원래 함수명을
보존하고 싶다. 라고 하시면 PreserveName = "true"로 속성을 지정해주시면 이 이름이 바뀌지 않습니다.
여기서 쿼리는 보통 다음과 같은 쿼리가 지원이 됩니다.
1. Where
2. OrderBy / ThenBy
3. Skip / Take
그리고 자료형으로는.. 일반형 중의 원시형으로는
Boolean, Byte, Sbyte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, Single
일반형 중의 열거형으로는
String, Decimal, DateTime, TimeSpan, Guid, Uri, XElement
등이 있습니다. 그리고 복합형으로는
Byte[]
System.Data.Linq.Binary
위에 있는 일반형의 배열형태
IEnumerable<T>. 이때 T는 위의 일반형(원시형, 열거형) 중의 하나
Nullable<T>. 이때 T는 위의 일반형(원시형, 열거형) 중의 하나
IList 가 구현된 형태 전체
등을 지원합니다.
이번 포스트는 살짝 짧은 느낌이 있네요.. 내용도 별로 없고.. -_-;
그냥 그렇다. 라고 계속 하는 것이고.. 실전에서는 그냥 이론 정도로 알아두시면 될 듯 보입니다.
다음장에서는 실습을 통해서 엔티티를 수정하는 방법을 보도록 하겠습니다.
감사합니다.
RECENT COMMENT