This blog has, IMO, some great resources. Unfortunately, some of those resources are becoming less relevant. I'm still blogging, learning tech and helping others...please find me at my new home on http://www.jameschambers.com/.

Thursday, April 15, 2010

ASP.NET MVC and jQuery Part 2 – Suggesting Content

This is the second post in a series about jQuery and ASP.NET MVC 2 in Visual Studio 2010.

Two perpetually frustrating tasks I have dealt with are scratching out a UI to collect data and having to work with that data client-side. Thankfully, a marriage of jQuery and ASP.NET MVC make this much more appealing and helps us to achieve, very quickly, a usable interface.

Suggesting Content from User Text

If you’ve used forums or user communities such as MSDN or StackOverflow where you can pose questions to peers, you’ve likely noticed that as you type your question related content begins to appear.

image

Here, we’ll replicate that behaviour and show how easy it is to make it work using jQuery and ASP.NET MVC. Because I will use some contrived data to make this work I will also include a solution download at the end of the article.

The Basic Steps

If you have a good grasp on things and just want to give it a try, here’s what you need to do:

  • Create an ASP.NET MVC 2 Web Application
  • Add jQuery to your Site.Master page
  • Establish your model for the results of the search
  • Create a repository to return a set of results
  • Create a controller action to return the list of results in a partial view
  • Create the required partial view
  • Create the entry form that lets users ask questions
  • Write script to take the form input and send it to the appropriate controller action, updating a DOM element with the results of the call

The Walkthrough

Setting up a project and getting jQuery was highlighted in the first article in this series. Create the project and get it set up for jQuery before you get going.

Creating a Model

Under normal circumstances you’d be working from a database that stores the related content you wish to display. Your model

Right-click on the Models folder in the Solution Explorer and click Add –> Class. Name the class Question and click Add. We’re going to add some properties to the class. This is really easy with auto-implemented properties in c# and the prop snippet. Type prop inside the class and you’ll see the following:

image

Hit tab and Visual Studio will generate the code you need and give you some assistance in filling in the key bits. You’ll notice the hightlighted text. You can use tab to cycle through the highlighted sections, which will select the text for you, and you can just over-type your values. Press enter to exit the snippet.

Add the following properties to the class:

  • int UpVotes
  • string Title
  • string Tags

Building the Repository

Right-click on the Models folder and add another class called QuestionRepository. We are going to fake a database here and create a property called Questions. Make the property private and of type IEnumerable<Question>.

image

When the class is created we want Questions to be filled with some data to emulate something like a DataContext you would see if you were using LINQ to SQL. Create a constructor and populate a List<Question> with several questions. This is easy if you use the parameterless constructors of c#. I even just write a blank one, then copy-and-paste it out:

image

Fill in the blanks! For my sample (and in the download) I have added about 10 questions and assigned the list of questions to the class’ private Questions property. I also assigned the UpVotes to a random number between 0-20 so that I could sort off of it and get different results.

Next, add an internal function called FindMatchedQuestions with the following signature:

image

The algorithm for deducing the related questions is not the subject of this article. Though I’ve included some basic logic to make it work in the download, here’s all you need to get going for now:

image

With that inside our ‘find’ function, we’ll always get some random results. Use the downloadable project for search-relevant results.

At this point, let’s build our project (Shift + CTRL + b) to get our models compiled and help out our tooling. If you do not compile, some steps will not be as smooth as I suggest; MVC tooling uses reflection to find types in namespaces, types that don’t exist without an initial compilation

To the Controller

Navigate to Controllers –> HomeController.cs and open it up. Add a new public PartialViewResult to the class called FindRelated that accepts a string parameter. Add the following code to the method, which calls the repository to get some data (the model) and returns a PartialViewResult with said model.

image

Visual Studio 2010 contains some great helpers for the MVCers of the world. While the library is heavily tested, (mostly) strongly-typed, robust and extensive, it also allows us to rely on convention to afford us great convenience in code and in tool support.

