[WP8] Xây dựng ứng dụng đọc tin RSS

(Tạp chí Lập trình) – Trong bài viết này tôi sẽ hướng dẫn các bạn xây dựng một ứng dụng đọc tin RSS. Nếu bạn còn chưa quen thuộc với RSS thì xin đọc qua ở đây trước khi chúng ta tiếp tục với bài viết.

Khi xây dựng ứng dụng đọc tin RSS, chúng ta sẽ sử dụng những kỹ thuật sau:

  1. Thiết kế giao diện và xử lý sự kiện với XAML và C#.
  2. Kết nối internet để tải về tập tin RSS.
  3. Sử dụng XML Serializer để chuyển từ dữ liệu XML sang các đối tượng trong C#.
  4. Sử dụng Data Binding để gắn dữ liệu lên các thành phần của XAML.
  5. Dùng dịch vụ NavigationService để di chuyển giữa các trang của ứng dụng.

Cấu trúc file RSS

Chúng ta thử xem qua một file RSS mẫu để biết được cấu trúc chính của nó (ở đây ta đang nói đến phiên bản 2.0):

<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/" xmlns:bc="http://www.brightcove.tv/link" xmlns:atom="http://www.w3.org/2005/Atom" xml:base="http://www.goupstate.com" xmlns:xlink="http://www.w3.org/1999/xlink">
	<channel>
	<title>Local News from Spartanburg Herald Journal</title>
	<link>http://www.goupstate.com</link>
	<description>Local News from Spartanburg Herald Journal</description>
	<item>
		<title><![CDATA[Spartanburg Mayor Junie White files for re-election ]]></title>
		<link><![CDATA[http://www.goupstate.com/article/20130508/articles/130509720]]></link>
		<description><![CDATA[
		Spartanburg Mayor Junie White filed to run for re-election this afternoon.
		]]></description>
	</item>
	<item>
		<title><![CDATA[Chapman goes to court after softball protest denied]]></title>
		<link><![CDATA[http://preps.goupstate.com/news/article/45809/chapman-goes-to-court-to-after-softball-protest-denied/]]></link>
		<description><![CDATA[
		Chapman High School and Spartanburg School District 1 have petitioned the 7th Judicial Circuit for a temporary restraining order and declaratory relief after the South Carolina High School League denied a protest to allow the completion of the Panthers' road game at Daniel in the 3A upper state softball playoffs.
		]]></description>
	</item>
	</channel>
</rss>

Ta nhận thấy rằng một file rss thực ra là một tài liệu xml với thẻ root ngoài cùng là <rss>. Thẻ <rss> chỉ có một thẻ con duy nhất đó là thẻ <channel> đại diện cho một kênh cung cấp tin rss, ta sẽ liệt kê các thẻ con của thẻ <channel> này.

Thẻ <channel> có các thẻ con bắt buộc là:

<title>: Tiêu đề của kênh tin tức.

<link>: Đường dẫn đến trang web có chứa rss này.

<description>: Mô tả của kênh tin tức.

Một thẻ <channel> có thể chứa nhiều thẻ con <item>, mỗi thẻ <item> đại diện cho một tin tức của trang web đó. Tất cả các thẻ con của thẻ <item> đều là tùy chọn (không bắt buộc), tuy nhiên, thông thường thì nó chứa các thành phần sau:

<title>: Tiêu đề của tin tức.

<link>: Đường dẫn đến tin tức.

<description>: Đoạn tóm tắt của tin tức.

Ngoài những thẻ mà chúng ta đã liệt kê ở trên thì <channel><item> còn có thể có thêm nhiều thẻ tùy chọn khác nữa, các bạn hãy ghé qua trang này để biết thêm chi tiết về các thẻ được dùng trong rss.

Các lớp ViewModel

Với cấu trúc của một file rss như đã phân tích ở trên, tôi đã xây dựng các lớp ViewModel như sau:

Lưu ý là tôi có gắn thêm attribute cho các thuộc tính của mỗi lớp này nhằm phục vụ cho việc Serialize sau này, mã nguồn chi tiết các bạn có thể tải về ở cuối bài viết.

Tải dữ liệu về

Để tải về file RSS từ một nguồn trên mạng thì tôi tạo ra lớp RssLoader với phương thức LoadRss().

public class RssLoader
{
	public static string RSS_URI = "http://tapchilaptrinh.vn/feed/";
	public event Action<RssModel> RssLoaded;
	public void LoadRss()
	{
		WebRequest request = WebRequest.Create(RSS_URI);
		request.BeginGetResponse(DownloadCompleted, request);
	}
	private void DownloadCompleted(IAsyncResult result) {
		WebRequest request = (WebRequest)result.AsyncState;
		WebResponse response = request.EndGetResponse(result);
		using (Stream stream = response.GetResponseStream()) {
			XmlSerializer serializer = new XmlSerializer(typeof(RssModel));
			RssModel rss = (RssModel)serializer.Deserialize(stream);
			if (RssLoaded != null) {
				RssLoaded(rss);
			}
		}
	}
}

Có một lưu ý ở đây đó là trong .NET 4.5 đã được đưa vào mô hình lập trình bất đồng bộ, và do đó trong trường hợp này chúng ta cũng tiến hành việc tải dữ liệu bất đồng bộ. Tiếp theo chúng ta sử dụng XmlSerializer để chuyển dữ liệu từ xml thành các đối tượng tương ứng trong C#. Event RssLoaded là nhằm xử lý một RssModel khi việc tải dữ liệu về hoàn tất và đối tượng RssModel được xây dựng xong, sau này chúng ta sẽ sử dụng event này để đưa RssModel này lên giao diện của ứng dụng.

Thiết kế giao diện

Chúng ta sẽ thiết kế giao diện trang chính của ứng dụng với bố cục như sau:

Chúng ta sử dụng TextBlock để hiển thị tên ứng dụng, tiêu đề của trang RSS và mô tả của nó.

Còn đối với danh sách bài viết thì chúng ta sử dụng thành phần LongListSelector, trong đó mỗi item của nó sẽ là một TextBlock để hiển thị tiêu đề của mỗi bài viết. Các bạn hãy tùy chỉnh bố cục của mỗi item này theo cách mình mong muốn. Có thể cho hiển thị thêm phần description của bài viết, hoặc thậm chí ảnh đại diện của bài viết. Lưu ý rằng tất cả các thẻ này đều là tùy chọn nên chỉ có thể sử dụng với RSS của một số trang mà bạn đã kiểm chứng.

Khi người dùng chọn vào một tiêu đề bài viết thì nó sẽ chuyển sang trang đọc tin tức, ở trang này chúng ta sẽ nhúng vào một trình duyệt (WebBrowser).

Tất cả các mã để tạo giao diện các bạn có thể tham khảo ở file mã nguồn.

Data Binding

Chúng ta quan sát một đoạn mã của trang MainPage.xaml:

<phone:LongListSelector x:Name="lstRssItems" Margin="0" ItemsSource="{Binding Channel.Items}" SelectionChanged="lstRssItems_SelectionChanged">
	<phone:LongListSelector.Resources>
		<DataTemplate x:Key="RssItemTemplate">
			<Grid Margin="0,0,0,15">
				<TextBlock Margin="10,10,0,0" TextWrapping="Wrap" Text="{Binding Title}" VerticalAlignment="Top"/>
			</Grid>
		</DataTemplate>
	</phone:LongListSelector.Resources>
	<phone:LongListSelector.ItemTemplate>
		<StaticResource ResourceKey="RssItemTemplate"/>
	</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>

Ở đây chúng ta gắn thuộc tính ItemsSource của LongListSelector với thuộc tính Items (Items chính là danh sách các bài viết mà mình tải về). Mỗi item của LongListSelector thì có một TextBlock với thuộc tính Text được gắn với thuộc tính Title (Title là tiêu đề của bài viết).

Code Behind

Chúng ta sẽ xét qua hai phương thức OnNavigatedTo()lstRssItems_SelectionChanged() của Lớp MainPage (nằm trong file MainPage.xaml.cs):

protected override void OnNavigatedTo(NavigationEventArgs e)
{
	if (App.RssModel == null) {
		App.LoadRssModel();
	}
}

private void lstRssItems_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
	if (lstRssItems.SelectedItem != null) {
		RssItemModel item = (RssItemModel)lstRssItems.SelectedItem;
		NavigationService.Navigate(new Uri(string.Format("/NewsPage.xaml?link={0}",item.Link), UriKind.Relative));
	}
}

Phương thức OnNavigatedTo() sẽ được gọi khi chúng ta đi đến trang MainPage.xaml, do đó ở đây chúng ta bắt đầu tải dữ liệu về. Phương thức lstRssItems_SelectionChanged() sẽ được gọi khi người dùng chọn vào tiêu đề của một bài viết, do đó chúng ta sẽ chuyển đến trang đọc tin bằng cách sử dụng phương thức Navigate() của lớp NavigationService.

Trang đọc tin

Mã tạo giao diện của trang đọc tin (NewsPage.xaml) rất đơn giản, bởi vì ở đây chúng ta chỉ nhúng vào một trình duyệt với đoạn mã sau:

<phone:WebBrowser x:Name="browserNews" Margin="0" Grid.Row="1" LoadCompleted="browserNews_LoadCompleted" IsScriptEnabled="True"/>

Ở phần code behind của trang này (lớp NewsPage) chúng ta có phương thức
OnNavigatedTo()
được dùng để tải về trang web tương ứng với đường link mà ta nhận được:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
	String link = NavigationContext.QueryString["link"];
	browserNews.Navigate(new Uri(link));
}

Mở rộng

Hãy mở rộng ứng dụng này bằng cách đưa thêm nhiều tính năng mới vào cho nó, tôi có thể gợi ý cho bạn một số tính năng như sau:

  • Hiển thị ảnh của bài viết trong danh sách.
  • Tổng hợp tin từ nhiều nguồn khác nhau chứ không chỉ từ TCLT.
  • Cho phép người dùng chọn các kênh RSS mà họ muốn.
  • Chia các kênh RSS vào từng chủ đề khác nhau như: Thể thao, kinh tế, Âm nhạc…
  • Tính năng đọc tin offline bằng cách lưu các tin lại.
  • Tích hợp thêm các hiệu ứng chuyển trang…

Vậy là chúng ta đã hoàn thành xong một bài tập đơn giản, hy vọng rằng các bạn sẽ tạo được ứng dụng RSS như mình mong muốn. Hãy chia sẻ với mọi người kết quả làm việc của mình nhé.
Tải mã nguồn: Download

Nguyễn Khắc Nhật

17 comments on “[WP8] Xây dựng ứng dụng đọc tin RSS

  1. a Việt cho e hỏi, Ở Mainpage, e muốn load ‘danh sách các bài viết mà mình tải về’ vào một RssModel đã rồi mới show từng tin một thông qua 1 button click thì làm sao ạ? E chưa hiểu cái Action lắm

  2. Hi anh Nhật, a cho e hỏi e áp dụng bài của anh vào trường hợp của e, nhưng khác là e load các item vào các textblock để show lên ui. A cho e hỏi là cách load Action vào 1 object RssModel ngay khi đến trang MainPage để e có thể get các item ra ạ?

    • Thực sự là tớ chưa hiểu câu hỏi của bạn Giang lắm. Bạn thử nói rõ hơn một chút nhé, để xem thử mình có giúp được bạn không.
      “load các item vào các textblock để show lên u”: cái này thì trong ví dụ của mình cũng làm thế mà.
      “load Action vào 1 object RssModel”: cái này thì không hiểu ý muốn nói gì luôn.

  3. Có một câu hỏi rất mong bạn giúp đỡ:
    Hiện tại tôi đang xây dựng ứng dụng đọc truyện Offline trên windows phone
    Tôi thiết kế một API cho phép lấy nội dung truyện từ server, sau đó gọi API từ ứng dụng windows phone và lưu trữ vào SD card
    Tiếp đến là load ứng dụng và đọc truyện
    Nhưng hiện tại đang bí quá, bạn có thể viết một Tut về ứng dụng như vậy được không?
    Cụ thể như sau:
    Ví dụ: Mình lấy truyện từ site này http://ebt.vn
    Và tôi có một ứng dụng đặt tại blog dạng để viết API lấy truyện: http://blog.com/get_story.php?id=xxx
    trong đó xxx là ID truyện trên ebt.vn
    Bây giờ muốn viết ứng dụng trên windows phone cho phép lấy truyện về máy và đọc offline trên chương trình của mình
    Mogn bạn giúp đỡ!

    • Việc viết một hướng dẫn như yêu cầu của bạn thì quả là khó (về mặt thời gian) để mình đáp ứng được. Bạn có thể tự mình thiết kế và phát triển từng phần một, nếu gặp khó ở chỗ nào thì có thể trao đổi thêm với mình nhé.

  4. Bạn có thể hướng dẫn luôn phần load nhiều RSS được không bạn, cụ thể là mình sử dụng Panorama control, khi load app lên mình muốn mỗi panorama item sẽ load đồng thời 1 rss riêng, mình làm mãi mà không được.!! Mong bạn giúp đỡ…

    • Để làm như bạn nói thì chỉ cần cho mỗi PanoramaItem tương ứng với một RssChanelItem là được, điều này không khó đâu.
      Hơn nữa, mình thấy trong trường hợp này nếu bạn sử dụng Pivot thì có vẻ hợp lý hơn.
      Bạn có thể mô tả thiết kế của bạn (“mãi mà không được”) để mình góp ý thêm nhé.

  5. cho em hỏi khi chạy thử ứng dụng của anh nó báo lỗi
    “An exception of type ‘System.Net.WebException’ occurred in System.Windows.ni.dll but was not handled in user code

    Additional information: The remote server returned an error: NotFound.”

    và nó dừng ở dòng” WebResponse response = request.EndGetResponse(result); ” trong đoạn:

    “private void DownloadCompleted(IAsyncResult result) {
    WebRequest request = (WebRequest)result.AsyncState;
    WebResponse response = request.EndGetResponse(result);
    using (Stream stream = response.GetResponseStream()) {
    XmlSerializer serializer = new XmlSerializer(typeof(RssModel));
    RssModel rss = (RssModel)serializer.Deserialize(stream);
    if (RssLoaded != null) {
    RssLoaded(rss);
    }
    }
    }
    Anh có thể giải thích hộ em tại sao và cách sửa như thế nào được không ạ? em dùng vs2013 mới tập tọe học

    • Hi bạn,
      Thông báo lỗi như trên là do không tải được dữ liệu về. Bạn kiểm tra lại xem emulator của mình có kết nối được vào internet không nhé (bằng cách vào trình duyệt của emulator và thử vào một trang web bất kỳ). Nếu đã có kết nối đến internet mà vẫn bị lỗi thì bạn hãy thay đường dẫn “http://tapchilaptrinh.wordpress.com/” bằng đường dẫn “http://tapchilaptrinh.vn/feed/”. Nếu được thì phản hồi lại cho mình biết với nhé.

  6. Anh cho em hỏi là em muốn lấy 1 phần nội dung(VD: phần play nhạc và lời bài hát ) của trang web đó thì phải làm như thế ,em có dùng webservice trên web rồi còn WP8 em chưa hiểu cách nó hoạt động,mong anh chỉ bảo
    em Cám Ơn Anh

    • Hi bạn,
      Mình đọc câu hỏi của bạn xong vẫn chưa hiểu ý của bạn lắm, bạn thử giải thích rõ hơn về tình huống của mình được không.
      Còn nếu muốn lấy một phần của trang web nào đó thì có nhiều cách, cho dù là làm Web hay làm ứng dụng WP thì cũng không khác nhau nhiều đâu. Bạn thử tìm thêm với từ khóa “Web crawler” và “Web data extraction” để tìm hiểu thêm nhé. Có gì bạn trao đổi thêm nhé.

      • Đầu tiên thì em xin cảm ơn anh,tiếp đến em cũng xin được hỏi là
        Em đang học Lập Trình Di Động” Với ứng dụng đọc tin từ RSS khi nó navigate tới trang Details thì em chỉ muốn đọc nội dung thôi chứ không cần phải tải hết nguyên trang web đó về,không biết trong LTDĐ mình có thể làm được không.
        Mong anh và các bạn chỉ bảo,Em cảm ơn

  7. Em sử dụng lấy nội dung về nó bị null mong thầy giải đáp

    [XmlElement(“content”, Namespace = “http://purl.org/rss/1.0/modules/content/”)]
    public string Content { get; set; }
    hoặc
    [XmlElement(“content:encoded”, Namespace = “http://purl.org/rss/1.0/modules/content/”)]
    public string Content { get; set; }

    hoặc
    [XmlElement(“content:encoded”, Namespace = “content”)]
    public string Content { get; set; }

    cảm ơn thầy !

  8. Chào thầy, Sao cái link trong trang RSS của em lúc mở bằng máy tính thì ok nhưng mở trong window phone lại không được

  9. Pingback: link code in Dec 15 | AUDI PROGRAMMING

Leave a Reply

Your email address will not be published. Required fields are marked *