Pull to refresh
0
Microsoft
Microsoft — мировой лидер в области ПО и ИТ-услуг

Работа с HTTP в 3 строчки, или делаем свой RSS Reader c WebClient и WebBrowser

Reading time6 min
Views5.3K

В мире мобильных устройств балом правит контент. Каждый пользователь смартфона хочет на своём устройстве получить доступ к необходимой ему информации быстро, красиво и, желательно, с экономией трафика.

С другой стороны, каждый владелец информационного ресурса, хочет, чтобы его ресурсом пользовались. Самый простой и распространённый вариант – это RSS ленты. И разнообразные «читалки» RSS доступны на всех мобильных платформах. Даже есть сервис, который может по RSS или ATOM источнику сгенерировать вам приложение для некоторых мобильных платформ, что позволяет веб-мастеру или владельцу ресурса быстро получить доступ к армии пользователей мобильных устройств.

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

Итак, исходная задача. Написать RSS Reader для ленты RSS блога (в примере будет мой блог на MSDN), который будет показывать список постов и отображать содержание поста. Собственно – это всё. Вполне себе работоспособная заготовка для будущего продвинутого RSS Reader.

Начнём с анализа задачи и доступных нам ресурсов. Итак, нам нужно скачать RSS ленту (XML), разобрать её, показать в виде списка и по выбору названия поста – отобразить его (HTML).

С простыми задачами работы с HTTP отлично справляется WebClient. Список нам поможет отобразить ListBox. Разобрать XML в список объектов нам поможет LINQ to XML. Связать список объектов с ListBox – DataBinding, красиво отобразить – DataTemplate. Осталось отображение HTML. Выбор прост — воспользуемся возможностями встроенного элемента управления WebBrowser. А чтобы удобно отображать содержимое поста в WebBrowser, при переходе от списка, будем его сохранять в файл с определенным названием на IsolatedStorage.

Ну что ж, на этом можно было бы и закончить, но давайте попробуем реализовать все наши идеи в коде.

Как обычно, создадим новый проект на базе шаблона Windows Phone Application. Сразу же добавим к нему ссылку (Reference) на System.Xml.Linq, а в блок using следующие записи:
using System.Xml.Linq;

using System.IO;
using System.IO.IsolatedStorage;

Первое пространство имён нам нужно, чтобы удобно разобрать полученный XML в список объектов, а два остальных, чтобы работать с IsolatedStorage и StreamWriter.

Определимся RSS ленту какого блога мы будем отображать и как будет называться файл, содержащий текст выбранного поста. Я буду использовать свой блог и незамысловато назову файл post.html:
const string blogRSSURL = "http://blogs.msdn.com/b/stasus/rss.aspx";
public static string postFileName = "post.html"
bool isPageNew = false;

Чтобы получить доступ к имени файла со второй страницы, на которой я планирую отображать выбранный пост, я объявляю имя файла public static. Флаг isPageNew будет использоваться, чтобы определить, вызвался ли конструктор (т.е. создавалась ли страница) или она уже создана. Это нам пригодится для того, чтобы оптимизировать обращения на сервер, а также корректно обрабатывать выходы из Dormant и Tumbstone состояний.

Чтобы разбирать XML в список объектов, нужно определить объект. Добавим в проект файл класса с названием PostMessage.cs, который будет содержать минимальный набор полей:
public class PostMessage
    {
        public DateTime pubDate { get; set; }
        public string title { get; set; }
        public string link { get; set; }
        public string description { get; set; }
    }

Перейдём к странице MainPage.xaml, дадим название приложению и странице, а также добавим ListBox с привязкой к данным, шаблоном и обработчиком события изменения выбранного элемента:
<!--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="БЛОГ СТАСА ПАВЛОВА" 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">
    <ListBox Name="PostList" SelectionChanged="PostList_SelectionChanged">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock Text="{Binding pubDate, ConverterCulture=ru-RU, StringFormat=D}" FontSize="20" Foreground="Coral"/>
                    <TextBlock Text="{Binding title}" TextWrapping="Wrap" FontSize="22"/>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

Обратите внимание, я использую форматирование, представляя дату в необходимом мне виде при выводе в ListBox.

Теперь у нас всё готово и мы можем после загрузке страницы запрашивать RSS и разбирать его в список объектов.
// Constructor
public MainPage()
{
    InitializeComponent();
    isPageNew = true;
    Loaded += new RoutedEventHandler(MainPage_Loaded);
}

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    if (isPageNew)
    { 
        WebClient client = new WebClient();
        client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
        client.DownloadStringAsync(new Uri(blogRSSURL));
        isPageNew = false;                    
    }
}

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

void ParseRSSAndBindData(string RSSText)
{
    XElement rssElements = XElement.Parse(RSSText);

    var blogPosts =
        from post in rssElements.Descendants("item")
        select new PostMessage
        {
            title = post.Element("title").Value,
            pubDate = DateTime.Parse(post.Element("pubDate").Value),
            link = post.Element("link").Value,
            description = post.Element("description").Value
        };

    PostList.ItemsSource = blogPosts;
}

Обратите внимание, что RSS скачивается только тогда, когда у нас вызывался конструктор. Это означает, что при выходе из Dormant состояния мы не будем скачивать RSS, а при выходе из Tumbstoned – будем. Также это гарантирует нам, что при переходе со страницы просмотра поста обратно к списку, мы не будем заново запрашивать RSS, что произошло бы, поскольку и в этом случае событие Loaded происходит.

В данном примере мы используем самый простой вариант использования WebClient, просим скачать асинхронно контент в виде строки методом GET c указанного URI и обрабатываем завершение, преобразуя полученный RSS XML в список объектов.

Для того, чтобы отображать пост, добавим в проект страницу PostPage.xaml, поменяем на ней название приложения и страницы и добавим элемент управления WebBrowser:
<!--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="БЛОГ СТАСА ПАВЛОВА" 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">
    <phone:WebBrowser Name="blogPost"/>
</Grid>

Вернёмся к коду страницы MainPage.xaml.cs и добавим обработчик изменения выбора в ListBox:
private void PostList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (e.AddedItems.Count > 0)
    { 
            
        IsolatedStorageFile appStorage = IsolatedStorageFile.GetUserStoreForApplication();
        IsolatedStorageFileStream postFileStrieam = appStorage.CreateFile(MainPage.postFileName);

            
        StreamWriter sw = new StreamWriter(postFileStrieam);
        sw.WriteLine("<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>");
        sw.Write(((PostMessage)e.AddedItems[0]).description);
        sw.WriteLine("</body></html>");
        sw.Close();
            
        postFileStrieam.Close();
 
        NavigationService.Navigate(new Uri("/PostPage.xaml", UriKind.RelativeOrAbsolute));
    }
}

Итак, в обработчике мы проверяем, что действительно что-то выбрано. В настройках ListBox у нас разрешено выбирать только один элемент (это настройки по умолчанию), так что если выбран, только один. Далее, кастуем выбраный элемент в PostMessage, берём у него текст поста и сохраняем его в файл, обернув парой строчек, указывая тип контента и кодировку.

Теперь осталось отобразить этот файл при открытии второй страницы:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    blogPost.Navigate(new Uri(MainPage.postFileName, UriKind.RelativeOrAbsolute));
}

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

Можете самостоятельно добавить:
  • сохранение RSS в IsolatedStorage и отображение сохранённого RSS при старте приложения;
  • добавить ProgressBar при загрузке RSS;
  • не обновлять автоматически, а запрашивать пользователя;
  • добавить Background Agent для автоматического обновления;
  • добавить Live Tile для отображения последней новости …

Дальнейшее улучшения ограничены только вашей фантазией.

В заключение, хочу заметить, что как я и обещал, работа с HTTP заняла в коде 3 строчки.

UPD: Код проекта в архиве

Полезные ссылки:
Live Tiles и фоновые агенты
Центр разработки Windows Phone на MSDN (курс по Windows Phone)
Windows Phone SDK 7.1
Форумы по разработке под Windows Phone на русском языке
Tags:
Hubs:
+15
Comments14

Articles

Change theme settings

Information

Website
www.microsoft.com
Registered
Founded
Employees
Unknown
Location
США