Steve Moseley

"To err is human. To really screw up takes a computer." - Dilbert

JQuery, Asp.Net MVC 2 Multi Project Areas and Other News Minutia

clock April 3, 2010 08:45 by author Steve

jQuery News

I know this nugget is a couple of weeks old now, but did anyone catch the news that Microsoft is throwing its support behind jQuery?

So it seems that if you have been using the Microsoft AJAX tools you are probably not going to be seeing much of an effort to improve those tools in the future. Not that it really matters, since it seems that most developers have abandon that toolset about a year ago, and with the emergance of MVC, they were becoming irrelevant anyway. Also, the earlier versions of the Microsoft AJax framework had performance problems under heavy load and although they have done a lot of work to make improvements, jQuery is still far and away much faster. Plus, when working with designers who are not Microsoft centric, jQuery built a bridge for the developer and designer to work together.

I have several examples on jQuery Ajax calls in case you are interested.

My first official large site using ASP.Net MVC 2

So this past month I started working on my first large MVC web application using MVC 2, and although I have been playing around with for more than a year now, I have learned a lot in the few months. The book that was really helpful was “Pro ASP.Net MVC” Framework by Steven Sanderson. I highly recommend it if you are getting started, but you might want to wait until the MVC 2 revision comes out. If you can't wait for the revision there is the eBook "Asp.Net MVC 2 In Action." This book is good in that it covers some more advanced topics like how to keep you controllers light. I think the two books together will get you well on the way to developing in ASP.Net MVC.

So far the only hang up I had was that I had set up my solution to incorporate multi project areas which was supported in the MVC 2 preview releases of Areas. However, when the RTM came out it was no longer supported. I searched and searched for solutions to my dilemma, but the only thing I could find was post by Jonathon who basically had the same experience I had, and a reference to an obscure message on a message board saying (by what appeared to be some one from the ASP Team) that it was not supported. To date, I haven't found any more formal post or article saying that was not the case.

I looked at trying the MvcContrib’s portable area alternative, and it’s a pretty nice approach, but I would like my views deployed to production as separate files and not as a resource file in a assembly, so I decided not go that route. I think for now, I am just going to include the areas in a single project and when the next version of MVC comes out—pull the different areas out into different projects then—if that is, they will have that feature in future releases.

TFS license for VS 2008

I heard this week, not from someone official, that once Visual Studio 2010 is released, they will also include Team Foundation Server for Visual Studio 2008 as a part of the MSDN license. That good news for me as I have been using the trial version for the past few weeks. I suppose the cool tool now is GIT so maybe I should take a look at that as well. At least for my VS 2010 stuff. My only thing is I am getting kind of tired of having to learn a new source control repository every year, there are other cooler things I could be learning, but I guess that is part of the job.



MVC 2 Strongly Typed HTML Helper and Enhanced Validation Sample

clock March 13, 2010 09:40 by author Steve

In lue of the off the official release of ASP.NET MVC 2 RTM, I decided I would put together a quick sample of the enhanced HTML.Helpers and validation controls.

I am going to use my sample event site where I will have a form so a user can search for information about a certain events. So when the Search page loads the Search action is fired return my strongly typed model. to the view.

   1: [HttpGet]
   2: public ViewResult Search(): public ViewResult Search()
   3: {
   4:     IList<EventsModel> result = _eventsService.GetEventList();
   5:     var viewModel = new EventSearchModel
   6:                         {
   7:                             EventList = new SelectList(result, "EventCode","EventName","Select Event")
   8:                         };
   9:     return View(viewModel);
  10: }

Nothing special here, although I did want to show how to load up a strongly typed drop down list because that hung me up for a little bit. So to that, I am going to pass back a SelectList to the view and my HTML helper should no how to load this.

