Pull to refresh

WCF RIA Services. Начало. Часть 1

Reading time10 min
Views40K
Original author: Brian Noyes
WCF RIA Services. Начало. Часть 1
WCF RIA Services. Получение данных. Часть 2
WCF RIA Services. Обновление данных. Часть 3
WCF RIA Services. Внедряем паттерн Model-View-ViewModel (MVVM). Часть 4

От переводчика


В данном цикле переводов присутствует хорошая доля, добавленная лично мной, так как в оригинале используется устаревшая VS, а так же пропущены некоторые, на мой взгляд важные моменты, без которых усвоение материала сильно усложняется. Поехали.

Осторожно. Много картинок!

Вступление


Представьте, что Вам необходимо создать серьезное бизнес приложение, клиент которого оперирует множеством данных, распределенных в различных местах, и которые должны каким-то образом собираться. Для реализации такой задачи Вам будет необходимо изучить ряд новых технологий и подходов, написать кучу кода, отладка. А что в итоге? Если грубо — то просто пересылка данных с серверной части приложения в клиентскую и обратно. Ну и работа с БД. А на что бы Вы хотели сфокусироваться в первую очередь? На реализации пересылки данных? Или на логике обработки, манипулирования, представления этих данных? Думаю, Вы выберете второе.

WCF RIA Services предлагает автоматизацию пересылки данных и создания многоуровневых приложения на основе Silverlight, фокусируя внимание разработчика именно на логике работы. Вы пишите только один раз экземпляр серверного кода, а он автоматически переносится и становится доступным на клиентской стороне, без необходимости ручного дублирования или применения других ухищрений со стороны разработчика. Так же на клиенте остаются доступны все серверные возможности, такие как валидация, права доступа и многие др.

В этом цикле статей Вы ознакомитесь с основными возможностями WCF RIA Services, увидите на практике какие преимущества предоставляет использование данного подхода. На диаграме ниже показано, за что именно отвечает WCF RIA Services:
image

сегодня воспользуемся стандартным, самым простым шаблоном Silverlight. В последующих частях будем наращивать наши знания путем доработки приложения и использования новых методик, тактик и технологий, одними из которых будет паттерн Model-View-ViewModel (MVVM) и модульное тестирование (unit testing).

Задачи, решаемые создаваемым приложением


Просто электронный блокнот, в который можно заносить событие, давать ему название, исполнителя, проект, дату начала и окончания. В общем ничего сложного или необычного, однако хорошее понимание того, ЧТО нужно сделать только облегчит понимание того КАК это сделать в рамках изучаемой технологии.

Изучаемые аспекты WCF RIA Services:


  • Project Links (ссылки между проектами) – связь между клиентским Silverlight проектом и серверным вэб приложением или классом библиотеки. Серверный проект определяет или создает ссылки на доменные службы, сущности, метаданные, а так же предоставляет механизм, благодаря которому код, определенный на сервере доступен клиенту. Благодаря ссылке весь необходимый клиенту код генерируется динамически во время компиляции.

  • Domain Services (доменные службы) – ядро WCF RIA Services. Они определяют доступные для выполнения на сервере операции. В основном они сводятся к CRUD (запись, чтение, обновление, удаление), однако могут быть и любыми другими, которые Вы сочтете нужными. Все эти операции доступны автоматически на стороне клиента благодаря WCF, причем клиенту не нужно знать о серверной стороне практически ничего.

  • Entities (сущности) – Вам необходимо определить типы сущностей на серверной стороне, а клиентская часть будет ими оперировать благодаря автоматической кодогенерации. Так же возможно использование валидации и других метаданных. Типы сущностей на стороне сервера и клиента используются для операций сериализации/десериализации, при передаче данных между ними. Сущности могут быть созданы с помощью Entity Framework, LINQ to SQL, или Plain Old CLR Objects (POCO).

  • Domain Context (контекст домена) – клиентский аналог доменных служб. Автогенерирует код, благодаря которому получаете доступ к функционалу серверной части. Содержит внутри себя WCF proxy, который предоставляет возможность вызова сервиса, а так же управляет запросами, которые должны будут выполниться на серверной стороне, отслеживает изменение сущностей и многое другое.

  • DomainDataSource — это объект источника данных, который используется для выполнения запросов и уведомления сервера об внесенных изменениях. Облегчается прямое связывание в XAML и не только.

