Создание приложения веб-чата на Silverlight 2 - Служба обмена данными между приложениями (WCF)

ОГЛАВЛЕНИЕ

Служба обмена данными между приложениями (WCF)

Так как Silverlight является клиентской технологией, есть несколько способов управления доступом к базе данных, все из которых использует своего рода AJAX или JavaScript. Предпочтительно использовать WCF вместо классической технологии веб-службы (.asmx). Чтобы добавить службу WCF, щелкните правой кнопкой мыши по веб-проекту, выберите «Добавить новый элемент», затем выберите "Служба WCF", как показано ниже. При нажатии «Добавить» в веб-проект добавляются три файла: файл ".svc", соответствующий файл кода ".svc.cs" и файл интерфейса "I.....cs". Также вставляется запись в файл Web.config.

Интерфейс

Файл интерфейса служит основным интерфейсом для службы WCF. В нем определяются все методы, реализуемые службой WCF. Интерфейс помечен как ServiceContract. Каждый из методов помечен как OperationContract.

12     [ServiceContract]
13     public interface ILinqChatService
14     {
15         [OperationContract]
16         int UserExist(string username, string password);
17
18         [OperationContract]
19         List<MessageContract> GetMessages(int messageID, int roomID,
                                             DateTime timeUserJoined);
20
21         [OperationContract]
22         void InsertMessage(int roomID, int userID, int? toUserID,
                              string messageText, string color);
23
24         [OperationContract]
25         List<UserContract> GetUsers(int roomID, int userID);
26
27         [OperationContract]
28         void LogOutUser(int userID, int roomID, string username);
29     }

Внутри этого интерфейса было создано два открытых класса. Классы определяют свойства соответствующего DataContract. Члены данных класса MessageContract прямо увязаны с таблицей «Сообщение», а члены данных класса UserContract прямо увязаны с таблицей «Пользователь» в базе данных. Замечание: были добавлены только члены данных, используемые в этом руководстве. Также эти два класса были встроены в интерфейс, а не сделаны двумя отдельными открытыми классами.

31     [DataContract]
32     public class MessageContract
33     {
34         [DataMember]
35         public int MessageID;
36
37         [DataMember]
38         public string Text;
39
40         [DataMember]
41         public string UserName;
42
43         [DataMember]
44         public string Color;
45     }
46
47     [DataContract]
48     public class UserContract
49     {
50         [DataMember]
51         public int UserID;
52
53         [DataMember]
54         public string UserName;
55     }

Реализация интерфейса в LinqChatService

Интерфейс реализован в файле отделенного кода службы WCF, "LinqChatService.svc.cs". Для реализации интерфейса сначала надо унаследовать интерфейс, как показано ниже. Наследование запрограммировано по умолчанию.

11     public class LinqChatService : ILinqChatService

Щелкните правой кнопкой мыши по интерфейсу "ILinqChatService", выберите «Реализовать интерфейс», далее выберите «Реализовать интерфейс явно», как показано ниже. Это генерирует члены интерфейса внутри метки области.

Методы члена интерфейса

Теперь, когда известно, как реализовать члены интерфейса, рассматривается реализация каждого из методов члена. Каждый из методов члена обращается к базе данных посредством LINQ-для-SQL. Каждый из методов был назван осмысленно исходя из его работы.

•    InsertMessage: Этот метод вставляет в базу данных одно сообщение за раз. Он вызывается, когда пользователь набирает сообщение в разделе чата и затем жмет кнопку «Отправить».

13     void ILinqChatService.InsertMessage(int roomID, int userID, 
            int? toUserID, string messageText, string color)
14     {
15         Message message = new Message();
16         message.RoomID = roomID;
17         message.UserID = userID;
18         message.ToUserID = toUserID;
19         message.Text = messageText;
20         message.Color = color;
21         message.TimeStamp = DateTime.Now;
22
23         LinqChatDataContext db = new LinqChatDataContext();
24         db.Messages.InsertOnSubmit(message);
25
26         try
27         {
28             db.SubmitChanges();
29         }
30         catch (Exception)
31         {
32             throw;
33         }
34     }

•    GetMessages: Этот метод получает сообщения для конкретного раздела с момента присоединения вошедшего пользователя к разделу. Он получает лишь еще не извлеченные сообщения; поэтому передается messageID последнего сообщения, извлеченного при предыдущем вызове этого метода. Выделенный ниже код timeUserJoined.AddSeconds(1) ограничивает сообщения теми, которые были получены за 1 секунду после момента присоединения вошедшего пользователя к разделу. Причина в том, что при присоединении пользователя к разделу в базу данных вставляется сообщение, гласящее "пользователь присоединился к разделу". Это сообщение увидят все остальные участники чата, исключая вошедшего пользователя.

Обратите внимание на цикл foreach. Появляется вопрос: почему нельзя вернуть обобщенный список типа List<Message>? Интерфейс не понимает сложные типы, не определенные явно как DataContract. Поэтому была создана почти точная копия членов таблицы «Сообщение» и явно определена как класс MessageContract, где каждый член DataContract является DataMember.

36     List<MessageContract> ILinqChatService.GetMessages(int messageID, 
                             int roomID, DateTime timeUserJoined)