So lets take a look at the mark up for the view.

   1: <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
   2: Inherits="System.Web.Mvc.ViewPage<EventsSample.Models.EventSearchModel>" %>
   3:  
   4: <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
   5:     Search
   6: </asp:Content>
   7:  
   8: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
   9:  
  10:     <h2>Search for Events</h2>
  11:  
  12:     <% using (Html.BeginForm("Search","Events")) {%>
  13:         <%= Html.ValidationSummary(true) %>
  14:         
  15:         <fieldset>
  16:             <legend>Fields</legend>
  17:             
  18:             <div class="editor-label">
  19:                 <%= Html.LabelFor(model => model.EventNumber) %>
  20:             </div>
  21:             <div class="editor-field">
  22:                 <%= Html.TextBoxFor(model => model.EventNumber) %>
  23:                 <%= Html.ValidationMessageFor(model => model.EventNumber) %>
  24:             </div>
  25:             
  26:             <div class="editor-label">
  27:                 <%= Html.LabelFor(model => model.GuestLastName) %>
  28:             </div>
  29:             <div class="editor-field">
  30:                 <%= Html.TextBoxFor(model => model.GuestLastName) %>
  31:                 <%= Html.ValidationMessageFor(model => model.GuestLastName) %>
  32:             </div>
  33:             
  34:             <div class="editor-label">
  35:                 <%= Html.LabelFor(model => model.EventName) %>
  36:             </div>
  37:             <div class="editor-field">
  38:                 <%= Html.DropDownListFor(model => model.EventName, Model.EventList,"Select Event") %>
  39:                 <%= Html.ValidationMessageFor(model => model.EventName) %>
  40:             </div>
  41:             
  42:             <p>
  43:                 <input type="submit" value="Save" />
  44:             </p>
  45:         </fieldset>
  46:  
  47:     <% } %>
  48:  
  49:     <div>
  50:         <%= Html.ActionLink("Back to List", "Index") %>
  51:     </div>
  52:  
  53: </asp:Content>

 

A nice feature is the scaffolding that MVC has to generate code. I simply right clicked inside my Search() action, inside the EventsController and selected “Add View” and then I selected my strongly typed object that I wanted to pass to the view and also selected that I wanted the content type be “Edit”. With that the aspx page was completely generated, although I did have to go back in and change the textbox for the Event Names to a drop down list of the names to select from.

The new feature with MVC 2 are the strongly typed HTML helpers. So now, my textboxes, drop down list, and validation helpers are all strongly typed to my model.  This features gives you the benefits of intellisense and also makes it easier to debug. “The Gu” has a great post about the feature in case you want more details. The DropDownListFor function to generate the drop down list was a little tricky for me. You first need to use a Lanbda expression to pass in the property you want the selected value assigned to in your model, and then you need to pass in the list directly from the model.

Validations

To validate the form, you can use the strongly type validation HTML helpers which will inspect your model and return errors if the validation fails. The definitions of these rules are set directly on the Model itself so lets take a look.

   1: using System.ComponentModel.DataAnnotations;
   2: using System.Web.Mvc;
   3:  
   4: namespace EventsSample.Models
   5: {
   6:     public class EventSearchModel
   7:     {
   8:         [Required(ErrorMessage = "Please enter the event number.")]
   9:         [RegularExpression(@"\w{6}", 
  10:             ErrorMessage = "The Event Number must be 6 letters and/or numbers.")]
  11:         public string EventNumber { get; set; }
  12:  
  13:         [Required(ErrorMessage = "Please enter the guest's last name.")]
  14:         [RegularExpression(@"^[A-Za-zÀ-ÖØ-öø-ÿ1-9 '\-\.]{1,22}$", 
  15:             ErrorMessage = "The gueest's last name must 1 to 20 characters.")]
  16:         public string GuestLastName { get; set; }
  17:  
  18:         public string EventName { get; set; }
  19:         public SelectList EventList { get; set; }
  20:     }
  21: }

Pretty cool!

Okay, the only thing left to do is perform the validation in the POST action.

   1: [HttpPost]
   2: public ViewResult Search(EventSearchModel eventSearchModel)
   3: {
   4:     if (ModelState.IsValid) return View("SearchResults");
   5:     else
   6:     {
   7:          IList<EventsModel> result = _eventsService.GetEventList();
   8:         eventSearchModel.EventList = new SelectList(result, "EVentCode","EventName");
   9:  
  10:         return View(eventSearchModel);
  11:     }
  12: }
  13:     }

 