Шаг 1: Создаем Silverlight приложение и ссылку на серверную часть.


Воспользуемся простой БД, которая будет сохранять задания, а также всю сопутствующую информацию. Для этого необходимо будет запустить скрипт TaskManager.sql, находящийся в коне проекта, создать БД и схему, заполнить БД начальными значениями.

Создайте новый проект с именем «TasksManager». Возможность использовать WCF RIA Services присуща любому типу Silverlight приложений. Однако начнем с самого простого и выберем шаблон «Приложение Silverlight».


После нажатия на кнопку ОК, откроется следующее диалоговое окно, в котором необходимо добавить птичку на «Включить службы WCF RIA».


Благодаря ей создадутся необходимые ссылки на серверный проект, появится кодогенерация и остальные функции, описанные ранее.
Нажимаем ОК и ждем создания проекта.

Шаг 2: Создаем Domain Model Entities


Так как WCF RIA Services в основном предназначен для перемещения данных туда/сюда от клиента к серверу и обратно, Вам понадобятся некоторые модели данных данные. Из коробки поддерживается Entity Framework. Однако существует возможность использовать LINQ to SQL в WCF RIA Services Toolkit. Если создать свой доменный сервис, то можно работать с POCO. Сейчас воспользуемся стандартным Entity Framework.

Оригиал TaskManager.sql
USE [master]
GO

/****** Object:  Database [TaskManager]    Script Date: 06/06/2010 18:01:49 ******/
IF  EXISTS (SELECT name FROM sys.databases WHERE name = N'TaskManager.mdf')
DROP DATABASE [TaskManager.mdf]
GO

CREATE DATABASE [TaskManager.mdf]
GO

