Блог Сергея Байдачного

Мой блог о технологиях

Windows Phone 7: Разрабатываем приложение с использованием Push Notification (часть 1, веб-роль)

4 комментария

Этим постом я начинаю серию из 4-х статей, посвященных разработке решения для Windows Phone 7 с использованием Push Notification. Описанное тут решение мы разрабатывали для Украинского Медиа Холдинга. Между тем оно является типовым, хотя информацию по некоторым шагам найти достаточно сложно.

Начнем с описания задачи: партнер реализовал специальную службу, которая позволяет пользователям мобильных телефонов получать нотификацию о ходе прохождения футбольных игр, а также получать расписание ближайших игр, интересных для украинского зрителя. Доступ к службе осуществляется на основе платной подписки. Оплата взымается, в основном, за использование гейтов мобильных операторов для рассылки СМС сообщений.

Поскольку Windows Phone 7 обладает такой замечательной вещью, как Push Notification, то было принято решение, реализовать аналогичный функционал для устройств WP 7 на бесплатной основе. Ведь для использования Push Notification мобильный оператор не используется, а уведомления доставляются с помощью специальной Push Notification службы, которой управляет Microsoft.

Идея очень простая: клиент, используя приложение партнера, подписывается на службу доставки мгновенных сообщений. При этом клиент передает партнеру уникальный URL, позволяющий партнеру связаться с Notification службой и передать сообщение на конкретный телефон. Партнер, генерируя новое сообщение, опрашивает свою базу адресов, и по каждому из адресов отправляет сообщение в Push Notification службу, которая на основании уникального адреса нотифицирует конкретный телефон, передавая ему сообщение.

Таким образом, при разработке этого решения, нам необходимо следующие три компонента:

· Служба, которая позволяет получить новое сообщение от оператора и поставить его в очередь;

· Модуль (служба), который обрабатывает сообщения в очереди;

· Клиент для Windows Phone 7, позволяющий пользователю подписаться на службу и настроить параметры уведомлений;

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

Учитывая проблемы выше и список основных компонентов, было принято решение использовать технологию Windows Azure для создания подобного решения. В случае использования Windows Azure, первый компонент можно было бы реализовать в виде обычных WCF служб, для реализации очереди использовать соответствующий тип хранилища в Storage службе, а для компонента обработки элементов очереди можно использовать Рабочую роль. Последняя предназначена для работы в фоне и может регулярно опрашивать очередь на наличие новых элементов. Кроме того, Рабочая роль очень хорошо масштабируется.

Таким образом, 3 из 4 статей будут иметь достаточно опосредованное отношение к Windows Phone, а скорее относятся к Windows AzureJ

Начнем работу. На первом шаге создадим проект в Visual Studio на базе шаблона Windows Azure

image

В создаваемое решение добавим две роли, это WCF Service Web Role и Worker Role. Тут Вашим ролям можно дать более осмысленные названия (я не буду).

image

Про Рабочую роль сейчас забудем и сосредоточимся на реализации WCF службы.

Опишем интерфейс нашей службы следующим образом:

   1:      [ServiceContract]
   2:      public interface INotification
   3:      {
   4:          [OperationContract]
   5:          void SubscribePhoneToService(Guid guid, string pushServiceUri);
   6:   
   7:          [OperationContract]
   8:          SMSMessage[] RefreshData();
   9:   
  10:          [OperationContract]
  11:          void NewSMSMessage(string sms, string login, string password);
  12:      }
  13:   
  14:   
  15:      [DataContract]
  16:      public class SMSMessage
  17:      {
  18:          string date;
  19:          string message;
  20:   
  21:          [DataMember]
  22:          public string DateTimeValue
  23:          {
  24:              get { return date; }
  25:              set { date = value; }
  26:          }
  27:   
  28:          [DataMember]
  29:          public string MessageValue
  30:          {
  31:              get { return message; }
  32:              set { message = value; }
  33:          }
  34:      }

 

Тут описан класс SMSMessage, с помощью которого мы будем передавать данные между службой и телефонами, а также интерфейс INotification, описывающий три метода интерфейса службы. Тут определены следующие методы:

· SubscribePhoneToService – этот метод позволяет подписать телефон на сообщения, принимая уникальный ID телефона и адрес службы уведомлений для отправки сообщений данному устройству. Обратите внимание, что сообщения высылаются каждому подписанному телефону отдельно. Это дает возможность кастомизировать текст сообщения и фильтры, в зависимости от настроек. В данном случае какой-то специальной настройки для конкретного телефона мы не поддерживаем, поэтому метод принимает всего два параметра;

