Twitter style paging with ASP.NET MVC and jQuery


 

I really like the simplicity of the AJAX paging at Twitter so I decided to use the same type of paging, including a similar more button, on the start page on this site. It actually surprised me how simple it was to build it, including fallback for visitors that doesn’t have javascript enabled (such as our dear friend Google), with ASP.NET MVC and a few lines of javascript with jQuery.

Basic controller logic

Being an advocate of progressive enhancement I started out by building a non-AJAX version of the feature. In the controller for the start page I let the default (Index) method have a nullable int parameter named entryCount which tells the method how many of the latest blog entries it should return for the view to display.

public class HomeController : Controller

{

private const int defaultEntryCount = 10;


public ActionResult Index(int? entryCount)

{

if (!entryCount.HasValue)
entryCount = defaultEntryCount;
//Retrieve the first page with a page size of entryCount
int totalItems;
IEnumerable<Entry> entries = GetLatestEntries(1, 
entryCount.Value,out totalItems);
if (entryCount < totalItems)
AddMoreUrlToViewData(entryCount.Value);
return View(entries);
}
private void AddMoreUrlToViewData(intentryCount)
{
ViewData["moreUrl"] = Url.Action("Index", "Home",
 new { entryCount = entryCount + defaultEntryCount });
}
}

The method begins by making sure that the entryCount variable has a value, setting it to a default value if the parameter is null. It then retrieves as many of the latest blog entries as entryCount specifies by calling the GetLatestEntries method. I’ve omitted the GetLatestEntries method as it’s implementation will vary depending on blogging platform. The GetLatestEntries method also has an out parameter, totalItems, which tells us the total number of blog entries. I’m not a big fan of using out parameters but that’s the way the framework that I used for my blog (EPiServer Community) works so I decided to follow that pattern for consistency. If you use some other type of blogging platform I would recommend making a field of the totalItems.

The method moves on to check if there are more blog entries than the  number that will be displayed, in other words if a link for showing more entries should be displayed. If so, it calls the AddMoreUrlToViewData which, you guessed it, adds a route URL for displaying more entries to the ViewData dictionary.

Finally the method returns a ViewResult with the list of blog entries as the model.

Creating the views

The Index view for the Home controller is very simple. It simply renders a partial view named EntryTeaserList, passing along the list of blog entries (Model) and the it’s ViewData dictionary.

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
Inherits="System.Web.Mvc.ViewPage<IEnumerable<Entry>>" %>
<asp:ContentContentPlaceHolderID="PrimaryMainContent"runat="server">
<% Html.RenderPartial("EntryTeaserList", Model, ViewData); %>
</asp:Content>

The partial view EntryTeaserList offers a bit more excitement. It renders an ordered list and displays a teaser for each blog entry by rendering another partial view, EntryTeaser, inside a list item, passing in each individual blog entry as model to it. It also checks if the ViewData dictionary contains a URL for a more link. If it does it renders a link with that URL in the href attribute.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Entry>>" %>
<div id="entryTeaserList">
<ol>
<% foreach (Entry item in Model) { %>
<li class="entryTeaser">
<% Html.RenderPartial("EntryTeaser", item); %>
</li>
<% } %>
</ol>
<% if(ViewData["moreUrl"] != null) { %>
<a href='<%= ViewData["moreUrl"] %>' id="moreLink">More</a>
<% } %>
</div>

Finally I made the link look like a button with some CSS.

#moreLink {
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
border: 1px solid #666666;
background:url('/styles/gfx/more-bg.gif')repeat-x;
width: 100%;
display: block;
text-align: center;
padding: 0.4em 0 0.4em 0;
font-weight: bold;
color:#9aa57c; 
}
#moreLink:hover {
background:url('/styles/gfx/more-bg.gif') 0 -64px; repeat-x;
border: 1px solid #888888;
text-decoration: none;
}

As you might have noticed I used CSS to round the buttons corners. This will only work for some browsers. In this particular case I though that that was OK, but in many other situations I would instead have used images or javascript. You might also have noticed that the button has the same background image when it’s hovered over as when it isn’t. The background image is however offset vertically so it appears that it’s actually another image. I did this to keep the number of HTTP requests required to load the page to a minimum.

Spicing things up with AJAX

