Разработка под Windows Phone: Часть 4: Локальное хранение данных и работа с HTTP

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

На примере простого приложения мы научимся получать данные по протоколу HTTP и, при необходимости, сохранять их на устройстве.

Работа с  HTTP и простой клиент для чтения RSS

Разработаем простой клиент для чтения RSS, который будет при запуске обращаться по определенному адресу, считывать RSS ленту и представлять её заголовки пользователю. Для простоты мы закодируем ссылку на RSS ленту прямо в код, в качестве примера ленты в нашем приложении будет использоваться RSS лента русского блога MSDN: https://blogs.msdn.com/b/rudevnews/rss.aspx

Наш клиент будет отображать только заголовки с возможностью перейти по ссылке на новость, так что разработку начнём со стандартного шаблона Windows Phone Application и назовём приложение SimpleRussianRSSReader.

Сначала научимся получать данные, а потом перейдем к их отображению.

Добавим в код константу – URL, указывающий на RSS:

const string RSS = "https://blogs.msdn.com/b/rudevnews/rss.aspx";

Теперь нам надо обратиться по указному адресу и скачать ленту.

Разработчику доступны два API: WebClient и HttpWebRequest. WebClient API позволяет удобно работать со GET/POST запросами, HttpWebRequest позволяет использоваться методы PUT/DELETE и даёт больше контроля разработчику над параметрами запроса. Поскольку нам надо просто скачать документ с веба, мы будем использовать WebClient.

Напишем простую функцию, в которой создадим экземпляр класса, зарегистрируемся на окончание процесса скачивания и запустим асинхронную процедуру скачивания документа:

private void LoadRSS()
{
     WebClient client = new WebClient();
     client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
      client.DownloadStringAsync(new Uri(RSS));        
}

Добавим глобальную строковую переменную в которой сохраним результат запроса:

  string RSSString = ""; 

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

void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
     if (e.Error == null)
     { 
          RSSString = e.Result;
     }
 }

Чтобы проверить работоспособность кода на этом этапе, добавим элемент управления TextBlock на страницу приложения, заодно отредактировав его название, так что XAML код будет выглядеть следующим образом:

<!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="РУССКИЙ MSDN" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="новости" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>
 
        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <TextBlock Name="RSSText" ></TextBlock>            
        </Grid>

В конструктор класса добавим вызов функции LoadRSS, а в обработчик загрузки, присвоение свойству Text элемента управления RSSText результата полученного из веб.

Запустите приложение (F5) и убедитесь, что мы получаем ответ от сервера.

Собственно мы научились основам работы с HTTP. Тот же самый запрос, реализованный через HttpWebRequest потребует значительных усилий, написания callback функции и определения множества дополнительных переменных.

Чтобы получить более красивый интерфейс отображения мы немного забежим вперёд. Настойчивый читатель сможет при желании разобраться с

Удалим TextBloсk и добавим ListBox с шаблоном и привязкой к данным.

<!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <ListBox Name="RssList">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <TextBlock Text="{Binding pubDate}" FontSize="20" Foreground="Coral"/>
                            <TextBlock Text="{Binding title}" TextWrapping="Wrap" FontSize="22"/>
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>

Теперь нужно создать класс, который будет содержать свойства  pubDate и title. Список экземпляров этого класса мы получим разобрав при помощи LINQ полученный XML.

Добавьте в решение класс с именем PostMessage и определите в нем свойства pubDate и title типа строка:

public class PostMessage
    {
        public string pubDate { get; set; }
 
        public string title { get; set; }        
    }

Теперь добавьте в решение ссылку на библиотеку System.Xml.Linq  и соответствующую директиву using в файл MainPage.xaml.cs

using System.Xml.Linq; 

Теперь в обработчике результатов запроса мы можем обработать полученные результаты, проинициализировать список экземпляров класса и связать его с ListBox, чтобы отобразить в интерфейсе пользователя.

XElement twitterElements = XElement.Parse(e.Result);
 
                var postList =
                    from tweet in twitterElements.Descendants("item")
                    select new PostMessage
                    {
                        title = tweet.Element("title").Value,
                        pubDate = tweet.Element("pubDate").Value
                    };
 
                RssList.ItemsSource = postList;

Запустите приложение (F5) и проверьте, как работает наш простой клиент RSS.

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

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

Добавим в класс PostMessage свойство link:

public class PostMessage
    {
        public string pubDate { get; set; }
 
        public string title { get; set; }
 
        public string link { get; set; }
    }

При разборе RSS добавим инициализацию этого поля:

var postList =
                    from tweet in twitterElements.Descendants("item")
                    select new PostMessage
                    {
                        title = tweet.Element("title").Value,
                        pubDate = tweet.Element("pubDate").Value,
                        link = tweet.Element("link").Value
                    };

В XAML файле MainPage назначим обработчик события SelectionChanged и напишем следующий код в этом обработчике:

private void RssList_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            WebBrowserTask webTask = new WebBrowserTask();
            webTask.Uri = new Uri(((PostMessage)(RssList.SelectedItem)).link);
            webTask.Show();
        }

Не забудьте добавить в секцию using следующую директиву:

using Microsoft.Phone.Tasks; 

Запустите приложение (F5) и проверьте, как оно работает.

Локальное хранение данных

На платформе Windows Phone приложение может хранить данные тремя способами:

  • настройки: данные сохраняются как пары ключ/значение, используется класс IsolatedStorageSettings;
  • файлы и папки сохраняются на устройстве с использованием класса IsolatedStorageFile;
  • реляционные данные сохраняются в локальной базе данных с использованием технологии LINQ в SQL.