· RefreshData – этот метод необходим, если пользователь запустил приложение и хочет посмотреть последние сообщения;

· NewSMSMessage – этот метод инициируется оператором и служит для передачи сообщения в очередь. Поскольку мы не хотим, чтобы все желающие отправляли сообщение в очередь, метод кроме сообщения принимает логин и пароль оператора, используя примитивный способ аутентификации.

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

Следующим шагом мы должны перейти к реализации интерфейса. Но это делать еще рано, так как нам необходимо еще хранилище для наших подписок и сообщений, а также где-то определить параметры приложения, такие как строка соединения с базой, логин и пароль оператора и т. д.

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

Для этого воспользуемся панелью управления Windows Azure и создадим новую базу

image

Тут же, в панели, можно увидеть строку подключения к базе, которую мы будем использовать из приложения. Вот пример такой строки:

Server=tcp:imc39784zf.database.windows.net,1433;Database=UkraineTest;User ID=sbaidachni@imc39784zf;Password=myPassword;Trusted_Connection=False;Encrypt=True;

Далее нажмите кнопку Manage и создайте две таблицы, позволяющие хранить подписки и сообщения.

image

image

Помните, чтобы тестировать подключения к базам в SQL Azure, вы должны через панель управления выполнить настройки firewall, которые позволят подключаться с Вашей девелоперской машины к базе.

Поскольку нам нужен также и Storage Service, то создадим и его. Для этого также воспользуемся панелью управления.

image

Подготовительная работа завершена.

Теперь вернемся в Visual Studio и приступим к написанию кода. Первым делом внесем изменения в конфигурацию решения.

Обратите внимание, что параметры приложения (строка соединения, логин, доступ к Storage аккаунту) нужно прописывать в csdef и cscfg файлах. Если Вы внесете настройки в Web.Config (как Вы привыкли делать в обычных веб-приложениях), то менять их, в процессе работы, у Вас не выйдет.

Поэтому перейдите к настройкам нашей веб-роли и введите необходимые параметры именно там.

image

Тут мы ввели Логин, Пароль, строку соединения к базе, а также строку соединения к Storage Account. Последняя может быть описана с помощью специальной панели:

image

Тут Вы указываете имя созданного Storage аккаунта и ключ, который можно взять в панели управления Windows Azure. Не забудьте поменять HTTPS на HTTP. Наши сообщения не представляют особого интереса, поэтому лишняя безопасность тут не требуется.

Последний параметры NRecords определяет, сколько сообщений мы хотим возвращать на телефон. Мы могли бы отдать эту настройку и пользователю, но я все равно хочу ограничить их количество сверху.

А теперь приступаем к реализации кода.

Первый метод, позволяющий подписаться на сообщения, будет выглядеть следующим образом:

   1:  public void SubscribePhoneToService(Guid guid, string pushServiceUri)
   2:  {
   3:      SqlConnection conn = new SqlConnection(RoleEnvironment.GetConfigurationSettingValue("DatabaseConnection"));
   4:      SqlCommand comm = new SqlCommand("select count(*) from PhoneTable where PhoneID=@par1", conn);
   5:   
   6:      comm.Parameters.Add(new SqlParameter("par1", guid.ToString()));
   7:   
   8:      conn.Open();
   9:   
  10:      int res = (int)comm.ExecuteScalar();
  11:   
  12:      if (res > 0)
  13:      {
  14:          SqlCommand comm1 = new SqlCommand("update phonetable set PhoneUrl=@par2 where PhoneID=@par1", conn);
  15:          comm1.Parameters.Add(new SqlParameter("par1", guid.ToString()));
  16:          comm1.Parameters.Add(new SqlParameter("par2", pushServiceUri));
  17:          comm1.ExecuteNonQuery();
  18:      }
  19:      else
  20:      {
  21:          SqlCommand comm1 = new SqlCommand("insert phonetable (PhoneID, PhoneUrl) values (@par1, @par2)", conn);
  22:          comm1.Parameters.Add(new SqlParameter("par1", guid.ToString()));
  23:          comm1.Parameters.Add(new SqlParameter("par2", pushServiceUri));
  24:          comm1.ExecuteNonQuery();
  25:      }
  26:   
  27:      conn.Close();
  28:      conn.Dispose();
  29:  }

 

