Archive

Archive for the ‘ASP.NET’ Category

ASP.NET MVC and Unobtrusive Ajax with jQuery Part II

September 18th, 2008

In my last post, I introduced this series in which I add Ajax to an ASP.NET MVC application as a progressive enhancement, allowing the site to function without requiring JavaScript, but creating a better user experience for users with JavaScript enabled. In this post, I will discuss the feature I am working on and introduce the base controller and view code as well as introduce a minor change to the initial controller code to add support for Ajax calls/return data without breaking the standard post-based behavior.

The Target Functionality

The functionality that we will be looking at for the purpose of these posts will be a simple content page that allows comments. The comment form will exist on the content page and will call a controller that will then insert a record in the database. Since the comments live on the page with the associated content, the controller will redirect to the view that the form was submitted from (showing the content again, along with the comments). The page will be reloaded, just as you would expect from a typical form submission that redirects following the submission. Once that works, we can update the code to make the form submit via Ajax, at which point we will need to update the page through client script.

The Initial Code

The initial code is a simple controller action which makes a call to a business object that will handle inserting the comment into the database. I will be displaying a code comment as a placeholder for that code since it has very little relevance to the topic at hand.

[AcceptVerbs("POST")]
public ActionResult AddComment(string commentBody, int articleID)
{
//Logic and Database insert code...

//redirect to re-load the page
return RedirectToRoute("Default",
new { controller = "Article",
action = "Show",
id = articleID });
}

At this point, since the View for our content will re-query and display all of the associated comments all that this code needs to do is redirect the user back to the content page and the new comment will be loaded along with the rest of the comments. The above code accomplishes this redirect in a single line of code.

Updating The Controller Action

Now that we have the form in the view successfully submitting its content to the controller action through a post, we need to update the action code to distinguish between a standard post and an ajax-based post in order to return JSON when needed. One of the goals was to make any controller changes minimal. We also want to make sure that none of the business logic changes due to the addition of Ajax. So let’s take a look at the code for the first step:

[AcceptVerbs("POST")]
public ActionResult AddComment(string commentBody, int articleID, string mode)
{
   //Logic and Database insert code...

   //redirect to re-load the page
   return RedirectToRoute("Default",
      new { controller = "Article",
            action = "Show",
            id = articleID });
}

The only change we have made at this point is to add an additional string argument to the method’s signature. This “mode” argument isn’t part of the original form post, and that’s ok. Our form doesn’t need to change. The only time (at this point) the mode value will be passed is when we are making the call from JavaScript. Now we will add a simple check to see if that string has been passed. If it hasn’t been passed, then we know it was a standard post and we will perform the redirect, just like we have been doing:

[AcceptVerbs("POST")]
public ActionResult AddComment(string commentBody, int articleID, string mode)
{
   //Logic and Database insert code...

   if (!string.IsNullOrEmpty(mode) && mode == "ajax")
   {
      //handle json result for ajax call
   }
   else
   {
      //redirect to re-load the page
      return RedirectToRoute("Default",
         new { controller = "Article",
               action = "Show",
               id = articleID });
   }
}

Now all that’s left to do is return JSON formatted data if the mode has been passed. The final version of the controller action is listed below.

[AcceptVerbs("POST")]
public ActionResult AddComment(string commentBody, int articleID, string mode)
{
   //Logic and Database insert code...

   if (!string.IsNullOrEmpty(mode) && mode == "ajax")
   {
      //The comment object is returned after the data insert above
      return this.Json(comment);
   }
   else
   {
      //redirect to re-load the page
      return RedirectToRoute("Default",
         new { controller = "Article",
               action = "Show",
               id = articleID });
   }
}

This code includes a call to the Controller’s Json method (referenced as this.Json) which accepts your data object (in our case, a comment object that was returned from the omitted data insert code) and returns a JsonResult object that can be returned from the controller. The passed object is then made available to your JavaScript when it receives the Ajax result. We will take a closer look at the comment object in the next post, when we add the JavaScript that will trigger the JsonResult object being returned.

This controller action meets our goals of using the same logic regardless of the method that was used to post the form and I think the changes are fairly minimal (again, this is pretty subjective).

