Steve Moseley

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

Build a REST Web Application with WCF and jQuery

clock February 20, 2010 09:05 by author Steve

Introduction

So at my work, we started on a new web application, and mainly because a lot of guys working on it were going to be new and also because it is somewhat a visible project; we figured we would keep the risk low and use the traditional web forms ASP.Net application.

On the other hand we still wanted the separation of concerns you get from a MVC style application, and because we were also planming on using designers for the web pages who were very proficient with jQuery but who did not know much about ASP.net. As a matter of fact, most of these guys write jQuery and HTML with a simple text editor…show offs :)

Also, for performance reasons and the fact the web designers were very familiar with the protocol, we wanted to pass JSON objects back and forth from the client to the server and vice versa.

Another goal is we want to keep as little as code as possible in the web project and place it in a different “Core” assembly. This would make deployments for us easier.

The Solution Layout

The solution looks like this:

The EventList.aspx page will be the page I will use for this demo. I deleted the associated code-behind files with and also removed the references of the code-behind from the page deplaration.

When I added the EventService.svc WCF Service into the project, Visual Studio added the EventService.svc.cs file and the IEventService service contract interface. I moved them to the Core project and changed the namespaces.

I also added a folder for the Unity service factory build up. You can see how I did it in a previous post.

The Service

Since I moved the associated files that were added when I added the WCF Service, I have to go in and change the declarations so they point to the proper place.

   1: <%@ ServiceHost 
   2:     Language="C#" 
   3:     Debug="true" 
   4:     Service="RestSample.Core.ServiceContracts.EventsService" 
   5:     Factory="RestSample.Core.ServiceContrainer.UnityServiceHostFactory" 
   6: %>

 

Also the code behind reference was moved and Factory attribute was added to point to my custom Unity Service Host Factory.

The Service Contract

The Service contract needs to be decorated with the ServiceContract attrubute; but I also need to specify that this message will take “POST” requests, and that the protocal will be JSON.

   1: using System.ServiceModel;
   2: using System.ServiceModel.Web;
   3: using RestSample.Core.DataContracts;
   4:  
   5: namespace RestSample.Core.ServiceContracts
   6: {
   7:     [ServiceContract]
   8:     public interface IEventsService
   9:     {
  10:         [OperationContract]
  11:         [WebInvoke(Method = "POST",
  12:             ResponseFormat = WebMessageFormat.Json,
  13:             BodyStyle = WebMessageBodyStyle.Bare)]
  14:         EventList GetLatestEvents();
  15:     }
  16: }

 

The implantation of this contract is injected with my controller where my response will be built up.

Here is the service contract implementation:

   1: using RestSample.Core.Controllers;
   2: using RestSample.Core.DataContracts;
   3:  
   4: namespace RestSample.Core.ServiceContracts
   5: {
   6:     public class EventsService : IEventsService
   7:     {
   8:         private readonly IEventController _eventController;
   9:  
  10:         public EventsService(IEventController eventController)
  11:         {
  12:             _eventController = eventController;
  13:         }
  14:  
  15:         #region IEventsService Members
  16:  
  17:         public EventList GetLatestEvents()
  18:         {
  19:             return _eventController.GetLatestEvents();
  20:         }
  21:  
  22:         #endregion
  23:     }
  24: }

 

Here is the controller class implementation:

   1: using System;
   2: using System.Collections.Generic;
   3: using RestSample.Core.DataContracts;
   4:  
   5: namespace RestSample.Core.Controllers
   6: {
   7:     public class EventController : IEventController
   8:     {
   9:         #region IEventController Members
  10:  
  11:         public EventList GetLatestEvents()
  12:         {
  13:             var list = new EventList {Items = new List<EventItem>()};
  14:             const int upper = 5;
  15:             for (int i = 0; i < upper; i++)
  16:             {
  17:                 list.Items.Add(new EventItem
  18:                                    {
  19:                                        EventCode = i.ToString(),
  20:                                        EventDate = DateTime.Today.AddDays(-1*i).ToLongDateString(),
  21:                                        EventName = string.Format("Name {0}", i)
  22:                                    });
  23:             }
  24:  
  25:             return list;
  26:         }
  27:  
  28:         #endregion
  29:     }
  30: }