The first convenience I used here is part of the framework; by passing my model into the PartialView() that I’m returning, the framework looks for a View that matches the name of my action (FindRelated) and passes the model to it. The executed result is what is returned to the browser (after the framework is done with it).

The second convenience I’m demonstrating comes from the tooling support and the IDE’s awareness of the MVC conventions. On to our View…

Building the Partial View

I’m not going for style points, here, so I’m going to use what we get for free. Let’s use that convention-aware tooling and create our partial view by right-clicking on the Home folder in Views, then selecting Add view…

You’ll see the following:

image

This is okay, but there’s an even better way to do this. Cancel the wizard and go back to your HomeController source file. Right-click anywhere inside the the FindRelated function and click Add View…

Now, you’ll see the already-named, convention-following parameters for creating your view. We’re going to set the wizard to create a partial view that is strongly-typed with the Question class. We’ll also set the View content to List.

image

The template outputs a table for us, which is acceptable, but it won’t look great for our purposes. For now, let’s just clean up a little bit by:

  • Removing the first row which contains the headers
  • Removing the first column in the foreach with the ActionLinks
  • Removing the column that contains the output for item.Tags
  • Removing the P tag at the end of the View with the Create link

If you kill the whitespace in the document you are left with a very short Partial View:

image

To finish off the view, add an H3 tag for a title and set the contents to Related Questions.

A Quick Test

At this point, we’re basically ready to rig up our functionality. If you want to see the results of your work so far, we can just make a request to the controller’s PartialViewResult action and see the list of related content. Press F5 to run the project, then enter the following in your browser (remember to replace your port number!):

http://localhost:PORT/Home/FindRelated?searchText=foo

Looking at the URL we can observe the following interesting things:

  • Home is the name of our controller
  • FindRelated in the name of our action
  • searchText is the name of the parameter in our action
  • The view that is rendered sits in a folder called Home which is the first place the framework looks when resolving views for a controller called HomeController

Through convention and the routing support in MVC 2 and ASP.NET we are able to see our results. If you examine the HTML source you’ll notice there are no HTML, HEAD or BODY tags; they aren’t rendered from partial views.

Here’s the results of my test view:

image

Again, not pretty, but it will do!

Involving the User and Adding Script

The default template for an MVC 2 project includes Index.aspx. Open it to edit from the Views –> Home folder in Solution Explorer.

Delete the H2 tag and the P tag that were so kindly added to the home page for you.

Add two DIVs. One will host the user input control and the other will be the container for our results, so we’ll ID them appropriately. Specifying an ID allows us to work directly with the controls with a simple jQuery selector.

image

Expand the user search container to include brief instructions and a text input.

image

Finally, add your SCRIPT tag so we can insert some JavaScript. First, lets add our function that can update the contents of our results placeholder:

image

Next, let’s add a jQuery handler that is executed when the document is fully loaded. In here, we’ll bind the ‘blur’ event of the text input to a the SubmitQuery function we just wrote:

image

That should be it! Now, just like when you tab out of the question asking box on StackOverflow, your query will be submitted and a list of related questions can come back! Run the app with F5 to see the results. Enter some text and tab out of the text box to see the list load.

image

Wrapping Up

Obviously this is not a complete feature-for-feature exhibit of a related-question section on a site, but it’s a good start. In the download I have improved the search results (somewhat) and styled the results to look a little more like SO’s.

image

You can also extend your model to include an ID, or use a model from a database. With an ID, you could then create a controller action that loads a question, given an ID, and outputs a view that is strongly-typed to render a complete question.

Good luck!

Resources

4 comments:

  1. Great job, James. I'll call you MISTER, even if no one else does.

    ReplyDelete
  2. James,

    Tried to view the content images and download the project but seems that the domain has expired. any chance of linking the images/download to another domain as i'd love to see this in action.

    thanks

    jim

    ReplyDelete
  3. Hey Jim,

    Shoot! Lapsed over the weekend! I think I got it worked out...should be back online now (I can see the images here, not sure if it's resolving everywhere yet).

    Cheers,
    -James

    ReplyDelete
  4. james - all fine here now, thanks again

    jim

    ReplyDelete