Coming Up

We still need to add the jQuery code required to make an Ajax call to the controller and ensure that there is a return value. After we have the code in place to make the call and run the controller code, there will be a couple more posts to explain how to handle the return data and add some other nice UX features for those with JavaScript enabled.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

.NET, ASP.NET, Ajax, JavaScript, MVC, jQuery , , , , ,

ASP.NET MVC and Unobtrusive Ajax with jQuery Part I

September 15th, 2008

I have recently started developing a website using ASP.NET MVC (preview 5). One of the things I love about the MVC framework is the total control I have over my markup. I can keep the HTML clean, use ID values that make sense and won’t change each time the page is rendered and I can add all of my own client-side interactivity using jQuery (without the id mess that web controls create).

One of my goals with this new site is to create a functional site that works and renders in a usable manner even with CSS and JavaScript disabled. In order to do this, I have been creating all of the required interactions using standard get and post operations. The next logical step is to add Ajax functionality as a progressive enhancement using jQuery. Since ASP.NET MVC is still pre-beta, there isn’t a lot of documentation or published best practices with specifics about how to best handle this through the framework. I have read several posts from others with different solutions, they range anywhere from routing hacks (including ajax or html in the url) to seperate views (one standard view and an ajax enabled view). None of the solutions I have found have been what I wanted to do, so I set some goals for how I wanted it to work and through a process of trial and error, I have a solution. Through the next couple of posts, I will outline how I am handling this. I look forward to any feedback (good or bad) and if somebody has a better solution, please let me know.

The Goals

  1. Site should work in an acceptable manner without JavaScript enabled
  2. The same view should handle both Ajax enabled and standard post/get operations
  3. All controller code should be run in either scenario except for returning the ActionResult
  4. Changes to the controller action signature should be minimal and the same approach should work for any controller action (with minimal change)

Some of these goals are no-brainers, it helped to have all of the goals in place to keep myself from creating a massive amount of “Franken-code” in order to get the site working. I also realize that using the word “minimal” when talking about code changes is subjective, so if you don’t think what I have done is minimal, feel free to comment on the posts that contain the code and we can discuss it, please just share something more than “your solution sucks…” that won’t help anybody. And, if you have any other goals that you think I shoudl keep in mind, please let me know. I can’t promise that I’ll be able to attain more than I have already planned for in this series, but if something that I haven’t thought of seems important enough, I will do my best to try to meet the new criteria.

Upcoming Posts

Stay tuned. I have the code worked out for the most part, but it hasn’t been fully implemented, so I will be posting as I make more progress and I have tested enough to feel confident that I will have working code. I’m hoping to get the first post that has any real substance out before the end of the week.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

.NET, ASP.NET, Ajax, JavaScript, MVC, jQuery , , , , ,

Alternating Item Styles In The ASP.NET ListView Control with jQuery

September 15th, 2008

In my last post I demonstrated how you could simply add alternating CSS styles to a ListView control without the need to duplicate your markup in an AlternateItemTemplate. This was accomplished through server-side code using the DataItemIndex to determine which style to apply. This time, we will look at how simple it is to apply the same effect using jQuery on the same simple markup that I used in the last post. I have removed the DataItemIndex related code and we are now left with this markup:

<asp:ListView ID="lvPeople" runat="server" ItemPlaceholderID="plcItem">
   <LayoutTemplate>
      <div id="people">
         <asp:PlaceHolder runat="server" ID="plcItem" />
      </div>
   </LayoutTemplate>
   <ItemTemplate>
      <div>
         First Name: <asp:Label runat="server" ID="lblFirstName" Text='<%# Eval("FirstName") %>' /><br />
         Last Name: <asp:Label runat="server" ID="lblLastName" Text='<%# Eval("LastName") %>' /><br />
         Age: <asp:Label runat="server" ID="lblAge" Text='<%# Eval("Age") %>' />
      </div>
   </ItemTemplate>
</asp:ListView>

With the markup in place, I have added a reference to the jQuery library in my page like so:

<script language="javascript" type="text/javascript" src="jquery-1.2.6.js"></script>