With the controller and views set up as described above I was done with the non-AJAX functionality. This will work fine for visitors that doesn’t have javascript enabled or debugging purposes, but this kind of paging is pretty pointless if the page has to reload. After all the point is that when someone clicks the more button the experience shouldn’t be that another page is displayed but that the list, more or less instantly, just grows a bit.

To add the AJAX functionality I begun by modifying the controllers Index method.

public ActionResult Index(int? entryCount)
{
if (!entryCount.HasValue)
entryCount = defaultEntryCount;
int totalItems;
if(Request.IsAjaxRequest())
{
int page = entryCount.Value / defaultEntryCount;
//Retrieve the page specified by the page variable with a page size o defaultEntryCount
IEnumerable<Entry> pagedEntries = GetLatestEntries(page,
 defaultEntryCount, out totalItems);
if(entryCount < totalItems)
AddMoreUrlToViewData(entryCount.Value);
return View("EntryTeaserList", pagedEntries);
}
//Retrieve the first page with a page size of entryCount
IEnumerable<Entry> entries = GetLatestEntries(1, 
entryCount.Value, outtotalItems);
if (entryCount < totalItems)
AddMoreUrlToViewData(entryCount.Value);
return View(entries);
}

The added code checks if the current request is an AJAX request, with the IsAjaxRequest extension method that ships with MVC. IsAjaxRequest determines if the current request is an AJAX request by looking for and at the X-Requested-With request header. If such an header, or actually any request parameter with that name, is set to “XMLHttpRequest” the method will return true. As jQuery’s AJAX methods sets that header this method works great in this example.

Anyway, if the current request is an AJAX request we know that the visitors browser already displays a number of blog entry teasers and instead of returning the full number of entries specified by the entryCount parameter we should only return those that haven’t yet been sent to the visitors browser. So, we calculate what page (as if we where using traditional paging) is requested by dividing entryCount with the defaultEntryCount constant. Then we retrieve a list of the entries on that page with a page size of defaultEntryCount. That is we retrieve the defaultEntryCount number of entries with an offset of page*defaultEntryCount.

Finally, if there are more entries we set the moreUrl in the ViewData dictionary by calling the AddMoreUrlToViewData method and return a ViewResult. This time around however we don’t return the default view for the method. Instead we return the EntryTeaserList partial view. This way we don’t return more HTML than necessary but we are able to reuse an already existing view. We could of course have returned the result as JSON or XML instead but that would have forced us to write javascript for rendering the markup to display the result and thereby duplicating the same markup in two places.

The last thing I did was to add a few lines of javascript to intercept clicks on the more link.

$(function() {
addMoreLinkBehaviour();
});
function addMoreLinkBehaviour() {
$('#entryTeaserList #moreLink').live("click", function() {
$(this).html("<img src='/images/ajax-loader.gif' />");
$.get($(this).attr("href"),function(response) {
$('#entryTeaserList ol').append($("ol", response).html());
$('#entryTeaserList #moreLink').replaceWith($("#moreLink", response));
});
return false;
});
}

When the DOM is ready we add a function to the click event of the more link, and, since I’m using the live function, to any future objects matching that selector. When the link is clicked two things initially happen. First the link’s text is replaced with an image to give the visitor some visual feedback if the response of the AJAX request isn’t instantly returned. Then an AJAX request is made to the same URL as the link had in it’s href attribute. That is, there’s no special URL for the AJAX request. This works as the controller takes care of determining what type of request it is.

When the server has responded with the partial view, that is an ordered list and possibly a new more link the list items are appended to the existing ordered list and the more link is replaced with the new more link if it exists. This way the more link is automatically updated with a new URL in it’s href attribute and the loading image is replaced with the original text.

Conclusion

I personally find this solution pretty elegant. It requires quite few lines of code and almost no duplicate logic or markup at all. It also offers full fallback functionality for visitors without javascript. However, if I was really interested in offering the best possible experience to human visitors with javascript disabled I could also give each blog entry teaser an id with it’s number in the list and include a hash tag with entryCount + 1 – defaultEntryCount in the more link’s target URL so that they would automatically be scrolled to the first entry that was added to the list. In my case I deemed that to be overkill though.

PS. For updates about new posts, sites I find useful and the occasional rant you can follow me on Twitter. You are also most welcome to subscribe to the RSS-feed.

Advertisements

S3Captcha For ASP.net MVC


Windows 7 Enterprise x86 1 link !


image

 