If the form entries are valid, here I am simply displaying the SearchResult, but in a real world sample I would also go out get the results first. You get the idea though. In my case, when the form is not valid, I also had to reload my SelectList with the event names before I loaded the page again. Remember this is MVC, no _VieState here :)

So that’s it. Now my form is validating the data and when it fails it looks like this.

response



Spark View Engine – Render Partial While Passing the Model

clock January 5, 2010 15:31 by author steve

Just a quick follow up on my previous post specifically about rendering partial views using the Spark View Engine.  If you want to use a partial view and you also want to pass it a model, you just create parameter and pass the model in that parameter.

 

As I stated, the convention for the shared content is to create a file in the “Shared” view directory and prefix the file with the “_”.

 

For example:

I have created a post list partial file and I am going to call it from my “Index” view in the “Home” folder.

renderpartialsamp1

 

In the HomeController class, the Index action is just returning 10 items in a IList collection.

   1: public ActionResult Index()
   2: {
   3:     IList<PostDto> items = _postService.GetMostRecentPosts();
   4:  
   5:     ViewData["BasicPosts"] = items;
   6:     return View();
   7: }

Next, my Index view passes the collection of posts to the partial control.  The Index.spark file has the following code.

   1: <viewdata BasicPosts="IList<PostDto>">
   2:  
   3:  
   4: <postList posts="BasicPosts" />

 

Now the model is in the partial file and I can do with it what ever I want. In this example, the _postList.spark file has the following code that loops through the collection of posts and displays the “Title”.

   1: <ul>
   2: <for each="var post in posts">
   3:     <li>${post.Title}</li>
   4: </for>
   5: </ul>

 

The above sample produces this partial view.

partialResult



Building an List of Links Asynchronously Using jQuery, AJAX, PURE, and Spark View Engine for MVC

clock December 27, 2009 13:05 by author steve

Yes, I know the title is a mouthful, but its all the cool stuff I used to make this trick work.  I have mentioned before that I have been using the Spark View Engine with ASP.Net MVC just because I think it is much cleaner markup when it comes to intermingling HTML with C#.  I also mentioned that I have been working on my blog app and like my own home blog page, I want to have a blog roll.  So what I did was create a table with two records containing information about my two favorite blogs.

blog_table

Now what I want to do is on the Master Page that I am using is have to these two links display on the page asynchronously after the base HTML loads.

The Spark Master Page and Partial Rendering

There is pretty good documentation for Spark for getting started, so I won’t go into detail about it here; but there are a few things I wanted to mention.  The default way to use a Master Page in the Spark View Engine is to create a folder named “Layouts” inside the “View” folder and then to add a file named “Application.spark” in that folder.  This is the convention that will allow all other views to access that Master Page unless otherwise specified.

   1: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
   2: <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
   3:   <head>
   4:     <title>${H(Title)}</title>
   5:     <link rel="stylesheet" href="~/Content/Site.css" type="text/css" />
   6:     <use content="head"/>
   7:      <script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.3.2.min-vsdoc.js" type="text/javascript"></script>
   8:     <script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.3.2.min.js" type="text/javascript"></script>
   9:     <script src="/Scripts/pure.js" type="text/javascript"></script>
  10:   </head>
  11:   <body>
  12:     <div id="wrapper">
  13:         <div id="header">
  14:             <div id="logo">
  15:                 <h1>Website Logo Goes Here</h1>
  16:             </div>
  17:             <div id="search">
  18:                 Search goes here
  19:             </div>
  20:         </div>
  21:         <div id="header_menu">
  22:             <ul id="menu">
  23:                 <li>
  24:                     <a title="" accesskey="1" href="#">Home</a>
  25:                 </li>
  26:                 <li>
  27:                     <span>|</span>
  28:                 </li>
  29:                 <li>
  30:                     <a title="" accesskey="2" href="#">Blog</a>
  31:                 </li>
  32:                 <li>
  33:                     <span>|</span>
  34:                 </li>
  35:                 <li>
  36:                     <a title="" accesskey="3" href="#">About Me</a>
  37:                 </li>
  38:                 <li>
  39:                     <span>|</span>
  40:                 </li>
  41:                 <li>
  42:                     <a title="" accesskey="4" href="#">Contact</a>
  43:                 </li>
  44:             </ul>
  45:             <div id="menu_spacer">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
  46:             </div>
  47:         </div>
  48:         <div id="content">
  49:             <div id="colOne">
  50:                 <div class="box">
  51:                      
  52:                     <myLinks />
  53:                 </div>
  54:             </div>
  55:             <div id="colTwo">
  56:                 <use content="view"/>
  57:             </div>
  58:         </div>
  59:         
  60:     
  61:   </body>
  62: </html>