37     {
38         LinqChatDataContext db = new LinqChatDataContext();
39
40         var messages = (from m in db.Messages
41                         where m.RoomID == roomID &&
42                         m.MessageID > messageID &&
43                         m.TimeStamp > timeUserJoined.AddSeconds(1)
44                         orderby m.TimeStamp ascending
45                         select new { m.MessageID, m.Text, m.User.Username,
                                        m.TimeStamp, m.Color });
46
47         List<MessageContract> messageContracts = new List<MessageContract>();
48
49         foreach (var message in messages)
50         {
51             MessageContract messageContract = new MessageContract();
52             messageContract.MessageID = message.MessageID;
53             messageContract.Text = message.Text;
54             messageContract.UserName = message.Username;
55             messageContract.Color = message.Color;
56             messageContracts.Add(messageContract);
57         }
58
59         return messageContracts;
60     }

•    GetUsers: Этот метод получает всех пользователей в конкретном разделе. Сначала он проверяет, есть ли вошедший пользователь в таблице LoggedInUser; если нет – пользователь вставляется (строки 75-82). Эта проверка упрощает вставку нового пользователя; Когда пользователь впервые входит в раздел чата, он вставляется в таблицу LoggedInUser. Все последующие вызовы для получения пользователей извлекают всех пользователей из базы данных.

Как в методе GetMessages, все извлеченные пользователи присваиваются классу контракта данных UserContract (строки 92-97).

62     List<UserContract> ILinqChatService.GetUsers(int roomID, int userID)
63     {
64         LinqChatDataContext db = new LinqChatDataContext();
65
66         // Проверяется, существует ли этот аутентифицированный пользователь в
67         // таблице LoggedInUser (подразумевается, что пользователь вошел в этот раздел)
68         var user = (from u in db.LoggedInUsers
69                     where u.UserID == userID
70                     && u.RoomID == roomID
71                     select u).SingleOrDefault();
72
73         // если пользователь не существует в таблице LoggedInUser,
74         // он добавляется/вставляется в таблицу
75         if (user == null)
76         {
77             LoggedInUser loggedInUser = new LoggedInUser();
78             loggedInUser.UserID = userID;
79             loggedInUser.RoomID = roomID;
80             db.LoggedInUsers.InsertOnSubmit(loggedInUser);
81             db.SubmitChanges();
82         }
83
84         // получить всех вошедших в этот раздел пользователей
85         var loggedInUsers = from l in db.LoggedInUsers
86                             where l.RoomID == roomID
87                             orderby l.User.Username ascending
88                             select new { l.User.Username };
89
90         List<UserContract> userContracts = new List<UserContract>();
91
92         foreach (var loggedInUser in loggedInUsers)
93         {
94             UserContract userContract = new UserContract();
95             userContract.UserName = loggedInUser.Username;
96             userContracts.Add(userContract);
97         }
98
99         return userContracts;
100     }

•    UserExist: Проверяет, существует ли пользователь. Если пользователь существует –  возвращается идентификатор пользователя; если нет –  возвращается -1. Этот метод вызывается из файла отделенного кода Login.xaml, чтобы проверить, существуют ли в базе данных имя пользователя и пароль, введенные пользователем. Почему возвращается идентификатор пользователя, и почему только он? С момента входа пользователя приложение помнит идентификатор пользователя и имя пользователя этого пользователя, подобно эффекту сессии. Для минимизации извлечения данных в этот момент нужен только идентификатор пользователя, так как имя пользователя уже было предоставлено через текстовое поле имени пользователя со страницы входа.

102     int ILinqChatService.UserExist(string username, string password)
103     {
104         int userID = -1;
105
106         LinqChatDataContext db = new LinqChatDataContext();
107
108         var user = (from u in db.Users
109                     where u.Username == username
110                     && u.Password == password
111                     select new { u.UserID }).SingleOrDefault();
112
113         if (user != null)
114             userID = user.UserID;
115
116         return userID;
117     }

•    LogOutUser: Разрегистрирует пользователя. Строки 124-130 удаляют пользователя из таблицы LoggedInUser. Строки 133-142 вставляют сообщение в таблицу «Сообщение», говорящее, что пользователь покинул раздел, чтобы другие пользователи видели это сообщение при выходе пользователя. Доступ к базе данных упрощен; вместо того, чтобы сделать дополнительный запрос на базе идентификатора пользователя для получения имени выходящего пользователя, метод ожидает имя пользователя, как выделено ниже. Поэтому запоминается имя пользователя текущего пользователя, вошедшего в этот раздел чата. Далее рассказано, как это делается. Метод LogOutUser вызывается при нажатии кнопки «Выйти» в пользовательском интерфейсе Chatroom.xaml.

Замечание: Можно разрегистрировать пользователя, когда он жмет кнопку закрытия браузера, перехватив событие onunload тега body.

119     void ILinqChatService.LogOutUser(int userID, int roomID, string username)
120     {
121         // Разрегистрировать пользователя, удалив его из таблицы LoggedInUser
122         LinqChatDataContext db = new LinqChatDataContext();
123
124         var loggedInUser = (from l in db.LoggedInUsers
125                             where l.UserID == userID
126                             && l.RoomID == roomID
127                             select l).SingleOrDefault();
128
129         db.LoggedInUsers.DeleteOnSubmit(loggedInUser);
130         db.SubmitChanges();
131
132         // вставить текст «пользователь покинул раздел»
133         Message message = new Message();
134         message.RoomID = roomID;
135         message.UserID = userID;
136         message.ToUserID = null;
137         message.Text = username + " left the room.";
138         message.Color = "Gray";
139         message.TimeStamp = DateTime.Now;
140
141         db.Messages.InsertOnSubmit(message);
142         db.SubmitChanges();
143     }