Windows 7 Enterprise x86
File Name : en_windows_7_enterprise_x86_dv d_x15-70745.iso
File Size : 2.24 GB
Date Posted: 8/6/2009 9:56 AM ISO/CRC: 6A9B5097
SHA1: C6B905E48FDB6CB5BFCA967715A644 61B812D40C

http://download391.mediafire.com/w1s…_x15-70745.iso

Windows Se7en XP x86 – Black Edition (08-2010)


image

All driverpacks are included :
DriverPack Graphics A 8.12.1
DriverPack Graphics B 8.12.1 DriverPack Graphics C 8.12.1
DriverPack LAN 8.12.1
DriverPack Mass Storage 9.01
DriverPack Sound A 8.05
DriverPack Sound B 8.05
DriverPack WLAN 8.06
DriverPack CPU 8.04
CCleaner 2.21.940
dfxWMP [for more voice in WMP11]
DirectX 9.0c
Flash Player 10 IE/FF
IconPackager 3.2
Internet Explorer 8
Java[TM] 6 Update 15
Messenger Plus for WLM 9
Mozilla Firefox 3.5.0
NetFramework 2.0
Silverlight 2.0
Winrar 3.80
Ueber icon 1.0.4
Windows Media Player 11
Windows Live Messenger 9.0
Yahoo 10
More…
– Cyber Se7en Black Edition theme
– Cpls
– Cursors
– Fonts
– Wallpapers

Download

Name: XP Se7en Black Edition (700mb)
Service Pack: 3
System: x86 32bit
Product K.@.y: Not Needed
This version of Windows XP is designed for your convenience

http://www.mediafire.com/?ocpu6blkxi9w0gd
http://www.mediafire.com/?tx77ockh6e8x4jg
http://www.mediafire.com/?8ir6geuga98v48b
http://www.mediafire.com/?01283xttv5npt59
Password : allmedia4u@soft

Biến Windows lậu thành Windows có bản quyền


Đầu tiên vào Start ,Run,gõ Regedit rùi tìm đến khóa
HKEY_LOCAL_MACHINE\Software\Microsoft\WindowsNT\CurrentVersion\WPAEvents

Ở ô bên phải nhấn chuột phải vào OOBETimer rồi click vào Modify. Tiếp đến hãy thay đổi một khóa bất kì (ví dụ 71 thành 72 chẳng hạn). Sau đó đóng nó lại và tiếp tục vào Start , Run , và đánh

%systemroot%\system32\oobe\msoobe.exe /a

Nó sẽ đưa bạn đến cửa sổ Active windows.

Đừng lo hãy chọn
I want to telephone a customer service representative to activate Windows

Tiếp đến nhấn Next, nhấn Change Product key
Hãy nhập key này

DHXQ2-WRGCD-WGYJY-HHYDH-KKX9B
hoặc
JG28K-H9Q7X-BH6W4-3PDCQ-6XBFJ
hoặc
MJPMD-69P47-4JK37-DGQF2-XMWPQ

Đánh xong hãy nhấn Update. Nó sẽ lại đưa chúng ta về cửa sổ Active, đừng quan tâm, hãy đóng nó lại bằng cách nhấn vào Remind me later
Xong đâu đó rùi thì còn chờ gì nữa, khởi động lại Windows đi thôi. Khởi động lại xong hãy vào Run, đánh tiếp

%systemroot%\system32\oobe\msoobe.exe /a

Cửa sổ Active sẽ lại hiện ra nhưng ko phải là bắt chúng ta Active mà là báo cho ta biết Windows is activated.

Vậy là Windows đã có bản quyền rùi đó. Hãy tận hưởng bằng cách vào ngay trang update hay cài IE7, WMP11, Windows Defender …. hay bất cứ cái gị có check bản quyền của Microsoft.

WF4 – Xây dựng Flowchart workflow


Trong bài viết này, chúng ta sẽ tìm hiểu về một activity rất quan trọng trong workflow, đó là Flowchart activity. Trong flowchart activity, các child activity được kết nối với nhau qua các cây quyết định (decision tree). Nếu như trong Sequence, các child activities được thực hiện một cách tuần tự top-down theo một thứ tự nhất định, thì trong flowchart activity, các child activities có thể được thực hiện theo một thứ tự bất kỳ và tùy thuộc vào các điều kiện tại các nhánh quyết định (decision branches).

Xây dựng một flowchart workflow