Some things to notice on the master page here, is that I am accessing jQuery, and PURE.  Of course, jQuery is the JavaScript framework that makes almost everything JavaScript related really easy.  The other script tag for PURE is also a JavaScript framework but it is mainly to make the mapping of markup to your JSON response much easier.  We use it a lot where I work, so I have become a big fan of it.  I will demonstrate that later.

Also another thing to notice in this code is the tag <myLinks />.  This is how Spark does partial HTML rendering.  The convention for partial rendering in Spark is to create a file in the “Views/Shared” folder that is prefixed with “_” and has the extension “spark” like all the other views.  So for my example above, I have a file located at “<project_location>/View/Shared/_myLinks.spark”.

 

The Controller Class

Nothing special here. I have a controller class that returns a JSON response.  The only thing I should mention though is in order for PURE to work with a list of records you will need to rap your List object in another object.  Also, as mentioned in a previous post, I need to return a JsonResult to get the properly formatted response.  Since these links won’t change much (I am not that fickle) I am caching this action using the OutputCache attribute.

   1: using System.Collections.Generic;
   2: using System.Web.Mvc;
   3: using Aviblog.Core.Dto;
   4: using Aviblog.Core.Services;
   5:  
   6: namespace Aviblog.Web.Controllers
   7: {
   8:     public class LinksController : Controller
   9:     {
  10:         private readonly ILinksService _linksService;
  11:  
  12:         public LinksController(ILinksService linksService)
  13:         {
  14:             _linksService = linksService;
  15:         }
  16:  
  17:         [AcceptVerbs(HttpVerbs.Get)]
  18:         [OutputCache(Duration = 60, VaryByParam = "None")]
  19:         public JsonResult All()
  20:         {
  21:             Links links;
  22:             IList<LinkDto> result = _linksService.GetActiveLinks();
  23:             links = result != null ? new Links() {LinkList = result} : null;
  24:             return Json(links);
  25:         }
  26:     }
  27: }

My Links class looks like this:

   1: using System.Collections.Generic;
   2:  
   3: namespace Aviblog.Core.Dto
   4: {
   5:     public class Links
   6:     {
   7:         public IList<LinkDto> LinkList { get; set; }
   8:     }
   9: }

This will generate a JSON response as follows:

{"LinkList":
[{"LinkId":1,"Title":"Scott Gunthie","Description":"Scott Gunthrie\u0027s Blog","BlogUri":http://weblogs.asp.net/scuttgu/,
"FeedUri":null,"IsActive":true},{"LinkId":2,"Title":"Scott Hanselman",
"Description":"Scott Hanselman\u0027s Blog","BlogUri":"http://www.hansleman.com/","FeedUri":null,"IsActive":true}]}

Notice, that I now have my wrapper class called LinkList that I can use to tell PURE that this is my collection.

 

The Partial HTML that Loads the Links