Obviously I am just returning back trash, but you get the point. Here are the data contracts:

   1: using System.Runtime.Serialization;
   2:  
   3: namespace RestSample.Core.DataContracts
   4: {
   5:     [DataContract]
   6:     public class EventItem
   7:     {
   8:         [DataMember]
   9:         public string EventCode { get; set; }
  10:         [DataMember]
  11:         public string EventName { get; set; }
  12:         [DataMember]
  13:         public string EventDate { get; set; }
  14:     }
  15: }

 

   1: using System.Collections.Generic;
   2: using System.Runtime.Serialization;
   3:  
   4: namespace RestSample.Core.DataContracts
   5: {
   6:     [DataContract]
   7:     public class EventList
   8:     {
   9:         [DataMember]
  10:         public IList<EventItem> Items { get; set; }
  11:     }
  12: }

 

The web.config Set Up.

Because I want to return JSON, and because I moved the service files; I need to make some changes to the System.ServiceModel section of the web.config.

   1: <system.serviceModel>
   2:     <behaviors>
   3:         <serviceBehaviors>
   4:             <behavior name="RestSample.Core.ServiceContracts.EventsServiceBehavior">
   5:                 <serviceMetadata httpGetEnabled="true"/>
   6:                 <serviceDebug includeExceptionDetailInFaults="false"/>
   7:             </behavior>
   8:         </serviceBehaviors>
   9:   <endpointBehaviors>
  10:     <behavior name="WebHttpBehavior">
  11:       <webHttp />
  12:     </behavior>
  13:   </endpointBehaviors>
  14:     </behaviors>
  15:     <services>
  16:         <service behaviorConfiguration="RestSample.Core.ServiceContracts.EventsServiceBehavior" name="RestSample.Core.ServiceContracts.EventsService">
  17:             <endpoint address="" binding="webHttpBinding" contract="RestSample.Core.ServiceContracts.IEventsService" behaviorConfiguration="WebHttpBehavior">
  18:                 <identity>
  19:                     <dns value="localhost"/>
  20:                 </identity>
  21:             </endpoint>
  22:             <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
  23:         </service>
  24:     </services>
  25: </system.serviceModel>

I added an Endpoint Behavior and specified webHttp and then referenced it in the endpoint. This enables the “web programming model” for WCF which makes the service RESTful.

I also changed the name and contract location of the service so it points to new location in my solution.

Now my service is ready and if I run the service file at this point I get the standard service page to come up to show the everything is configured properly.

The Client

Now that the service is working I am just going to write some jQuery to make a call to the service when a button is click.

Here is the aspx page.

   1: <%@ Page Title="" Language="C#" MasterPageFile="~/Shared/Main.Master" %>
   2: <asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
   3:     <script src="../Scripts/json2.js" type="text/javascript"></script>
   4:     <script src="../Scripts/restsample.js" type="text/javascript"></script>
   5: </asp:Content>
   6: <asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
   7:     
   8:     <input type="button" value="Make Call" id="restCall" />
   9: </asp:Content>

Here is the jQuery

   1:  
   2: $(document).ready(function() {
   3:     setInputFunction();
   4: });
   5:  
   6: function setInputFunction() {
   7:     $('input').click(function() {
   8:         makeEventCall();
   9:     });
  10: }
  11:  
  12: function makeEventCall() {
  13:     $.ajax({
  14:         url: 'http://localhost:56296/Services/EventsService.svc/GetLatestEvents',
  15:         type: "POST",
  16:         processData: false,
  17:         contentType: "application/json",
  18:         timeout: 10000,
  19:         dataType: "text",  // not "json" we'll parse
  20:         success: function(data) {
  21:             loadSuccessful(data);
  22:         }
  23:     });
  24: }
  25:  
  26: function loadSuccessful(data) {
  27:     var result = JSON.parse(data);
  28:     console.log('result', result);
  29: }
  30:  
  31:  