Изолированное хранилище для настроек

Если говорить о настройках, то одна из основных возможностей изолированного хранилища для настроек -  это его автоматическое сохранения при выходе пользователя из приложения.

Предназначено собственно для хранения настроек приложения, один экземпляр для одного приложения, создаётся при первом обращении.

Изолированное хранилище для файлов и папок

Чтобы разобраться, с основами работы с изолированным хранилищем, добавим в наше приложение возможность сохранять полученную RSS ленту между запусками  и запрашивать сервер тогда, когда у нас лента не загружена или пользователь попросил обновить ленту.

Как обычно, сначала добавим в секцию using дополнительную директивы:

using System.IO.IsolatedStorage; 
FakePre-646bb7b7739d4573a010e760af194e76-e2755c97e5cf4d959c9185df053a6d12

Добавим функции сохранения и считывания файла. Для этого сначала определим константу с именем файла:

const string RSSFileName = "rss.xml"; 

Далее определим функции считывания и записи файла в строку из изолированного хранилища:

void SaveRSSToIsolatedStorage(string RSSText)
        {
            IsolatedStorageFile rssFileStorage = IsolatedStorageFile.GetUserStoreForApplication();
            IsolatedStorageFileStream rssFileStream = rssFileStorage.CreateFile(RSSFileName);
            
            StreamWriter sw = new StreamWriter(rssFileStream);
            sw.Write(RSSText);
            sw.Close();
            
            rssFileStream.Close();
        }

 
        string LoadRSSFromIsolatedStorage()
        {
            IsolatedStorageFile rssFileStorage = IsolatedStorageFile.GetUserStoreForApplication();
            IsolatedStorageFileStream rssFileStream = rssFileStorage.OpenFile(RSSFileName, System.IO.FileMode.Open);
            
            StreamReader sr = new StreamReader(rssFileStream);
            string RSS = sr.ReadToEnd();
            sr.Close();
            rssFileStream.Close();
 
            return RSS;
        }

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

bool IsRSSExist()
        {
            IsolatedStorageFile rssFileStorage = IsolatedStorageFile.GetUserStoreForApplication();
            return rssFileStorage.FileExists(RSSFileName);
        }

Теперь выделим разбор полученного результата и связывание данных в отдельную функцию:

void ParseRSSAndBindData(string RSSText)
        {
            XElement twitterElements = XElement.Parse(RSSText);
 
            var postList =
                from tweet in twitterElements.Descendants("item")
                select new PostMessage
                {
                    title = tweet.Element("title").Value,
                    pubDate = tweet.Element("pubDate").Value,
                    link = tweet.Element("link").Value
                };
 
            RssList.ItemsSource = postList;
        }

И добавим сохранение полученного результата в обработчик завершения загрузки:

void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            if (e.Error == null)
            { 
                RSSString = e.Result;
 
                ParseRSSAndBindData(RSSString);
 
                SaveRSSToIsolatedStorage(RSSString);
            }
        }

Осталось модифицировать функцию LoadRSS():

private void LoadRSS()
        {
            if (IsRSSExist())
            {
                RSSString = LoadRSSFromIsolatedStorage();
                ParseRSSAndBindData(RSSString);
            }
            else
            {
                RequestRSS();            
            }
        }

Где RequestRSS() – это наша старая функция LoadRSS():

private void RequestRSS()
        {
            WebClient client = new WebClient();
            client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
            client.DownloadStringAsync(new Uri(RSS));
 
        }

Запустите программу под отладчиком несколько раз и убедитесь, что после первого запуска программа сохраняет результат в изолированное хранилище и больше не обращается к вебу.

Самостоятельно измените интерфейс, добавив кнопку для обновления и дописав необходимый код.

Локальная база данных

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

Для того, чтобы им воспользоваться необходимо добавить в решение ссылку на библиотеку System.Data.Linq, а также добавить блок using следующие  директивы:

using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.ComponentModel;
using System.Collections.ObjectModel;

Далее необходимо определить классы, которое будут представлять сущности для хранения в локальной базе, отаттрибутировать их соответсвующим образом ([Table] и [Column] с параметрами) и реализовать 2 интерфейса INotifyPropertyChanged, INotifyPropertyChanging, чтобы минимизировать использование памяти.

Затем необходимо определить свой класс контекста данных, унаследованный от DataContext и определить в нем таблицы. Это создаст базовую инфраструктуру для использования локальной базы данных на устройстве.

Полный разбор работы с базой данных выходит за рамки этой статьи.

Для дальнейшего изучения работы с базой данных, можно посмотреть простой пошаговый пример создания приложения для работы с базой данных можно посмотреть здесь: https://msdn.microsoft.com/en-us/library/hh202876(v=VS.92).aspx

Более сложный пример приложения, работающего с локальной базой данных и сделанного в паттерне MVVM можно скачать здесь: https://go.microsoft.com/fwlink/?LinkId=219066 , краткое пояснение к проекту доступно по следующей ссылке https://msdn.microsoft.com/en-us/library/hh286405(v=VS.92).aspx

Итоги и следующие шаги

Итак, мы познакомились с тем, как получать данные из веб и сохранять их локально в файл. Также мы узнали, что существует локальная база данных, доступ к которой реализован аналогично работе с Entity Framework с поправкой на особенности платформы.

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

Файлы для загрузки

Проект SimpleRussianRSSReader
Проект SimpleRussianRSSReader с поддержкой Isolated Storage