So in my _myLinks.spark file I have the JavaScript that is going to make an AJAX call to get the links, and the PURE code that will map the response back to the unordered list.

   1: <script type="text/javascript">
   1:  
   2:     $(document).ready(function() {
   3:         $.ajax({
   4:             type: "GET",
   5:             url: "/Links/All",
   6:             dataType: "json",
   7:             success: function(res) {
   8:                 var $blogRollList = $("ul#blogRollList");
   9:                 
  10:                 var directive = {
  11:                     'li':{
  12:                         'link<-LinkList':{
  13:                             'a':'link.Title',
  14:                             'a@href':'link.BlogUri'
  15:                         }
  16:                     }
  17:                 };
  18:                 
  19:                  $blogRollList.render(res, directive);
  20:  
  21:             }
  22:         });
  23:     });
</script>
   2:  
   3:         
   4: <h3>Bloggroll</h3>
   5: <div id="blogRoll">
   6:     <ul id="blogRollList">
   7:         <li><a></a></li>
   8:     </ul>
   9: </div>

I am using the $.Ajax function to make a call out to my controller class “LinksController” and the action “All”.  If the code returns a successful response, the PURE code maps the response to the list.

The $blogRollList variable is the ul tag that I want my response to be loaded into.

The directive variable is how I tell PURE to map my response.  It is saying for each item in the response, create a list tag.  The “link<-LinkList” is saying for the collection LinkList there will be items named “link”.  Inside that declaration I am then using each “link” item that was defined and them mapping it to an anchor tag.

The Result

So now when the page loads I get the result:

blogroll



Syndicating RSS in an ASP.Net MVC app using WCF

clock December 15, 2009 15:26 by author steve

About a month back Rob Conery mentioned that he was going to create a blogging application using MVC, which caused me to think to myself: “SELF! why don’t you try something like that and see what you can come up with?”  So in my spare time when I am not doing real work or at home shuttling kids around, I’ve been playing around with this idea.  I am really happy with my BlogEngine.Net set up and probably will continue to use it even if I were to finish this app but I figure this would be an opportunity to just do something different than I have been doing lately and learn some new things that I have not had a chance to work with as of yet.

So with that in mind, one of the first things I decided to tackle was going about syndicating an RSS feed and as it turns out it is not too terribly difficult to do; although, there are some things I did struggle with—uhhh---but I am getting ahead of myself.

Build you own ActionResult

I mention in my previous post that ASP.Net come with several different controller actions, and the one that I used in the that example was the JsonResult.  Well JsonResult really is just a class that is derived from the abstract class ActionResult.  I can write my own class that derives from ActionResult to return back to the view an RSS feed which is what I am going to do in this example.

   1: using System.ServiceModel.Syndication;
   2: using System.Web.Mvc;
   3: using System.Xml;
   4:  
   5: namespace Aviblog.Core.ActionResults
   6: {
   7:     public class RssResult : ActionResult
   8:     {
   9:         private readonly SyndicationFeed _feed;
  10:  
  11:         public RssResult(SyndicationFeed feed)
  12:         {
  13:             _feed = feed;
  14:         }
  15:  
  16:         public override void ExecuteResult(ControllerContext context)
  17:         {
  18:             context.HttpContext.Response.ContentType = "application/rss+xml";
  19:             var formatter = new Rss20FeedFormatter(_feed);
  20:  
  21:             using (XmlWriter writer = XmlWriter.Create(context.HttpContext.Response.Output))
  22:                 if (writer != null) formatter.WriteTo(writer);
  23:         }
  24:     }
  25: }

So in my custom RssResult that inherits from ActionResult, I need to then override the ExecuteResult method.  Inside that method I then need to set the response type to “application/rss+xml” so it can be rendered as RSS.  The next thing to notice is that in my constructor I am passing in an object of type SyndicationFeed which is a part of WCF.  It is in the System.ServiceModel.Syndication namespace.  You can find it by referencing the System.ServiceModel.Web assembly.  I can then use the Rss20FeedFormatter to generate XML which is RSS compliant.

 

Building the SyndicationFeed