So the jQuery code above makes a call to the service when the button is clicked. Here are some things to notice about this jQuery code.

  • First off, you cannot see it here but I have a script reference to the jQuery 1.4.1 file located on the Microsoft CDN. More on that here.
  • The url is the a combination of the service url and the function GetLatestEvents.
  • I need to specify “POST” as the type because that is what I specified on my ServiceContract.
  • I am going to return back text and then parse it using Douglas Crockford's json2.js.
  • For this demo, I am just going to log the response to the Firebug console using the console.log feature. (Note: remember to take this out later because you will get a JavaScript error in other browsers).

So with I am done and now when I bring up that web page and click the button I get this JSON response back.

{"Items":[{"EventCode":"0","EventDate":"Saturday, February 20, 2010","EventName":"Name 0"},
{"EventCode"
:"1","EventDate":"Friday, February 19, 2010","EventName":"Name 1"},
{"EventCode":"2","EventDate":"Thursday
, February 18, 2010","EventName":"Name 2"},
{"EventCode":"3","EventDate":"Wednesday, February 17, 2010"
,"EventName":"Name 3"},
{"EventCode":"4","EventDate":"Tuesday, February 16, 2010","EventName":"Name 4"
}]}

The response in Firebug looks like this:

response

 

Hope that helps :)



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



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);



Creating a Category Menu Dynamically Using the HoverMenuExtender

clock September 20, 2008 12:24 by author Steve

Introduction

When building an eCommerce website, there are a multitude of ways to display categories an subcategories that are dynamically retrieved from a database.  In this sample, I will show how to display list of categories that when you hover over the category with your mouse, a popup menu will appear with the subcategories.  To do this I am going use the AjaxControlToolKit HoverMenuExtender.

 

The Master Page

I am going to place this menu on my master page so it can be used by all pages related to searching for a product.  The first thing you will always need when using any of the AJAX Controls is the ScriptManager.

   16     <cc1:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server">

   17     </cc1:ToolkitScriptManager>

 The next thing I need to add is the markup for the categories.

   20             <asp:Repeater ID="RepeaterCategories" runat="server" OnItemDataBound="RepeaterCategories_ItemDataBound"

   21                 DataMember="CategoryId">

   22                 <ItemTemplate>

   23                     <asp:Panel class="categoryItem" runat="server" ID="categoryPanel">

   24                         <asp:LinkButton runat="server" ID="linkCategory" Text='<%#Eval("CategoryName")%>'></asp:LinkButton>

   25                         <asp:HiddenField ID="hiddenCategory" runat="server" Value='<%#Eval("CategoryId") %>' />

   26                     </asp:Panel>

   27                     <asp:Panel class="subCategoryList" runat="server" ID="subCategoryPanel">

   28                         <asp:Repeater ID="RepeaterSubCategories" runat="server">

   29                             <ItemTemplate>

   30                                 <div class="categoryItem">

   31                                     <a href="#">

   32                                         <%#Eval("CategoryName")%>

   33                                     </a>

   34                                 </div>

   35                             </ItemTemplate>

   36                         </asp:Repeater>

   37                     </asp:Panel>

   38                     <cc1:HoverMenuExtender ID="HoverMenuExtender1" runat="server" PopupPosition="Right"

   39                         PopupControlID="subCategoryPanel" TargetControlID="linkCategory" HoverCssClass="popupHover">

   40                     </cc1:HoverMenuExtender>

   41                 </ItemTemplate>

   42             </asp:Repeater>

Here I have a repeater control which will repeat for each main category.  Inside the ItemTemplate for that control, I have two panels, one for the main category links and the other which will be the container for the subcategories.  Make sure your style for subcategory panel is defaulted to display equals none and visibility equals hidden.  This will prevent the panel from flashing when the page first loads.  Inside the panel for the subcategories I have a nested repeater which will retrieve the subcategories for each category.  To get the subcategories I will need to fire off the OnItemDataBound event on the main category repeater to make a call to the database and get the subcategories.  The subcategory link would most likely have a link to a product page.

The OnItemDataBound Event