Now all that’s left is to add a little bit of jQuery code and we can apply our alternating css classes.

<script language="javascript" type="text/javascript">
   $(function() {
      $("div#people div")
         .filter(":even").addClass("even")
         .end().filter(":odd").addClass("odd");
   });
</script>

It’s that simple. The code listing above was broken out onto multiple lines, but could just as easily have been written in one single line, I prefer the format above for readability, but that’s just me.

The jQuery code selects all divs that are children of the wrapper div (identified with the “people” id attribute) and runs a filter that only returns the even items. It then adds a css class called “even” to those divs. The end() method then returns scope back to the first selection containing all of the child divs. The second filter runs just like the first, only this one returns the odd items and aplies the “odd” class to those divs.

If you haven’t used jQuery before, I suggest you check it out. It makes selecting items and then manipulating them simple and in a pretty intuitive way (IMHO). If you don’t think it looks easy, try writing code to do this without the aid of a JavaScript library such as jQuery, make it work across browsers and then decide if this way isn’t easier.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

.NET, ASP.NET, JavaScript, jQuery , , ,

Alternating Item Styles In The ASP.NET ListView Control

September 15th, 2008

If you have used the asp.net listview control, you know that you place your output elements in between the ItemTemplate tags. You may or may not have noticed that there is also an AlternateItemTemplate tag that can be used to render every other item differently. This is great when you want to alter the layout of every other item, but if you just want to apply a different CSS class on alternate items, you will be duplicating a lot of markup for a fairly minor change. Well, I have good news, there is an easier way. All you need to do is look at the DataItemIndex for your data item, determine if it is even or odd and apply the appropriate css class. The code is pretty self-explanatory, so here it is:

<asp:ListView ID="lvPeople" runat="server" ItemPlaceholderID="plcItem">
   <LayoutTemplate>
      <div id="people">
         <asp:PlaceHolder runat="server" ID="plcItem" />
      </div>
   </LayoutTemplate>
   <ItemTemplate>
      <div class='<%# Container.DataItemIndex % 2 == 0 ? "even" : "odd" %>'>
         First Name: <asp:Label runat="server" ID="lblFirstName" Text='<%# Eval("FirstName") %>' /><br />
         Last Name: <asp:Label runat="server" ID="lblLastName" Text='<%# Eval("LastName") %>' /><br />
         Age: <asp:Label runat="server" ID="lblAge" Text='<%# Eval("Age") %>' />
      </div>
   </ItemTemplate>
</asp:ListView>

The key is this line of code:

<div class='<%# Container.DataItemIndex % 2 == 0 ? "even" : "odd" %>'>

This allows alternating styles without duplicating your markup, which makes changing the item layout in the future much easier. You could also accomplish this with some javascript (in a line or two using jQuery) but that is a topic for another post.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

.NET, ASP.NET ,

The ASP.NET DropDownList Hates Me

July 22nd, 2008

It must, or I wouldn’t have spent nearly as much time over the last few days fighting with it as I have. He started it!

Ok, maybe it was slightly my fault that I’ve been having the issues I’ve been having. Maybe my expectations were just set too high. I figured the DropDownList could handle whatever I threw at it. I was wrong.

So I found myself in a position recently where I needed a DropDownList to allow users to select an item based on a search. So the workflow is a bit like this:

  • User enters a search term into a textfield
  • User clicks search
  • Resulting items are displayed in a DropDownList for selections
  • User selects appropriate item from DropDownList and moves on

Now, based on the search term, there is a good chance that multiple items in the list will have the same underlying value attached to them. That’s ok, it’s what is expected of the system. This allows the user to make their choice based on the display and still give the application the value that it needs to get the job done. This seems like something that should be reasonably easy to do, and at the beginning, it was.

The search returns its items and binds them to the DropDownList. No problem. The user can select an item in the list as part of the form completion process and submit the form. The value can be picked up on the server and the app can do what it needs with the data. Also, no problem.

At this point, there hasn’t been a problem. The problems arise when you go beyond simply binding a list of values and having that field as part of a larger form. Let’s look at each problem seperately.