Building the SyndicationFeed object is where I had the most trouble. If you just want a simple compliant RSS feed, this guy works great; however, as you can see just from looking at my own RSS feed there many other elements you can extend to an RSS feed and adding those to the SyndicationFeed element is a bit tricky.

   1: public SyndicationFeed Buildfeed()
   2:         {
   3:             var settingQry = _settingsRepository.GetSettings();
   4:             const string nameKey = "name";
   5:             const string nameDescription = "description";
   6:             const string link = "link";
   7:             string blogTitle = _settingsService.GetValue(settingQry, nameKey);
   8:             string blogDescription = _settingsService.GetValue(settingQry, nameDescription);
   9:             Uri blogUrl = _settingsService.GetUri(settingQry, link);
  10:  
  11:             var feed = new SyndicationFeed(blogTitle, blogDescription, blogUrl);
  12:  
  13:             foreach (var element in _feedRepository.GetFeedElements())
  14:                 feed.ElementExtensions.Add(BuildXmlElement(element));
  15:  
  16:             feed.Items = LoadPosts(_postService.GetMostRecentPosts());
  17:             return feed;
  18:         }
  19:  
  20:         private static XmlElement BuildXmlElement(FeedElementDto element)
  21:         {
  22:             var doc = new XmlDocument();
  23:             XmlElement feedElement = doc.CreateElement(element.Prefix, element.Element, element.Namespace);
  24:             feedElement.InnerText = element.InnerText;
  25:             return feedElement;
  26:         }

From the code you can see that when I am instantiating the SyndicationFeed object I am passing in the title of the blog, the blog description, and the URL.  Where it got a bit hairy, was adding other elements that prefixed a common namespace.  I could add the namespace for each element (as seen in the code) but there does not seem to be a way to put the common namespaces in say the channel element and then just use the prefix to those namespaces later. I ended up having to repeat the same namespaces over again.  But that being said, it is still pretty easy to build the feed.

Once the feed object is ready I then could add each of the posts by mapping them to a SyndicationItem object and then add those collection of objects to the items collection in the feed.

   1: private static IEnumerable<SyndicationItem> LoadPosts(IEnumerable<PostDto> posts)
   2:         {
   3:             IList<SyndicationItem> items = new List<SyndicationItem>();
   4:             foreach (var post in posts)
   5:             {
   6:                 var item = new SyndicationItem();
   7:                 item.Title = new TextSyndicationContent(post.Title);
   8:                 item.Authors.Add(new SyndicationPerson(post.Author));
   9:                 item.Content = SyndicationContent.CreateHtmlContent(post.PostContent);
  10:                 items.Add(item);
  11:             }
  12:             return items;
  13:         }

 

The Controller Class

Now that I have my RssResult and my functionality to build the SyndicationFeed object, I can then create an action inside my controller class that puts the two together.

   1: public class SyndicationController : Controller
   2:     {
   3:         private readonly IRss20SyndicationService _rss20SyndicationService;
   4:  
   5:         public SyndicationController(IRss20SyndicationService rss20SyndicationService)
   6:         {
   7:             _rss20SyndicationService = rss20SyndicationService;
   8:         }
   9:  
  10:         public RssResult Rss()
  11:         {
  12:  
  13:             return new RssResult(_rss20SyndicationService.Buildfeed());
  14:         }
  15:     }

 

The Result

So when I browse to domain/controller/action in this case http://localhost:64533/Syndication/Rss I get the following RSS sample:

 

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <rss xmlns:a10="http://www.w3.org/2005/Atom" version="2.0">
   3:   <channel>
   4:     <title>Steve Moseley</title>
   5:     <link>http://www.test.com/</link>
   6:     <description>Trying my best to apprehend all these dev changes.</description>
   7:     <blogChannel:blogRoll xmlns:blogChannel="http://backend.userland.com/blogChannelModule">http://www.avingtonsolutions.com/blog/opml.axd</blogChannel:blogRoll>
   8:     <blogChannel:blink xmlns:blogChannel="http://backend.userland.com/blogChannelModule">http://www.dotnetblogengine.net/syndication.axd</blogChannel:blink>
   9:     <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Steve Moseley</dc:creator>
  10:     <dc:title xmlns:dc="http://purl.org/dc/elements/1.1/">Steve Moseley</dc:title>
  11:     <geo:lat xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">0.000000</geo:lat>
  12:     <geo:long xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">0.000000</geo:long>
  13:     <item>
  14:       <author>Steve</author>
  15:       <title />
  16:       <description>
  17:        blah, blah, blah
  18:       </description>
  19:     </item>
  20:   </channel>
  21: </rss>

 