On the OnItemDataBound event,  I need retrieve the current item being bound to the control and if the item is of the appropriate type, then pass the necessary Category Id to my middle tier to retrieve the corresponding subcategories.

   46         protected void RepeaterCategories_ItemDataBound(object sender, RepeaterItemEventArgs e)

   47         {

   48             RepeaterItem item = e.Item;

   49 

   50             if (item.ItemType == ListItemType.Item || item.ItemType == ListItemType.AlternatingItem)

   51             {

   52                 HiddenField category = item.FindControl("hiddenCategory") as HiddenField;

   53                 int catId = Convert.ToInt32( category.Value );

   54                 Repeater subcategoryControl = (Repeater)e.Item.FindControl("RepeaterSubCategories");

   55                 subcategoryControl.DataSource = presenter.OnCategoryItemBound(catId);

   56                 subcategoryControl.DataBind();

   57 

   58             }

   59         }

 In this case I am retrieving the Category ID from the hidden tag placed in the same row as the category name.

 The Result

Okay, so this is not the prettiest sample in the world, but you get the idea.  Perhaps you can jaz this up a bit with a shadow back drop and a rounded corners but the intent was just show how to make it work, and all though I did not show it, I used LINQ  behind the scenes so all in all, there was very little code to do this.

 

 



Using ASP.Net AJAX To Auto Update From Date and To Date textboxes

clock August 16, 2008 09:15 by author Steve

Introduction

If you have ever had to make a hotel reservation on line you may have seen this functionality. Basically there is a field for a check-in date and there is also a field for a check-out date. Typically the websites have cool popup windows with a calendar in it that when selected, the text field is automatically updated with the chosen date. It then gets a little bit more complicated because usually when you select the check in date, the check out-date is automatically updated also.

 Let's take a look at how to a this with ASP.AJAX and the Ajax Control Toolkit CanderExtender.

 The Mark Up

   32 <asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">

   33         <ContentTemplate>

   34             <div class="DatePanel">

   35                 <table>

   36                     <tr>

   37                         <td>

   38                             from date:

   39                             <asp:TextBox runat="server" ID="txtFromDate" onFocus="javascript:this.blur();" Width="80"

   40                                 autocomplete="off" AutoPostBack="true" OnTextChanged="txtFromDate_TextChanged" />

   41                             <asp:ImageButton runat="Server" ID="Image1" ImageUrl="~/images/Calendar_scheduleHS.png"

   42                                 AlternateText="Click to show calendar" /><br />

   43                             <cc1:CalendarExtender ID="calendarButtonExtender" runat="server" TargetControlID="txtFromDate"

   44                                 PopupButtonID="Image1" />

   45                             <asp:RangeValidator ID="RangeValidatorFromDate" runat="server" ErrorMessage="Please enter a date a greater than or equal today's date"

   46                                 ControlToValidate="txtFromDate" Type="Date"></asp:RangeValidator>

   47                         </td>

   48                         <td>

   49                             &nbsp;&nbsp;&nbsp; to date:

   50                             <asp:TextBox runat="server" ID="txtToDate" onFocus="javascript:this.blur();" Width="80"

   51                                 autocomplete="off" OnTextChanged="txtToDate_TextChanged" AutoPostBack="true"/>

   52                             <asp:ImageButton runat="Server" ID="Image2" ImageUrl="~/images/Calendar_scheduleHS.png"

   53                                 AlternateText="Click to show calendar" /><br />

   54                             <cc1:CalendarExtender ID="CalendarExtender1" runat="server" TargetControlID="txtToDate"

   55                                 PopupButtonID="Image2" />

   56                             <asp:RangeValidator ID="RangeValidatorToDate" runat="server" ErrorMessage="Please enter a date a greater than or equal today's date."

   57                                 ControlToValidate="txtToDate" Type="Date"></asp:RangeValidator>

   58                         </td>

   59                     </tr>

   60                 </table>

   61                 <br />

   62                 <br />

   63             </div>

   64         </ContentTemplate>

   65         <Triggers>

   66             <asp:AsyncPostBackTrigger ControlID="txtFromDate" EventName="TextChanged" />

   67             <asp:AsyncPostBackTrigger ControlID="txtToDate" EventName="TextChanged" />

   68         </Triggers>

   69     </asp:UpdatePanel>