Tạo mới một project Console Workflow Application với tên là FlowcharWorkflow.

Để bắt đầu xây dựng một flowchart, kéo thả một Flowchart activity từ toolbox vào workflow mới vừa tạo ra. Sau khi kéo thả thành công Flowchart activity vào workflow, thì kết quả thu được sẽ như sau

image

Một Flowchart activity luôn có một node khởi đầu như trong diagram biểu diễn ở trên. Điểm khác biệt giữa Flowchart và Sequence chính là cách mà các child activities được sắp xếp. Trong Sequence, child activities được sắp xếp theo một thứ tự duy nhất là trên xuống dưới và đó cũng là thứ tự thực hiện của các activities, đồng thời sự liên kết giữa các activities trong Sequence được tạo ra tự động và theo một chiều nhất định là top-down. Trong Flowchart activity, child activities có thể được sắp xếp tùy ý và bắt buộc người dùng phải định nghĩa các mối liên kết giữa các activities.

Tiếp tục thêm vào một WriteLine activity để xuất ra Console dòng chữ “Hello, Flowchart.”. Sau khi đã thêm vào WriteLine activity vào Flowchart, chúng ta phải thiết lập connections cho các activities trong flowchart này .

image

Nếu chạy Flowchart vừa tạo ra ở trên thì chúng ta sẽ thu được trên màn hình Console một dòng là “Hello, Flowchart.”. Flowchart trên chỉ bao gồm 1 activity duy nhất cùng 1 flow duy nhất. Tuy nhiên, khi làm việc với Flowchart thì chúng ta sẽ có rất nhiều flow cùng với các điều kiện tương ứng cho mỗi flow. Để có thể xây dựng một flowchart như thế, WF4.0 cung cấp sẵn cho chúng ta các activities như là FlowDecision, FlowSwitch<T>

FlowDecision activtity

Quay trở lại với ví dụ trên, chúng ta sẽ sử dụng FlowDecision để đưa ra các lời chào thời gian khác nhau tùy thuộc vào thời gian hiện tại như là “Good morning”, “Good afternoon”, “Good evening”.

image

Đối với FlowDecision, activity này sẽ dựa vào kết quả true hoặc false của condition mà quyết định hướng đi tiếp theo. Chúng ta có thể thấy rằng cách thực hiện này rất giống với If khi chỉ phụ thuộc vào 2 giá trị là true hoặc false để đưa ra quyết định.

Để mở rộng khả năng của Flowchart, chúng ta có thể sử dụng một activity được xem là mở rộng của FlowDecision là FlowSwitch<T>

FlowSwitch<T> activity

FlowSwitch hoạt động giống với FlowDecision activity, tuy nhiên điểm khác biệt là FlowSwitch không phụ thuộc vào chỉ 2 giá trị true hoặc false mà FlowSwitch chấp nhận nhiều giá trị khác nhau. FlowSwitch giống với việc sử dụng switch trong C# thông thường.

image

Có một điểm cần lưu ý là đối với FlowSwitch, vì là một generic class FlowSwitch<T> nên khi kéo thả activity này từ toolbox thì Visual Studio 2010 đòi hỏi chúng ta phải tùy chọn kiểu T cho activity này.

Sơ đồ FlowChart hoàn chỉnh

image

Posted in WF

Giới thiệu Tuple trong .NET 4.0


Trong phiên bản sắp phát hành Microsoft .NET Framework 4.0 sẽ xuất hiện một kiểu dữ liệu mới là System.Tuple. System.Tuple là một kiểu tập hợp có kích thước cố định được dùng để lưu trữ tập các đối tượng có kiểu dữ liệu khác nhau. Cũng giống như kiểu mảng trước đây, một tuple sẽ có kích thước cố định và không thể thay đổi sau khi được khởi tạo thành công. Điểm khác biệt so với kiểu mảng là mặc dù các phần tử trong tuple có thể có kiểu dữ liệu khác nhau nhưng mỗi phần tử trong tuple được đảm bảo về kiểu dữ liệu của mình. Để hiểu rõ hơn, hãy xét một ví dụ sau đây:

 