Looks pretty good, although, if you look at my actual RSS feed and this one, you will notice that the name spaces are repeated verses being listed at the top as they probably should be.  I need to play around with it some more but this gets the job done and I did not have to write all that much code, which is nice.



Filtering Dropdowns in a ASP.Net MVC App using jQuery and jSon.

clock October 24, 2009 14:39 by author Steve

I have often come across the need to load a dropdown list based on the selection of another drop down list on a form.  In web forms it is really easy, especially if you were just doing a regular page post back.  You simply set the dropdown’s AutoPostBack property to true and then on the change event load the second dropdown based on the value of the first event.

Then along came the Ajax Toolkit that had Ajax server controls that would basically do that for you pretty simply without a full page postback.  You could see a demo of that functionality here.

How to do this with MVC

Now in MVC all the cool server controls are gone but with just a little extra work we can get the same functionality pretty easily.  Moreover, using the Json protocol makes the population of the second drop down much lighter accross the wire.

In my example, I have a pretend scenario where the user selects an event from the first drop down and based on the value of the first selection, the options of the second is dropdown is populated.

Here is the view code I am using for this example. (Note: I am using the Sparks View Engine for the View Framework instead of the Microsoft MVC View Framework because it makes the mark up much cleaner.

   1: <viewdata EventList="SelectList" />
   2:  
   3:  
   4: <content name="head">
   5:     <script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.3.2.min-vsdoc.js" type="text/javascript"></script>
   6:     <script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.3.2.min.js" type="text/javascript"></script>
   7: </content>
   8:  
   9:  
  10:  
  11:  
  12: #using (Html.BeginForm())
  13: #        {
  14:           
  15:  <div>
  16:             <fieldset>
  17:                 <legend>Event Information</legend>
  18:                 <p>
  19:                     <label for="eventname">Event name:</label>
  20:                     ${Html.DropDownList("eventname", EventList)}
  21:                 </p>
  22:                 <p>
  23:                     <label for="eventdate">Event Date:</label>
  24:                     <select id="eventdate">
  25:                         <option value="">Select Date:</option>
  26:                     </select>
  27:                 </p>
  28:                 <p>
  29:                     <input type="submit" value="See Event Info" />
  30:                 </p>
  31:             </fieldset>
  32:         </div>         
  33:           
  34:           
  35: #}

 

If you are wondering why my second selection list is not generated by the HtmlHelper class it is only because I was not able to append to it using jQuery.  Since it really wasn’t needed anyway I just decided not to bother.

The name of my view is called Events and it resides in my Home controller and the code for populates that view looks like this.

   1: public ActionResult Events()
   2:         {
   3:             IList<EventDto> events = new List<EventDto>();
   4:             const int upper = 5;
   5:             for (int i = 0; i < upper; i++)
   6:             {
   7:                 var eventItem = new EventDto() {EventCode = i.ToString(), Eventname = string.Format("Name {0}", i)};
   8:                 events.Add(eventItem);
   9:             }
  10:  
  11:             var eventList = new SelectList(events, "EventCode", "EventName");
  12:             ViewData["EventList"] = eventList;
  13:             return View();
  14:         }

 

Nothing special here.  I am just creating a list of events and then passing it to a SelectList so it can be populated in the view.

So when the page loads the HTML looks like this:

   1:  
   2:                 <legend>Event Information</legend>
   3:                 <p>
   4:                     <label for="eventname">Event name:</label>
   5:                     <select id="eventname" name="eventname"><option value="0">Name 0</option>
   6: <option value="1">Name 1</option>
   7: <option value="2">Name 2</option>
   8: <option value="3">Name 3</option>
   9: <option value="4">Name 4</option>
  10: </select>
  11:                 </p>
  12:                 <p>
  13:                     <label for="eventdate">Event Date:</label>
  14:                     <select id="eventdate">
  15:                         <option>Select Date:</option>
  16:                     </select>
  17:                 </p>
  18:                 <p>
  19:                     <input value="See Event Info" type="submit">
  20:                 </p>
  21:             

And the page looks like this:

page1

The JsonResult Function

ASP.Net MVC comes with different controller actions, one of them being the JsonResult action.  Like the ActionResult, the JsonResult sends data back to the view but in this case, the response is converted into JSON.

   1: [AcceptVerbs(HttpVerbs.Get)]
   2:         public JsonResult EventDates(string eventCode)
   3:         {
   4:             IList<EventDateDto> eventDates = BuildEventDates(eventCode);
   5:             return Json(eventDates);
   6:         }

In writing the function, first I have to tell it action to respond do.  In this case I want it to respond to “GET” requests, so I need to add the AcceptVerbs attribute to the function with the “GET” verb stated.  The function needs to have the parameter for the event code that the user selected in the dropdown, and then based on the parameter the application gets a list of dates.  These collection dates (are formatted as strings in this case) are then passed back by calling the Json() function.

 

The jQuery function:

So in order to get the data for the second dropdown we need to make an AJAX call to the server and pass it the value of the first dropdown when its “change” event is fired.

When the event is fired, we can then use the jQuery $.AJAX call to make a “GET” request to the URL as such:  …/Home/EventDates?eventCode=3.  When you look at the header of the request you also notice that “Accept” attribute is set to “application/json, text/javascript, */*”.

   1: <script type="text/javascript" charset="utf-8">
   2:         $(function(){
   3:           $("select#eventname").change(function(){
   4:                 var data = $(this).val();
   5:                 var json = {eventCode: data};
   6:                 
   7:                 
   8:                 $.ajax({
   9:                   type: "GET",
  10:                   url: "/Home/EventDates",
  11:                   data: json,
  12:                   dataType: "json",
  13:                   error: function(xhr, status, error) {
  14:                     alert("error routine");
  15:                   },
  16:                   success: function(res){
  17:                     var $dropdown = $("select#eventdate");
  18:                     $dropdown.find('option').remove().end();
  19:                     $dropdown.append('<option value="">Select Date</option>');
  20:                     for (var i = 0; i < res.length; i++) {
  21:                         $("select#eventdate").append('<option value="' + res[i].EventDateName + '">' + res[i].EventDateName + '</option>');
  22:                       }
  23:                       
  24:                   }
  25:                 });
  26:           });
  27:         })
  28:         </script>

Notice that the data we are passing must in the request must be in a jSon format.  So in the case the data we are passing is {eventCode:3}.  The response we get back is:

[{"EventDateCode":"10/27/2009","EventDateName":"10/27/2009"},{"EventDateCode":"10/30/2009","EventDateName":"10/30/2009"},
{"EventDateCode":"11/2/2009","EventDateName":"11/2/2009"},{"EventDateCode":"11/5/2009","EventDateName":"11/5/2009"},
{"EventDateCode":"11/8/2009","EventDateName":"11/8/2009"},{"EventDateCode":"11/11/2009","EventDateName":"11/11/2009"},
{"EventDateCode":"11/14/2009","EventDateName":"11/14/2009"},{"EventDateCode":"11/17/2009","EventDateName":"11/17/2009"},
{"EventDateCode":"11/20/2009","EventDateName":"11/20/2009"}]

 

If the response is successful, the callback function takes the collection that is returned and build a string of <option> tags and append it to the select object.

page1after

Update:

In the MVC 2 version the JsonResult response will by default throw an exception. This is because of a subtle vulnerability in which someone could gain access to sensitive information. You can get the details from Phil Haack's post. So the moral of the story is if you passing sensitive imformation in the response, it is probably better for you to make a POST rather than doing GET like I did in this example. If you are not passing sensitive information and you still would to use a GET request then you will need to change the JSon method from:

return JSon(eventDates);

to

return JSon(eventDates, JSonRequestBehavior.AllowGet);



Calendar

<<  September 2010  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

View posts in large calendar

Sign in