The textbox could be wired many different ways, but here is what I did.

  • First off, I set the OnFocus property to blur. This blocks the user's from entering a date in the texbox directly.  I did not want the user to have the ability to enter an incorrectly formated date, so I am only allowing them to enter the date only by using the calendar control.
  • Because I want to update the "to date" when the text changes in the "from date", I have set the OnTextChange event to fire which will map to our function that will do the update.  I have also set the AutoPostback property to true, so it knows to make a postback.  Because we also have this event mapped to UpdatePanel trigger, it will do an asynchronous call to the function causing only this section of the page to update.
  • The ImageButton will fire the popup calendar.
  • The CalendarExtender control them maps the Textbox and the ImageButton so they work in tandom.  Because I am using an ImageButton, the calendar will automatically disappear when the date is selected.
  • I have a RangeValidator that basically checks to make sure the date is not before today's date.
  • The same thing is then done for the "to date" controls.
  • All of this is then wrapped by an UpdatePanel (note: make sure you set the ToolScriptManager to enable partial rendering and you also set the UpdatePanel's UdateMode to "Conditional" for better performance).
  • The triggers are then set to asynchronously post back when text changes.

The Page_Load Event

I would definatelly refactor this code later but for simplicity sake I am just puttong the code right on the page load event.

   19 protected void Page_Load(object sender, EventArgs e)

   20         {

   21             if (!Page.IsPostBack)

   22             {

   23                 RangeValidatorFromDate.MinimumValue = DateTime.Today.ToShortDateString();

   24                 RangeValidatorFromDate.MaximumValue = DateTime.MaxValue.ToShortDateString();

   25                 RangeValidatorToDate.MinimumValue = DateTime.Today.ToShortDateString();

   26                 RangeValidatorToDate.MaximumValue = DateTime.MaxValue.ToShortDateString();

   27                 txtFromDate.Text = DateTime.Today.ToShortDateString();

   28                 txtToDate.Text = DateTime.Today.AddDays(30).ToShortDateString();

   29 

   30             }

   31 

   32 

   33         }

Here all I am doing is setting the RangeValidator exception rules, and I also setting the default date to be today and 30 days from now.

The Text Change Events

   35         protected void txtFromDate_TextChanged(object sender, EventArgs e)

   36         {

   37             DateTime fromDate = Convert.ToDateTime(txtFromDate.Text);

   38 

   39             if (fromDate >= DateTime.Today)

   40                 txtToDate.Text = fromDate.AddDays(30).ToShortDateString();

   41             else

   42             {

   43                 txtFromDate.Text = DateTime.Today.ToShortDateString();

   44                 RangeValidatorFromDate.IsValid = false;

   45 

   46             }

   47         }

   48 

   49         protected void txtToDate_TextChanged(object sender, EventArgs e)

   50         {

   51             DateTime toDate = Convert.ToDateTime(txtToDate.Text);

   52             DateTime fromDate = Convert.ToDateTime(txtFromDate.Text);

   53 

   54             if (toDate < fromDate && toDate >= DateTime.Today)

   55                 txtFromDate.Text = toDate.ToShortDateString();

   56             else

   57             {

   58                 if (toDate < DateTime.Today)

   59                 {

   60                     txtFromDate.Text = DateTime.Today.ToShortDateString();

   61                     txtToDate.Text = DateTime.Today.AddDays(30).ToShortDateString();

   62                     RangeValidatorToDate.IsValid = false;

   63                 }

   64             }

   65 

   66         }

  • When the "from date" is selected, I want to change the "to date" to be 30 days from now.  If the date is less than today, I want to set the "to date" back to today.
  • When the "to date" is selected, if it is less than the "from date", then I want to set the "from date" to be the same date.  If the "to date" is less than today's date, then I want warn the user and set the dates back to the default dates.
 Not too difficult.

 

 

 



Calendar

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

View posts in large calendar

Sign in