The first problem arises when the form is bound to an exsiting record. When binding the form, the options that were available in the list for the initial selection will be populated again. This puts multiple items in the DropDownList and in this scenario, there are multiple entries with matching values (but different text). Now as part of the binding process, text fields are set, checkboxes are checked and an item (prefereably the correct one) needs to be pre-selected in the DropDownList based on our record. To account for this, the display value from the selected item is stored as well (strictly to put the selection back in this scenario). So we pull the display value since those are unique and use that to find our desired item. Set the returned item to selected and we’re set. But we’re not. Even thought we have picked the item based on a unique property and we know that in the code-behind, only one item was selected, the page will result in an http error letting you know that you cannot select more than one item in the DropDownList. Removing the other items from the list prevents this, but it is not ideal since the original options should be available during an edit operation.

The second problem occurs when you want to perform some operation based on the DropDownList changing. Typically, you would attach a SelectedIndexChanged event handler to the DropDownList and then set its AutoPostBack property to true. So, when I needed this functionality, I followed those steps and it worked. It worked until there were items with matching values in the list. Once that happened, changing the value would cause a postback, but would not trigger the SelectedIndexChanged event handler. I worked around this one with a Page Method and some client-side ASP.NET Ajax code. Again, not an ideal use for my time.

So why are these tings happening? Well, based on everything I have seen happen here, I have to say that the value of each item is important and probably should be unique. In my opinion, the event and method names lead you to believe that it is looking at the Index, or that you can select an item based on picking the right ListItem object. Apparently, at some point, the value needs to be used to complete the setting, change detecting, etc. so it should be treated as a unique key.

My situation is not the most common way to use a DropDownList, but I can’t imagine that I’m the first person that needed to do something like this. Oh well, I’ll come up with a better way to handle the UI in situations like this in the future.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

.NET, ASP.NET

Back To Basics - Container.DataItem

June 16th, 2008

Since asp.net 2.0 was in beta, I pretty much bailed on .NET 1.1 and Visual Studio 2003 and I’ve been pretty happy that way.

One of my favorite features in asp.net 2.0 (it’s a long list after working in 1.1) was the shortened syntax for declarative databinding to objects using Eval(”propertyname”) where “propertyname” is, well, a property in the object being bound to the control. This works when binding to a collection of objects where the values you want to use are stored in properties of the object. What about simple types such as integers or strings?

It’s pretty easy to bind a databound control to a collection of simple types such as a List<string> or a string array but since I don’t do it very often, I have to stop and remember (or look up) how to do it. So for my own reference and anybody else that may find this useful, here is a simple code example:

<ul>
  <asp:Repeater ID="rptSampleStrings" runat="server">
    <ItemTemplate>
      <li>
        <asp:Label runat="server" ID="lblStringDisplay"
          Text='<%#Container.DataItem %>' />
      </li>
    </ItemTemplate>
  </asp:Repeater>
</ul>

Here I have a repeater that will render a bulleted list of items from my List<string>. Notice the use of Container.DataItem for the text property… this is the point of this post. This is the syntax I always have to think about.

This is just to illustrate a point, I know I could use a ListView or the BulletedList control to do this. Anyway, here is the code-behind that creates the List<string> and binds it to the repeater (this code is in the Page_Load event handler):

var sampleStrings = new List<string>();
sampleStrings.Add("Test One");
sampleStrings.Add("Test Two");
sampleStrings.Add("Test Three");
sampleStrings.Add("Test Four");
sampleStrings.Add("Test Five");

//Set repeater datasource
rptSampleStrings.DataSource = sampleStrings;
rptSampleStrings.DataBind();

So, as you can see, it’s very simple to bind control properties to a list of strings, integers, etc. as long as you can remember the syntax.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

.NET, ASP.NET, Back to Basics, programming , , ,

My IE 7 Is Having Issues

May 18th, 2008

For some reason IE7 has decided that it doesn’t want to show a couple of web controls on a page I just created. I have a Label, CheckboxList, Multiline Textbox and two Button controls on a page. The Textbox and Buttons aren’t showing up on the page. They are in my source, work in FireFox and there are no styles applied that would hid anything.