USE [TaskManager.mdf]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[User](
	[UserId] [int] IDENTITY(1,1) NOT NULL,
	[Username] [nvarchar](250) NOT NULL,
	[Password] [nvarchar](250) NOT NULL,
 CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED 
(
	[UserId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Customer](
	[CustomerId] [int] IDENTITY(1,1) NOT NULL,
	[CustomerName] [nvarchar](250) NOT NULL,
 CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED 
(
	[CustomerId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Project](
	[ProjectId] [int] IDENTITY(1,1) NOT NULL,
	[ProjectName] [nvarchar](250) NOT NULL,
	[Description] [nvarchar](max) NULL,
	[CustomerId] [int] NULL,
 CONSTRAINT [PK_Project] PRIMARY KEY CLUSTERED 
(
	[ProjectId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Task](
	[TaskId] [int] IDENTITY(1,1) NOT NULL,
	[TaskName] [nvarchar](250) NOT NULL,
	[Description] [nvarchar](max) NULL,
	[StartDate] [datetime] NULL,
	[EndDate] [datetime] NULL,
	[CustomerId] [int] NULL,
	[ProjectId] [int] NULL,
	[UserId] [int] NULL,
 CONSTRAINT [PK_Task] PRIMARY KEY CLUSTERED 
(
	[TaskId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[UsersTasks](
	[TaskId] [int] NOT NULL,
	[UserId] [int] NOT NULL,
 CONSTRAINT [PK_UsersTasks] PRIMARY KEY CLUSTERED 
(
	[TaskId] ASC,
	[UserId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[TimeEntry](
	[TimeEntryId] [int] IDENTITY(1,1) NOT NULL,
	[StartTime] [datetime] NOT NULL,
	[EndTime] [datetime] NOT NULL,
	[TaskId] [int] NOT NULL,
	[Description] [nvarchar](max) NOT NULL,
 CONSTRAINT [PK_TimeEntry] PRIMARY KEY CLUSTERED 
(
	[TimeEntryId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Project]  WITH CHECK ADD  CONSTRAINT [FK_Project_Customer] FOREIGN KEY([CustomerId])
REFERENCES [dbo].[Customer] ([CustomerId])
GO
ALTER TABLE [dbo].[Project] CHECK CONSTRAINT [FK_Project_Customer]
GO
ALTER TABLE [dbo].[Task]  WITH CHECK ADD  CONSTRAINT [FK_Task_Customer] FOREIGN KEY([CustomerId])
REFERENCES [dbo].[Customer] ([CustomerId])
GO
ALTER TABLE [dbo].[Task] CHECK CONSTRAINT [FK_Task_Customer]
GO
ALTER TABLE [dbo].[Task]  WITH CHECK ADD  CONSTRAINT [FK_Task_Project] FOREIGN KEY([ProjectId])
REFERENCES [dbo].[Project] ([ProjectId])
GO
ALTER TABLE [dbo].[Task] CHECK CONSTRAINT [FK_Task_Project]
GO
ALTER TABLE [dbo].[UsersTasks]  WITH CHECK ADD  CONSTRAINT [FK_UsersTasks_Task] FOREIGN KEY([TaskId])
REFERENCES [dbo].[Task] ([TaskId])
GO
ALTER TABLE [dbo].[UsersTasks] CHECK CONSTRAINT [FK_UsersTasks_Task]
GO
ALTER TABLE [dbo].[UsersTasks]  WITH CHECK ADD  CONSTRAINT [FK_UsersTasks_User] FOREIGN KEY([UserId])
REFERENCES [dbo].[User] ([UserId])
GO
ALTER TABLE [dbo].[UsersTasks] CHECK CONSTRAINT [FK_UsersTasks_User]
GO
ALTER TABLE [dbo].[TimeEntry]  WITH CHECK ADD  CONSTRAINT [FK_TimeEntry_Task] FOREIGN KEY([TaskId])
REFERENCES [dbo].[Task] ([TaskId])
GO
ALTER TABLE [dbo].[TimeEntry] CHECK CONSTRAINT [FK_TimeEntry_Task]
GO

SET NUMERIC_ROUNDABORT OFF
GO
SET XACT_ABORT, ANSI_PADDING, ANSI_WARNINGS, CONCAT_NULL_YIELDS_NULL, ARITHABORT, QUOTED_IDENTIFIER, ANSI_NULLS ON
GO
/*Pointer used for text / image updates. This might not be needed, but is declared here just in case*/
DECLARE @pv binary(16)
BEGIN TRANSACTION
SET IDENTITY_INSERT [dbo].[Task] OFF
SET IDENTITY_INSERT [dbo].[Task] ON
INSERT INTO [dbo].[Task] ([TaskId], [TaskName], [Description], [StartDate], [EndDate], [CustomerId], [ProjectId]) VALUES (1, N'Create Project', N'Create a Silverlight Application project with a RIA Services link', '20100601 00:00:00.000', '20100602 00:00:00.000', NULL, NULL)
INSERT INTO [dbo].[Task] ([TaskId], [TaskName], [Description], [StartDate], [EndDate], [CustomerId], [ProjectId]) VALUES (2, N'Define Data Model', N'Create an Entity Framework model for the application data', '20100602 00:00:00.000', '20100603 00:00:00.000', NULL, NULL)
INSERT INTO [dbo].[Task] ([TaskId], [TaskName], [Description], [StartDate], [EndDate], [CustomerId], [ProjectId]) VALUES (3, N'Define domain service', N'Create a domain service to expose task data to the client', '20100603 00:00:00.000', '20100604 00:00:00.000', NULL, NULL)
INSERT INTO [dbo].[Task] ([TaskId], [TaskName], [Description], [StartDate], [EndDate], [CustomerId], [ProjectId]) VALUES (4, N'Use data in the Silverlight client', N'Use the DomainContext and DomainDataSource to access and manipulate the data in the client', '20100604 00:00:00.000', '20100605 00:00:00.000', NULL, NULL)
SET IDENTITY_INSERT [dbo].[Task] OFF
COMMIT TRANSACTION


Но перед тем, как создать Domain Model Entities, необходимо создать БД и заполнить ее.
Для этого нужно добавить в серверной части (с приставкой .Web) папку «App_Data»:






Двойной клик по созданной БД «Database*.mdf». Откроется окно. Выбираем в нем опять «Database*.mdf», правой кнопкой — «Новый запрос». Откроется редактор запросов. Открываем TasksModel.sql, про который уже говорилось и копиреум от туда все, за исключением первых таких строк:
USE [master]
GO

/****** Object:  Database [TaskManager]    Script Date: 06/06/2010 18:01:49 ******/
IF  EXISTS (SELECT name FROM sys.databases WHERE name = N'TaskManager.mdf')
DROP DATABASE [TaskManager.mdf]
GO

CREATE DATABASE [TaskManager.mdf]
GO

USE [TaskManager.mdf]
GO

Нажимаем на кнопку «Выполнить». Все. БД заполнена таблицами, а таблица Tasks заполнена данными.

Жмем правой кнопкой мыши по TaskManager.Web и выбираем «Добавить»-«Создать элемент». Выбираем вкладку «Данные» в левом окне, а в правом выбираем «Модель ADO.NET EDM». Даем имя "TasksModel.edmx".


Добавить. Откроется диалоговое окно Мастер моделей EDM. Выбираем нужную БД, даем имя сущности «TaskManagerEntities» и кликаем Далее.


На следующем шаге выбираем галочкой таблицы. Даем имя модели «TaskManagerModel», а так же ставим галочку на «Формировать имена объектов во множественном или единственном числе»


Вуаля. Теперь у Вас имеется модель данных Entity Framework для всех таблиц в БД. Однако в этом уроке вы будете работать только с таблицей «Task», однако далее таблицы будут добавляться.

Постройте решение, что б убедиться, что все сделано правильно.

После создания EDM — вам должно открыться окно дизайнера.Если оно автоматически не открылось, то двойной клик в Обозревателе решения по TasksModel.edmx. Далее кликаем по пустой области. И переходим в окно свойств. Меняем параметр«Стратегия создания кода» с «Нет» на «По умолчанию». Пересобираем.


Теперь Пришло время создать службу домена.

Шаг 3: Создание Domain Service


Сейчас создадим ядро RIA Services — службу домена. Правой кнопкой на серверной части: TaskManager.Web – «Добавить»-«Создать элемент». Выбираем в левом окне категория «Веб», а справа «Класс DomainService». Даем имя "TasksDomainService".


После нажатия на кнопку Добавить, переходим в окно настроек службы. Так как сейчас будем работать только с таблицей Tasks — выбираем ее. В столбце «Включить редактирование» галочка не нужна. Пока. Если ее поставить то получите набор CRUD. Если нет – то только Get (чтение). Жмем ОК.

Созданный код, без коментариев будет выглядеть так:
    [EnableClientAccess()]
    public class TasksDomainService : LinqToEntitiesDomainService<TasksModelEntities>
    {
        public IQueryable<Task> GetTasks()
        {
            return this.ObjectContext.Tasks;
        }
    }


Класс LinqToEntitiesDomainService предоставляет связь между моделью EDM и службами WCF RIA. Атрибут «EnableClientAccess» указывает на то, что этот метод необходимо добавить в клиентскую часть во время автоматической кодогенерации. В данном случае получили только метод чтения данных из таблицы, однако можно реализовать полноценный CRUD или любые другие действия над данными, которые Вам будут нужны, и которые в итоге так же будут доступны в клиентском проекте. Как это делать рассмотрим более подробно в следующих статьях.

Так же запомните, что используются конвенции, которые относятся к типу возвращаемого значения(Queryable) и правилам именования(GetName). В данном случае имеем делегированную сущность модели данных ObjectContext, которая создается как часть базового класса и используется для извлечения всей таблицы Tasks. Вы так же можете усовершенствовать данный запрос добавив сортировку, фильтры и т.д., однако пока возвращаемое значение имеет тип IQueryable, WCF RIA Services понимает это и формирует соответствующие методы, которые позволят использовать данный функционал на стороне клиента.

Перестроим проект, чтоб убедиться, что все сделано правильно. В клиентском проекте, при нажатии на иконку двух листиков «Показать все файлы» — должен появиться файл с именем TaskManager.Web.g.cs, который и содержит сгенерированный код для клиента.

Шаг 4: Получение данных в UI используя DomainDataSource


Сегодня воспользуемся такой возможность WCF RIA Services, как «drag and drop», используя DomainDataSource непосредственно в UI. В следующих частях рассмотрим так же паттерн MVVM, как его использовать в WCF RIA, а так же как при таком подходе использовать DomainDataSource.

Итак, откройте MainPage.xaml. Далее откройте окно Источники данных. А теперь перетащите Tasks в графический редактор страницы на белое поле. Поправьте размещение. Все. В разделе XAML увидите созданный код для элемента Grid.




Строим и запускаем приложение. И наслаждаемся похожим видом:


Видео для этого урока




Исходники


На Github
Tags:
Hubs:
+2
Comments6

Articles