Приложение передачи файлов на Silverlight 3
Опубликовано 3 сентября 2009 23:43:00 | Coding4Fun
- Автор: Джованни Монтроне (Giovanni Montrone (EN))
- Сложность: средняя
- Необходимое время : 5-10 часов
- Затраты: бесплатно.
- ПО : Visual Web Developer Express SP1 (или Visual Studio 2008 SP1), Silverlight 3 SDK, Silverlight 3 Tools for VS, Silverlight Toolkit
- Оборудование: нет
- Попробуйте прямо сейчас : запустить приложение
- Исходные тексты : CodePlex
Введение
Раз за разом я сталкиваюсь с ситуацией, когда надо отправить кому-то файл, но сделать это оказывается не так-то просто. Программы мгновенного обмена сообщениями часто не могут обеспечить обмен из-за наличия брандмауэров, различий в версиях клиентских программ и других несовместимостей. При использовании электронной почты я сталкивался с тем, что почтовый сервер моего корреспондента блокировал файлы определенных типов. Предлагаемая программа позволяет двум пользователям быстро и просто связываться посредством клиентов, написанных на Silverlight 3 и слать друг другу файлы.
Обзор
В данном приложении пользователь сначала выбирает, инициировать ли ему самому сеанс или подключиться к существующему. Если он решает сам управлять сеансом, ему будет предоставлен случайный восьмисимвольный ключ, и программа будет находиться в состоянии ожидания до тех пор, пока не подключится другой пользователь. Когда какой-то пользователь захочет подключиться к данному сеансу, ему потребуется данный ключ для установления связи. Соединившись между собой, пользователи смогут обмениваться файлами и простыми текстовыми сообщениями.
Опрос в дуплексном режиме
Для обмена сообщениями между двумя клиентскими приложениями требуется некая центральная общая точка для маршрутизации сообщений. Поскольку Silverlight-приложение легко можно расположить на странице ASP.NET, мы будем использовать серверные возможности ASP.NET для управления коммуникациями между пользователями. Нам нужна служба, которая будет принимать входящие сообщения от Silverlight-клиента и переправлять их требуемому адресату. Это делается с помощью WCF-канала опроса в дуплексном режиме Polling Duplex (System . ServiceModel . PollingDuplex . dll). Silverlight 3 позволяет добавить ссылку на такую службу и скрывает от нас все сложные подробности ее работы. Я начал с использования файла DuplexService . cs из демонстрационного приложения, опубликованного на MIX09, когда вышла бета-версия Silverlight 3. Там есть пара абстрактных базовых классов и интерфейсов, от которых мы будем наследовать классы собственной службы.
FileSendService
Две главные вещи для создания собственной службы — это определение специальных типов сообщений, которые будут применяться в наших коммуникациях, и переопределение класса DuplexService таким образом, чтобы он правильно обрабатывал эти сообщения. Для создания собственных типов сообщений мы используем в качестве базового класс DuplexMessage , который определен в DuplexService . cs. Наши сообщения должны быть определены с атрибутом [ DataContract ] , а переменные-члены должны быть открытыми и иметь атрибут [ DataMember ] . Это позволит проекту Silverlight-клиента предоставлять доступ к этим определениям через ссылку службы. Кроме того, класс сообщения Duplex должен иметь атрибут [ KnownType ] для каждого созданного сообщения-наследника.
C#
1: [KnownType(typeof(HostSessionMessage))]
2: [KnownType(typeof(JoinSessionMessage))]
3: [KnownType(typeof(FileBeginUploadMessage))]
4: [KnownType(typeof(FileTransferBytesMessage))]
5: public class DuplexMessage { }
6: [DataContract]
7: public class HostSessionMessage : DuplexMessage
8: {
9: [DataMember]
10: public string Username;
11: }
12:
13: [DataContract]
14: public class JoinSessionMessage : DuplexMessage
15: {
16: [DataMember]
17: public string Username;
18: [DataMember]
19: public string SessionKey;
20: }
21:
22: [DataContract]
23: public class FileBeginUploadMessage : DuplexMessage
24: {
25: [DataMember]
26: public string FileName;
27: [DataMember]
28: public long TotalBytes;
29: }
30:
31: [DataContract]
32: public class FileTransferBytesMessage : DuplexMessage
33: {
34: [DataMember]
35: public long StartByte;
36: [DataMember]
37: public long PacketSize;
38: [DataMember]
39: public byte[] Bytes;
40: [DataMember]
41: public bool EndFile;
42: }
43:
VB
1: <DataContract(Namespace := "https://samples.microsoft.com/silverlight2/duplex"),
2: KnownType(GetType(HostSessionMessage)), KnownType(GetType(JoinSessionMessage)), KnownType(GetType(FileBeginUploadMessage)), KnownType(GetType(FileTransferBytesMessage))> _
3: Public Class DuplexMessage
4: End Class
5:
6: <DataContract()> _
7: Public Class HostSessionMessage
8: Inherits DuplexMessage
9: <DataMember()> Public Username As String
10: End Class
11:
12: <DataContract()> _
13: Public Class JoinSessionMessage
14: Inherits DuplexMessage
15: <DataMember()> Public Username As String
16: <DataMember()> Public SessionKey As String
17: End Class
18:
19: <DataContract()> _
20: Public Class FileTransferBytesMessage
21: Inherits DuplexMessage
22: <DataMember()> Public StartByte As Long
23: <DataMember()> Public PacketSize As Long
24: <DataMember()> Public Bytes() As Byte
25: <DataMember()> Public EndFile As Boolean
26: End Class
27:
28: <DataContract()> _
29: Public Class FileBeginUploadMessage
30: Inherits DuplexMessage
31: <DataMember()> Public FileName As String
32: <DataMember()> Public TotalBytes As Long
33: End Class
Следующий шаг — создание класса FileSendService, производного от DuplexService, как было сказано выше. Переопределим метод OnMessage чтобы обрабатывать сообщения собственных типов:
C#
1: [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
2: public class FileSendService : DuplexService
3: {
4: private List<SessionConnectionInfo> sessionConnections = new List<SessionConnectionInfo>();
5:
6: {...}
7:
8: protected override void OnMessage(string sessionId, DuplexMessage data)
9: {
10: if (data is HostSessionMessage)
11: CreateHostSession(data as HostSessionMessage);
12: else if (data is JoinSessionMessage)
13: JoinSession(data as JoinSessionMessage);
14: else if (data is FileBeginUploadMessage)
15: StartSendFile(data as FileBeginUploadMessage);
16: else
17: SendMessage(data);
18: }
19:
20: }
21:
22: else if (data is JoinSessionMessage)
23: JoinSession(data as JoinSessionMessage);
24: else if (data is FileBeginUploadMessage)
25: StartSendFile(data as FileBeginUploadMessage);
26: else
27: SendMessage(data);
28: }
29:
30: }
31:
32: }
33:
34: }
35:
VB
1: Public Class FileSenderServiceFactory
2: Inherits DuplexServiceFactory(Of FileSendService)
3: End Class
4:
5: <AspNetCompatibilityRequirements(RequirementsMode := AspNetCompatibilityRequirementsMode.Allowed)> _
6: Public Class FileSendService
7: Inherits DuplexService
8: Private sessionConnections As New List(Of SessionConnectionInfo)()
9:
10: ...
11:
12: Protected Overrides Sub OnMessage(ByVal sessionId As String, ByVal data As DuplexMessage)
13: If TypeOf data Is HostSessionMessage Then
14: CreateHostSession(TryCast(data, HostSessionMessage))
15: ElseIf TypeOf data Is JoinSessionMessage Then
16: JoinSession(TryCast(data, JoinSessionMessage))
17: ElseIf TypeOf data Is FileBeginUploadMessage Then
18: StartSendFile(TryCast(data, FileBeginUploadMessage))
19: Else
20: SendMessage(data)
21: End If
22: End Sub
23:
24: End Class
Чтобы отслеживать все хосты и подключенных к ним пользователей, создадим класс SessionConnectionInfo для управления соответствующими данными. Когда пользователь решает создать сеанс и управлять им, наш метод OnMessage получает сообщение HostSessionMessageи просматривает списокList < SessionConnectionInfo > на предмет наличия в нем другого хоста с тем же именем пользователя. Если таковой не обнаруживается, создается новый объект SessionConnectionInfo и сеансовый ключ, сгенерированный из случайных значений. Когда пользователь пытается подключиться к сеансу, метод OnMessage получает сообщение JoinSessionMessage , содержащее сеансовый ключ и имя подключающегося к сеансу пользователя. Наша служба ищет объект SessionConnectionInfo с таким сеансовым ключом и, если находит, связывает двух пользователей между собой.
C#
1: public class SessionConnectionInfo
2: {
3: public string HostUserName { get; set; }
4: public string ConnectedUsername { get; private set; }
5: public string SessionKey { get; set; }
6:
7: public string ConnectedUserInternalSession { get; private set; }
8: public string HostInternalSession { get; set; }
9:
10: public bool UserConnected
11: {
12: get { return ConnectedUserInternalSession != string.Empty; }
13: }
14:
15: public SessionConnectionInfo()
16: {
17: ConnectedUserInternalSession = string.Empty;
18: ConnectedUsername = string.Empty;
19: }
20:
21: ....
22: }
VB
1: Public Class SessionConnectionInfo
2: Private privateHostUserName As String
3: Public Property HostUserName() As String
4: Get
5: Return privateHostUserName
6: End Get
7: Set(ByVal value As String)
8: privateHostUserName = value
9: End Set
10: End Property
11: Private privateConnectedUsername As String
12: Public Property ConnectedUsername() As String
13: Get
14: Return privateConnectedUsername
15: End Get
16: Private Set(ByVal value As String)
17: privateConnectedUsername = value
18: End Set
19: End Property
20: Private privateSessionKey As String
21: Public Property SessionKey() As String
22: Get
23: Return privateSessionKey
24: End Get
25: Set(ByVal value As String)
26: privateSessionKey = value
27: End Set
28: End Property
29:
30: Private privateConnectedUserInternalSession As String
31: Public Property ConnectedUserInternalSession() As String
32: Get
33: Return privateConnectedUserInternalSession
34: End Get
35: Private Set(ByVal value As String)
36: privateConnectedUserInternalSession = value
37: End Set
38: End Property
39: Private privateHostInternalSession As String
40: Public Property HostInternalSession() As String
41: Get
42: Return privateHostInternalSession
43: End Get
44: Set(ByVal value As String)
45: privateHostInternalSession = value
46: End Set
47: End Property
48:
49: Public ReadOnly Property UserConnected() As Boolean
50: Get
51: Return ConnectedUserInternalSession <> String.Empty
52: End Get
53: End Property
54:
55: Public Sub New()
56: ConnectedUserInternalSession = String.Empty
57: ConnectedUsername = String.Empty
58: End Sub
59:
60:
61: End Class
62:
Клиентская часть
Основы серверной части готовы и можем добавить ссылку на нашу службу. Наша служба базируется в ASP.NET посредством простого xml-файла (см. FileSendService . svc), который позволяет нашему Silverlight-проекту ее видеть.
Не забудьте установить флажок «Всегда создавать контракты сообщений» (“Always generate message contracts”). Имейте в виду: часть проекта, связанная с веб, должна быть скомпилирована, чтобы можно было обнаружить службу.
Теперь у нас есть доступ к классу DuplexServiceClient , который позволит нам обмениваться сообщениями с сервером. Создадим экземпляр службы, как показано ниже. Предварительно надо вручную добавить ссылку на сборку System . ServiceModel . PollingDuplex.
C#
1: private DuplexServiceClient fileDuplexService;
2:
3: private CustomBinding binding = new CustomBinding(
4: new PollingDuplexBindingElement(),
5: new BinaryMessageEncodingBindingElement(),
6: new HttpTransportBindingElement());
7:
8: public MainPage()
9: {
10: InitializeComponent();
11: fileDuplexService = new DuplexServiceClient(binding, new EndpointAddress("https://localhost:9797/FileSendService.svc"));
12: ...
13: }
VB
1: Private fileDuplexService As DuplexServiceClient
2:
3: Private binding As New CustomBinding(New PollingDuplexBindingElement(), New BinaryMessageEncodingBindingElement(), New HttpTransportBindingElement())
4:
5: Public Sub New()
6: InitializeComponent()
7: fileDuplexService = New DuplexServiceClient(binding, New EndpointAddress("https://localhost:9797/FileSendService.svc"))
8: End Sub
Обратите внимание: на момент написания этой статьи, при добавлении ссылки на службу, файл ServiceReferences . ClientConfig не создавался. По этой причине в приведенном выше коде это делается программно.
Настроим службу таким образом, чтобы она обрабатывала отправляемые и получаемые сообщения. Для отправки сообщения сначала надо создать сообщение DupexMessage (или его производную) и применить метод SendToServiceAsync. Для этого требуется объект SendToService , содержащий данное сообщение, а также необязательный объект userState , который мы можем применять для маркирования запроса. В данном случае мы передаем перечисление, описывающее состояние передачи. В приведенном ниже примере, когда пользователь щелкает кнопку отправки, предварительно выбрав файл, мы открываем наш файл и отправляем сообщение FileBeginUpload , содержащее имя файла и его размер. Заметьте: сервер настроен на отказ от отправки файлов размером более 20 миллионов байт.
C#
1: private void btnSendFile_Click(object sender, RoutedEventArgs e)
2: {
3: OpenFileDialog openFileDialog = new OpenFileDialog();
4: openFileDialog.Multiselect = false;
5: openFileDialog.ShowDialog();
6: if (openFileDialog.File != null)
7: {
8: fileToSend = openFileDialog.File.OpenRead();
9:
10: FileBeginUploadMessage fsm = new FileBeginUploadMessage();
11: fsm.FileName = openFileDialog.File.Name;
12: fsm.TotalBytes = openFileDialog.File.Length;
13: fileDuplexService.SendToServiceAsync(new SendToService(fsm), FileSendState.FileStart);
14: ....
15: }
16: }
VB
1: Private Sub btnSendFile_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
2: Dim openFileDialog As New OpenFileDialog()
3: openFileDialog.Multiselect = False
4: openFileDialog.ShowDialog()
5: If openFileDialog.File IsNot Nothing Then
6: fileToSend = openFileDialog.File.OpenRead()
7: Dim fsm As New FileBeginUploadMessage()
8: fsm.FileName = openFileDialog.File.Name
9: fsm.TotalBytes = openFileDialog.File.Length
10: totalBytesSent = 0
11: fileDuplexService.SendToServiceAsync(New SendToService(fsm), FileSendState.FileStart)
12: ....
13: End If
14: End Sub
Наша служба генерирует два события, требующие обработки: SendToServiceCompleted и SendToClientReceived. Событие SendToServiceCompleted возникает после того как сервер подтверждает получение и завершение обработки отправленного клиентом сообщения. После того, как сервер получил и обработал сообщение FileBeginUploadMessage, приведенный ниже обработчик события получает результаты. В данном случае, если нет ошибки, а userState имеет значение FileSendState . FileStart, метод отправляет сообщение FileTransferBytesMessage , которое передает данные файла.
C#
1: private void FileDuplexServiceSendToServiceCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
2: {
3: if (e.Error == null)
4: {
5: {...}
6: if ((FileSendState)e.UserState == FileSendState.FileEnd)
7: {
8: fileToSend.Close();
9: fileProgress.Value = 100;
10: return;
11: }
12: if ((FileSendState)e.UserState == FileSendState.FileStart || (FileSendState)e.UserState == FileSendState.FileContinue)
13: {
14:
15: ...
16: FileTransferBytesMessage fileMessage = new FileTransferBytesMessage();
17: fileMessage.StartByte = totalBytesSent;
18: fileMessage.EndFile = false;
19: fileMessage.PacketSize = CHUNK;
20:
21: ...
22:
23: byte[] bytes = new byte[numBytesToRead];
24: fileToSend.Read(bytes, 0, numBytesToRead);
25: totalBytesSent += numBytesToRead;
26: fileMessage.Bytes = bytes;
27:
28: if (fileMessage.EndFile)
29: fileDuplexService.SendToServiceAsync(new SendToService(fileMessage), FileSendState.FileEnd);
30: else
31: fileDuplexService.SendToServiceAsync(new SendToService(fileMessage), FileSendState.FileContinue);
32: }
33: }
34: }
VB
1: Private Sub FileDuplexServiceSendToServiceCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.AsyncCompletedEventArgs)
2: If e.Error Is Nothing Then
3: If e.UserState Is Nothing Then
4: Return
5: End If
6: If CType(e.UserState, FileSendState) = FileSendState.FileEnd Then
7: fileToSend.Close()
8: fileProgress.Value = 100
9: Return
10: End If
11: If CType(e.UserState, FileSendState) = FileSendState.FileStart OrElse CType(e.UserState, FileSendState) = FileSendState.FileContinue Then
12: ...
13: Dim fileMessage As New FileTransferBytesMessage()
14: fileMessage.StartByte = totalBytesSent
15: fileMessage.EndFile = False
16: fileMessage.PacketSize = CHUNK
17: ...
18: Dim bytes(numBytesToRead - 1) As Byte
19: fileToSend.Read(bytes, 0, numBytesToRead)
20: totalBytesSent += numBytesToRead
21: fileMessage.Bytes = bytes
22:
23: If fileMessage.EndFile Then
24: fileDuplexService.SendToServiceAsync(New SendToService(fileMessage), FileSendState.FileEnd)
25: Else
26: fileDuplexService.SendToServiceAsync(New SendToService(fileMessage), FileSendState.FileContinue)
27: End If
28: End If
29: End If
30: End Sub
31:
Событие SendToClientReceived генерируется после отправки сообщения нашей службой. Клиент выясняет тип этого сообщения и соответствующим образом его обрабатывает. В приведенном ниже методе пользователь принимает или отклоняет получение файла при поступлении сообщения FileBeginUpload. Если пользователь не принимает файл, отправляется сообщение FileDenyMessage , указывающее отправителю, что надо прекратить передавать данные. В противном случае данные добавляются в буфер.
C#
1: private void FileDuplexServiceSendToClientReceived(object sender, SendToClientReceivedEventArgs e)
2: {
3: if (e.Error == null)
4: {
5: if (e.request.msg is ClientConnectedMessage)
6: {
7: ClientConnectedMessage msg = (ClientConnectedMessage)e.request.msg;
8: AddMsgToListbox(msg.Username + " has just connected.");
9: connectedTo = msg.Username;
10: UIState = UIState.Chat;
11: }
12:
13: else if (e.request.msg is HostSessionServerMessage)
14: {
15: HostSessionServerMessage hssm = e.request.msg as HostSessionServerMessage;
16: if (hssm.Failed) {...}
17: SessionCreated(hssm);
18:
19: }
20: else if (e.request.msg is JoinSessionServerMessage)
21: {
22: JoinSessionServerMessage jssm = e.request.msg as JoinSessionServerMessage;
23: if (jssm.Failed) {....}
24: SessionJoined(jssm);
25: }
26: else if (e.request.msg is FileBeginUploadMessage )
27: {
28: FileBeginUploadMessage fsm = (FileBeginUploadMessage)e.request.msg;
29:
30: int sizeInKB = (int)fsm.TotalBytes / 1024;
31: totalRevd = 0;
32: if (MessageBox.Show(connectedTo + " would like to send you the file: " + fsm.FileName + ", Size: " + sizeInKB + ". Would you like to receive this file?", "File Upload", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
33: {
34: bytesReceived = new List<byte>((int)fsm.TotalBytes);
35: fileNameReceiving = fsm.FileName;
36: ....
37: }
38: else
39: {
40: fileDuplexService.SendToServiceAsync(new SendToService(new FileDenyMessage()));
41: }
42: }
43: else if (e.request.msg is FileTransferBytesMessage)
44: {
45: if (bytesReceived == null)
46: return;
47: FileTransferBytesMessage fm = (FileTransferBytesMessage)e.request.msg;
48: bytesReceived.AddRange(fm.Bytes);
49: ....
50: }
51: else {....}
52:
53: }
54: }
VB
1: Private Sub FileDuplexServiceSendToClientReceived(ByVal sender As Object, ByVal e As SendToClientReceivedEventArgs)
2: If e.Error Is Nothing Then
3: If TypeOf e.request.msg Is ClientConnectedMessage Then
4: Dim msg As ClientConnectedMessage = CType(e.request.msg, ClientConnectedMessage)
5: AddMsgToListbox(msg.Username & " has just connected.")
6: connectedTo = msg.Username
7: UIState = UIState.Chat
8:
9: ElseIf TypeOf e.request.msg Is HostSessionServerMessage Then
10: Dim hssm As HostSessionServerMessage = TryCast(e.request.msg, HostSessionServerMessage)
11: If hssm.Failed Then ...
12:
13: SessionCreated(hssm)
14:
15: ElseIf TypeOf e.request.msg Is JoinSessionServerMessage Then
16: Dim jssm As JoinSessionServerMessage = TryCast(e.request.msg, JoinSessionServerMessage)
17: If jssm.Failed Then ...
18:
19: SessionJoined(jssm)
20: ElseIf TypeOf e.request.msg Is FileBeginUploadMessage Then
21: Dim fsm As FileBeginUploadMessage = CType(e.request.msg, FileBeginUploadMessage)
22:
23: Dim sizeInKB As Integer = CInt(Fix(fsm.TotalBytes)) / 1024
24: totalRevd = 0
25: If MessageBox.Show(connectedTo & " would like to send you the file: " & fsm.FileName & ", Size: " & sizeInKB & " KB. Would you like to receive this file?", "File Upload", MessageBoxButton.OKCancel) = MessageBoxResult.OK Then
26: bytesReceived = New List(Of Byte)(CInt(Fix(fsm.TotalBytes)))
27: fileNameReceiving = fsm.FileName
28: ...
29: Else
30: fileDuplexService.SendToServiceAsync(New SendToService(New FileDenyMessage()))
31: End If
32: ElseIf TypeOf e.request.msg Is FileTransferBytesMessage Then
33: If bytesReceived Is Nothing Then
34: Return
35: End If
36: Dim fm As FileTransferBytesMessage = CType(e.request.msg, FileTransferBytesMessage)
37: bytesReceived.AddRange(fm.Bytes)
38: ...
39: ElseIf
40: ...
41:
42: End If
43: End Sub
По завершении передачи файла пользователь увидит две кнопки. Одна позволяет сохранить файл, а другая — уничтожить полученные данные. Если пользователь выбирает сохранение, появляется диалоговое окно SaveFileDialog в котором можно задать имя файла, а расширение водить нет необходимости. После задания пользователем имени фала, данные записываются на диск. В завершении серверу отправляется ответное сообщение, которое позволит ему уведомить пользователя, что принимающая сторона сделала что-то с файлом. Во время передачи файла кнопка Send File остается недоступной и разблокируется только после того, как был получен или отклонен или операция была прервана.
C#
1: private void btnSaveFile_Click(object sender, RoutedEventArgs e)
2: {
3: SaveFileDialog sfd = new SaveFileDialog();
4: string extension = GetExtension(fileNameReceiving);
5: sfd.DefaultExt = extension;
6: sfd.Filter = extension + " Files|" + extension;
7:
8: if (sfd.ShowDialog() == true)
9: {
10: using (Stream fsx = sfd.OpenFile())
11: {
12: byte[] fBytes = bytesReceived.ToArray();
13: fsx.Write(fBytes, 0, fBytes.Length);
14: fsx.Close();
15: }
16:
17: fileDuplexService.SendToServiceAsync(new SendToService(new FileReceivedMessage()));
18: UIState = UIState.Chat;
19: btnSendFile.IsEnabled = true;
20: }
21: }
22:
VB
1: Private Sub btnSaveFile_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
2: Dim sfd As New SaveFileDialog()
3: Dim extension As String = GetExtension(fileNameReceiving)
4: sfd.DefaultExt = extension
5: sfd.Filter = extension & " Files|" & extension
6:
7: If sfd.ShowDialog() = True Then
8: Using fsx As Stream = sfd.OpenFile()
9: Dim fBytes() As Byte = bytesReceived.ToArray()
10: fsx.Write(fBytes, 0, fBytes.Length)
11: fsx.Close()
12: End Using
13:
14: fileDuplexService.SendToServiceAsync(New SendToService(New FileReceivedMessage()))
15: UIState = UIState.Chat
16: btnSendFile.IsEnabled = True
17: End If
18: End Sub
Завершение
В целом приложение выполняет мои задумки. Пользователи могут обмениваться файлами, и есть даже элементарный чат. Конечно же, есть много вариантов усовершенствования этой программы и увеличения ее надежности. Сейчас поддерживается список соединений, но не выполняется простое пингование, позволяющее убедиться в том, что клиент еще на месте. Единственный способ узнать, что клиент отключился, это сбой в передаче сообщения или когда пользователь щелкает кнопку отключения. Требуется лучшая поддержка этого списка. Данные файла хранятся в памяти, что заставило меня ввести ограничения по объему, но я уверен, что с использованием таких вещей, как локальное хранилище Silverlight, это ограничение можно облегчить.
Благодарности
Я хочу поблагодарить Брайана Пика (Brian Peek (EN)), который нашел время рецензировать мою статью и проверить код программы.
Дополнительные замечания
В проекте ASP.NET необходимо указать в качестве ссылки файл System.ServiceModel.PollingDuplex.dll. Где-то в промежутке между Silverlight 3 Beta и Silverlight 3 RTW, этот файл исчез из числа доступных в основном списке ссылок .NET. Я его добавлял из % ProgramFiles %\ MicrosoftSDKs \ Silverlight \ v3.0\ Libraries \ Server. Для пользователей 64-разрядной версии это будет папка ProgramFiles ( x86) .
Для простоты использования я в данном проекте работал со статическим портом 9797. При установке на сервере вам надо переименовать все ссылки https://localhost:9797 в Silverlight-проекте. Это останется в силе, пока поддерживается файл config.
В пользовательском интерфейсе задействована тема TwilightBlue из Silverlight Toolkit. Дополнительные сведения по этому вопросу см. на странице https://www.codeplex.com/Silverlight (EN).