Фактически, мы связываемся с базой, чтобы проверить наличие устройства. Если устройства нет, то мы вносим его URL в базу, если оно есть, то обновляем URL (он может измениться). Интерес представляет класс RoleEnvironment, позволяющий получить доступ к настройкам роли.

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

   1:  SqlConnection conn = new SqlConnection(RoleEnvironment.GetConfigurationSettingValue("DatabaseConnection"));
   2:  string n = RoleEnvironment.GetConfigurationSettingValue("NRecords");
   3:   
   4:  SqlCommand comm = new SqlCommand("select TOP (" + n + ") * from messagetable order by messagedate desc", conn);
   5:   
   6:  conn.Open();
   7:   
   8:  SqlDataReader res = comm.ExecuteReader();
   9:   
  10:  List<SMSMessage> SMSMessages = new List<SMSMessage>();
  11:   
  12:  while (res.Read())
  13:  {
  14:      SMSMessages.Add(new SMSMessage() { MessageValue = res["MessageText"].ToString(), DateTimeValue = res["MessageDate"].ToString() });
  15:  }
  16:   
  17:  conn.Close();
  18:  conn.Dispose();
  19:   
  20:  return SMSMessages.ToArray();

 

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

   1:  if ((login.Equals(RoleEnvironment.GetConfigurationSettingValue("Login"))) && (password.Equals(RoleEnvironment.GetConfigurationSettingValue("Password"))))
   2:  {
   3:   
   4:      SqlConnection conn = new SqlConnection(RoleEnvironment.GetConfigurationSettingValue("DatabaseConnection"));
   5:      SqlCommand comm = new SqlCommand("insert messagetable (MessageText, MessageDate) values (@par1, @par2)", conn);
   6:      comm.Parameters.Add(new SqlParameter("par2", DateTime.Now.ToUniversalTime().ToString()));
   7:      comm.Parameters.Add(new SqlParameter("par1", sms));
   8:   
   9:      conn.Open();
  10:   
  11:      comm.ExecuteNonQuery();
  12:   
  13:      conn.Close();
  14:      conn.Dispose();
  15:   
  16:      CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
  17:      {
  18:          configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
  19:   
  20:          RoleEnvironment.Changed += (sender, arg) =>
  21:          {
  22:              if (arg.Changes.OfType<RoleEnvironmentConfigurationSettingChange>()
  23:                  .Any((change) => (change.ConfigurationSettingName == configName)))
  24:              {
  25:                  if (!configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)))
  26:                  {
  27:                      RoleEnvironment.RequestRecycle();
  28:                  }
  29:              }
  30:          };
  31:      });
  32:   
  33:      CloudStorageAccount acc = CloudStorageAccount.FromConfigurationSetting("StorageConnection");
  34:      CloudQueueClient queueClient = acc.CreateCloudQueueClient();
  35:   
  36:      CloudQueue queue = queueClient.GetQueueReference("smsqueue");
  37:      queue.CreateIfNotExist();
  38:   
  39:      queue.AddMessage(new CloudQueueMessage(sms));
  40:  }

 

Тут мы вставляем новое сообщение в нашу таблицу, а также размещаем его в очереди. Для работы очереди мы использовали управляемую библиотеку Microsoft.WindowsAzure.StorageClient.dll, которая облегчает нам работу с REST API. Итак, тут мы создаем объект типа CloudStorageAccount, который будет ассоциироваться с нашим Storage аккаунтом в Azure. После чего создаем объект типа CloudQueue, который будет ассоциироваться с нашей очередью. И с помощью метода CreateIfNotExist создает новую очередь или получаем ссылку на существующую. После этого можно добавлять элементы.

Теперь необходимо реализовать рабочую роль, добавить поддержку https и написать клиента. Но об этом в следующей статье.

Реклама

Written by Sergiy Baydachnyy

13.04.2011 в 12:12

Опубликовано в Windows Azure, Windows Phone

Tagged with , , ,

комментария 4

Subscribe to comments with RSS.

  1. Здравствуйте, что-то не нашел продолжения этой очень интересной статьи…

    Vyacheslav Zolotov

    04.11.2012 at 23:59

  2. Where To Get cheap Senators jersey

  3. Wholesale cheap custom sports jerseys online,Custom personalized jerseys cheap from china.

    upvdzai@gmail.com

    30.12.2014 at 02:45

  4. buy cheap nike nfl jerseys with paypal


Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: