company_banner

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


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

    С другой стороны, каждый владелец информационного ресурса, хочет, чтобы его ресурсом пользовались. Самый простой и распространённый вариант – это 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 на русском языке
    Microsoft
    199.77
    Microsoft — мировой лидер в области ПО и ИТ-услуг
    Share post

    Similar posts

    Comments 14

    • UFO just landed and posted this here
        +2
        Метод Close() вызывает Dispose(true)
          0
          А если произойдёт исключение? Без using ведь не будет гарантии, что Dispose будет вызван и все внутренние ресурсы будут освобождены.
            0
            Если посмотреть на мой код, то можно увидеть, что у меня нет обработки ошибок, кроме той, которая обусловлена функциональностью. Это одна из причин, по кторой я назвал это приложение «заготовкой».

            Если произойдёт необработанное исключние, в случае WP, говернёр ресурсов грохнет моё приложение и освободит все ресурсы.
          0
          И в .NET, и в Silverlight
            0
            Разные фреймворки же.
              0
              Не заметил сообщения выше, сори :)
            0
            Зачем вводить в заблуждение фразой: "… в 3 строчки"??? Такие же три строчки и на андройде можно нарулить.
              +3
              Я вижу 3 строчки, которые делаю всю работу с HTTP:

              WebClient client = new WebClient();
              client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
              client.DownloadStringAsync(new Uri(blogRSSURL));

              Не совсем понимаю, при чём здесь Android. Можно сделать на Android за 3 строчки — отлично.
              0
              Всё-таки меня вводит в недоумение гигантский бессмысленный заголовок ПОСТ, занимающий 1/4 экрана. Это, конечно, в духе WP, но как-то расточительно для экранов мобильных устройств.
                +1
                К такому дизайну достаточно быстро привыкаешь, хотя до того, как попользуешься могут возникать вопросы.

                Опять же если для вашего приложения это критично, можно сделать «авторский» дизайн, но нужно очень аккуратно это делать, чтобы пользователю приложения было удобно и привычно.
                –4
                Судя по коду (названия свойств в нижнем регистре, отсутствия ViewModel, ручное создание делегатов, var не используется) — автор недавно начал изучать c# и xaml?
                Признавайтесь, месяц уже есть?
                  +4
                  Задача данной статьи в том, чтобы продемонстрировать возможности платформы для разработчика в максимально простой форме.

                  Я не считаю нужным для приложения из 2-х форм с простой логикой, использовать MVVM. По моему опыту это приводит к тому, что вместо того, чтобы рассказывать о том, что я хочу рассказать, я начинаю рассказывать о MVVM.

                  Я жёсткий противник использовании в примерах разнообразных «правильных» паттернов и «радостей программиста». Пример должен в доступной форме показать возможности использования и должен вызывать минимум вопросов.

                  В результате, я стараюсь не использовать MVVM и var во всех своих примерах. Я считаю, что это ухудшает читаемость кода, а в случае примера, это гораздо важнее, чем «радости программиста».

                  В завершение, отвечу на ваши вопросы.

                  Изучать C# я начал в момент его появления. XAML, конечно, не в момент его появления, а попозже, на платформе Windows Embedded CE.

                  Да, пожалуй, месяц прошёл уже.

                  Ещё маленькие буквы. Эта страшная привычка осталась у меня от С\С++
                    +2
                    Знаете, на самом деле всё, что вы указали — правильно и логично.
                    Но всё же, читая название поста и сам пост в целом, я, к примеру, и не ожидал, что автор всё это учтёт.
                    Даже напротив, мне как новичку в C# в целом и для WP7 в частности, и хотелось что бы автор, не погружаясь глубоко, показал простой способ того, как использовать «HTTP в 3 строчки»,«WebClient» и «WebBrowser» для написания простенького RSS — ридера.
                    Спасибо автору, с удовольствием прочитал пост и с удовольствием напишу (попрактиковаться) почти тоже самое, но с блекджеком (всё то, что описал LexL) и шл…

                  Only users with full accounts can post comments. Log in, please.