This is pretty frustrating. If anybody has any idea why this might be happening I would appreciate the input. If I fix it and have a reasonable explanation, I will post it for anybody else that has this problem in the future.

Well, back to troubleshooting browser issues instead of being productive….

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

ASP.NET , ,

ASP.NET’s Built-in Maintenance Page

April 2nd, 2008

This was originally posted on vanslaars.com in April of 2006.

Web applications require updates, maintenance and sometimes there are problems behind the scenes that require an app be taken offline temporarily. In the past I have accomplished this by setting my IIS default documents to index.html and default.aspx, in that order. My applications always use Default.aspx as the home page and I leave a file called “_index.html” sitting in the root directory. This html file contains a standard message letting users that the system is temporarily down. When I need to perform maintenance, I rename “Default.aspx” to “_Default.aspx” and remove the underscore from the html file to leave “index.html” as the default page for the application. This stops bookmarks to Default.aspx from working and displays the error message.

This approach works reasonably well, but it has some inherent problems. For one, the application continues to run, so bookmarks to other pages in the site continue to work. People looking for “Default.aspx” will get a 404 (page not found) error, and renaming both files is more work than I care to do for such a common need. Well, ASP.NET 2.0 comes to the rescue again with a very simple, yet elegant solution…the “App_Offline.htm” file.

Placing a file in the application root with the name “App_Offline.htm” causes the application domain to be unloaded and requests stop being processed. There is no need to rename any files, or change anything in the application. Just add the file and all requests will be directed to that file. Place your friendly message there, upload the file and perform your maintenance. When you are done, remove the file and the application runs normally for the next request.

The nice thing about this is that you can keep the file off of the server, FTP it up when you need it and delete it when you are done. The local copy can be used again in the future and no files need to be moved or renamed for the application to start again. There is also no requirement to change any setting in IIS.

Who would have thought a simple html page would become a key tool for application maintenance?

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

.NET, ASP.NET ,

RenderControl(), it isn’t just for page output

April 1st, 2008

This was originally posted on vanslaars.com in December of 2006.

In browsing through the unanswered posts on the asp.net forums, I came across an interesting question… somebody wanted to know how you would put the output from a GridView control in an email… since all of the ASP.NET Server controls end up producing html content, and the best way(actually the only way that I know of) to put a table into an email is with html, why not use the same mechanism for both? So how are controls rendered? Through their RenderControl method of course. This is actually a pretty simple task to accomplish and I found that there was more code to create temporary data than there was to actually embed the output in the email. So let’s take a look at the code:

protected void SendGrid()
{
MailMessage msg = newMailMessage();
msg.To.Add(<EMAIL HERE>);
msg.From = newMailAddress(<EMAILHERE>);
msg.Subject = "Testing";
msg.IsBodyHtml = true;
msg.Body = "<h1>Start Of Message</h1>";
msg.Body += GetGridHtml();
msg.Body += "<hr/>End of message";
SmtpClient mailServer = newSmtpClient("<MAIL SERVER ADDRESS>");
NetworkCredential mailLogin = newNetworkCredential("<USER NAME>",
"<PASSWORD>");
mailServer.Credentials = mailLogin;
mailServer.Send(msg);
}

private string GetGridHtml()
{
//Setup the gridview object
  GridView gv = new GridView();
gv.DataSource = CreateDataTable();
gv.DataBind();
StringWriter writer = new StringWriter();
HtmlTextWriter htmlWriter = newHtmlTextWriter(writer);
gv.RenderControl(htmlWriter);
return writer.ToString();
}

private DataTable CreateDataTable()
{
DataTable tbl = newDataTable();
tbl.Columns.Add("FirstName",typeof(string));
tbl.Columns.Add("LastName", typeof(string));
tbl.Columns.Add("Age", typeof(int));

DataRow row1 = tbl.NewRow();
row1["FirstName"] = "Bob";
row1["LastName"] = "Jones";
row1["Age"] = 32;

DataRow row2 = tbl.NewRow();
row2["FirstName"] = "Sally";
row2["LastName"] = "Smith";
row2["Age"] = 41;

DataRow row3 = tbl.NewRow();
row3["FirstName"] = "Jane";
row3["LastName"] = "Doe";
row3["Age"] = 26;

tbl.Rows.Add(row1);
tbl.Rows.Add(row2);
tbl.Rows.Add(row3);

return tbl;
}