Code Snippet
  1. static void Main(string[] args)
  2.         {
  3.             object[] objects = new object[3];
  4.             objects[0] = "This is a string";
  5.             objects[1] = 1;
  6.             objects[2] = new DateTime(2009, 10, 24);
  7.             PrintObjects((string)objects[0], (int)objects[1], (DateTime)objects[2]);
  8.         }
  9.         static void PrintObjects(string str, int i, DateTime dt)
  10.         {
  11.             Console.WriteLine("{0} {1} {2}", str, i, dt.ToString());
  12.         }

Ví dụ trên tạo ra một mảng gồm 3 phần tử có kiểu là object. Thoạt nhìn, đoạn code trên không có vấn đề gì nảy sinh. Nhưng nếu xét về mặt an toàn kiểu dữ liệu (type safety) thì đoạn code trên mắc rất nhiều lỗi. Khi khai báo một mảng các object như vậy, thì ta có thể đưa vào đó bất kỳ thứ gì ta muốn và không thể kiểm soát được. Trong trường hợp, nếu như tại phần tử thứ 2, ta đưa vào là một chuỗi ký tự như sau:

Code Snippet
  1. static void Main(string[] args)
  2.         {
  3.             object[] objects = new object[3];
  4.             objects[0] = "This is a string";
  5.             objects[1] = "This is an another string";
  6.             objects[2] = new DateTime(2009, 10, 24);
  7.             PrintObjects((string)objects[0], (int)objects[1], (DateTime)objects[2]);
  8.         }

Đoạn code sau khi sửa xong vẫn sẽ biên dịch thành công. Nhưng khi gọi thực thi phương thức PrintObjects thì sẽ bị lỗi InvalidCastException khi thực hiện việc chuyển đổi chuỗi “This is an another string” sang kiểu số int.

Bây giờ, cùng ví dụ trên, nhưng ta sử dụng System.Tuple

Code Snippet
  1. static void Main(string[] args)
  2.         {
  3.             System.Tuple<string, int, DateTime> tuple = new Tuple<string, int, DateTime>
  4.             ("This is a string", 1, new DateTime(2009, 10, 24));
  5.             PrintObjects(tuple.Item1, tuple.Item2, tuple.Item3);
  6.         }

Với cách giải quyết vấn đề bằng việc sử dụng System.Tuple, ta có thể loại bỏ các phép toán “ép kiểu” (cast operators) ra khỏi chương trình, điều này giúp cho việc kiểm tra lỗi tại thời điểm biên dịch tốt hơn. Với ví dụ vừa nêu ở trên, ta thấy rằng System.Tuple là một kiểu dữ liệu hữu ích và cần phải có trong .NET Framework.

System.Tuple và KeyValuePair

Hãy xem một phương thức khởi tạo của Tuple như sau:

public static System.Tuple<T1,T2> Create<T1, T2>(T1 item1, T2 item2)

Từ các phiên bản trước của .NET Framework, đã có một kiểu dữ liệu có tên là KeyValuePair<Tkey, Tvalue> nằm trong namespace System.Collections.Generic. Cả hai kiểu Tuple<T1, T2> và KeyValuePair<Tkey, Tvalue> đều rất giống nhau. Thật ra, giữa Tuple<T1, T2> và KeyValuePair<Tkey, Tvalue> có khá nhiều điểm khác biệt. Đối với KeyValuePair<Tkey, Tvalue>, ngoài việc lưu trữ 2 thành phần Tkey và Tvalue, nó còn mang kèm cả “mối quan hệ” giữa 2 thành phần này – và cũng với lý do này, KeyValuePair<Tkey, Tvalue> được dùng trong Dictionary class. Bên cạnh đó, tuples khi khởi tạo có thể thay đổi được kích thước lưu trữ, còn đối với KeyValuePair thì mặc nhiên chỉ lưu trữ duy nhất 2 thành phần là Tkey và Tvalue.

Đối với System.Tuple, ta có các phương thức khởi tạo sau:

Code Snippet
  1.         Tuple <T1>
  2.         Tuple <T1, T2>
  3.         Tuple <T1, T2, T3>
  4.         Tuple <T1, T2, T3, T4>
  5.         Tuple <T1, T2, T3, T4, T5>
  6.         Tuple <T1, T2, T3, T4, T5, T6>
  7.         Tuple <T1, T2, T3, T4, T5, T6, T7>
  8.         Tuple <T1, T2, T3, T4, T5, T6, T7, TRest>

 

Tham khảo bài viết CLR Inside Out: Building Tuple tại http://msdn.microsoft.com/en-us/magazine/dd942829.aspx