The “CreateTableData” method is strictly to create some sample data to use as the datasource for the GridView control so we won’t focus on that, you could obviously use any valid datasource for your GridView. The “SendGrid” method is responsible for sending the email, to use this code you will need to enter the email information including the mail server and authentication information. This method also makes a call to “GetGridHtml” which is responsible for creating the GridView, binding the data to it and ultimately returning the Html output for the control. You’ll notice that once the new GridView is instantiated and DataBound, a StringWriter object is created and passed to the contructor for our HtmlTextWriter. RenderControl uses our HtmlTextWriter to load the rendered html to the underlying StringWriter and we use the StringWriter’s ToString method to return our html. The resulting html body for the email will contain an html table complete with column headings data from our datasource.

Another approach would be to loop through your datasource and create a standard html table, but with this approach we can take advantage of the internals of the GridView control, cutting down on code and making it much easier to make changes to the table structure directly through the datasource. I have a couple more ideas for RenderControl beyond page output that I will be posting in the near future. I’d be interested to hear of any other creative uses for RenderControl that you might have.. if you have anything you would like to share, feel free to leave a comment.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

.NET, ASP.NET , , , ,

Forcing an “open, save” prompt for known file types

March 31st, 2008

This was originally posted on vanslaars.com in December of 2006. It seemed pretty popular so I wanted to make sure it made it up on the new one.

Ever want to provide a link for a document that the user should download even though it will want to open directly in the user’s browser? Well, this isn’t as hard as you might think. Basically, all you need is a way to get some indirection between the clicking of the link and the transmission of the file to the user in order to tweak the response headers a bit. Using an HttpHandler makes this a breeze.

For this example, we will use a “.ashx” file, rather than creating a seperate HttpHandler assembly and registering it in the web.config, this way you can just drop the code in a file and see it work. I would highly recommend the Class Library approach for a handler that will be useful in multiple projects.

The basic project setup for this includes a directory in the root of my application called “PDF”, which holds a sample PDF document, the standard Default.aspx page and a “.ashx” handler file called “PdfHandler.ashx”. The code that will perform the file retrieval and force the dialog to appear is all in the “.ashx” file and the Default.aspx file will simply hold the following link code for testing purposes:

<a href="PdfHandler.ashx?fileName=css_cheat_sheet">Download CSS Cheatsheet</a>

As you can tell from the link, we will be calling the .ashx file from our link with a parameter called filename. The path and file extension for the file are not included since we want to limit the amount of information that is available in our querystring for the link.

In the Handler file (”PdfHandler.ashx”) we will add the following code to the “ProcessRequest” method:

string fileName = context.Request.QueryString[?FileName?];
if (!string.IsNullOrEmpty(fileName))
{
string filePath = context.Server.MapPath(string.Format(?~/PDF/{0}.pdf?,fileName));
context.Response.ContentType = ?application/pdf?;
context.Response.AddHeader(?Content-disposition?,
string.Format(?attachment;filename={0}.pdf?,fileName));
context.Response.WriteFile(filePath);
}

This code simply grabs the filename from the querystring, uses it to get the full path to the file through the MapPath method and then uses Response.WriteFile to send the file back to the user. This is not what I would call “production ready” code, but this is not the point of this example. The point here is the Header that is being added to the Response. The line that uses the AddHeader method is where the dialog comes in… setting the content-disposition to attachement and specifying a default filename will cause the browser to prompt the user to save the file. Without this, the PDF would be opened in Acrobat.

This is pretty simple to do, you just need to remember the proper syntax for the header since there is no strongly-typed way to set this through the Response object.

The first time I had to do this was in .NET 1.1 with a text file a few years back and it took quite some time to find the information, so hopefully this has made your life a little bit easier.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

.NET, ASP.NET, C#, programming , ,