Tuesday, June 30, 2009

Writing a Custom NAnt Task (Part 2)

Over the past couple of months, I have been working on writing the SchemaSpy Task for NAnt located on CodePlex.  When I began to create a few custom tasks for NAnt in the past, I found very little documentation about how to create one.  Much of the documentation surrounding how to create a custom task is based solely on examples without any supporting information.  While this works very well for basic things, there were some experiences I had to work through while writing the task for SchemaSpy that I would like to talk about here.  In this, the second of four parts, we'll be extending what we created on Part 1 by adding a FileSet collection to add some flexibility to our application.  In addition, the accompanying source code will remain in both C# and VB.Net similar to the first section in this series.

 

Changing the Input Directory Attribute:

In Part 1 of this series, our Log Combining task contained an attribute to identify the directory that held all files that were to be combined.  What we want to do first is remove this attribute and provide an alternative solution using the NAnt FileSet object.  The reason to change this is to allow us to not be tied to a single directory for combining files, as well as provide a ways to be more selective of the files to combine. For more general information on the FileSet Type of NAnt, make sure to check out the official documentation for FileSet over at the NAnt website.

In order to use a FileSet object, we'll need to import the NAnt.Core.Types namespace.  In addition, we have to instantiate a private FileSet object in order to be exposed by the new property.  The reason for this is that the NAnt engine does not look to ensure that the collection-based objects are instantiated.  On collection-based elements that contains children elements, the NAnt engine ultimately just adds items into the collection.  If the collection is not instantiated, a null-reference error will be thrown.

After the property has been setup and the private object has been instantiated, the code for such should look similar to the following:

   1:  private FileSet _logFileSet = new FileSet();
   2:   
   3:  /// <summary>
   4:  /// The files that will be combined by this task.
   5:  /// </summary>
   6:  [BuildElement("fileset")]
   7:  [StringValidator(AllowEmpty = false)]
   8:  public FileSet LogFileSet 
   9:  {
  10:      get { return _logFileSet; } 
  11:      set { _logFileSet = value; }
  12:  }

 

Once this is done, we can update the project's build file to look something like the below to mimic the same functionality as we had with the previous attribute.

   1:  <logCombiner outputFile="combined.txt" >
   2:      <fileset basedir="..\logs\">
   3:          <include name="**/*" />
   4:      </fileset>
   5:  </logCombiner>

 

Updating the Combining Method to use the FileSet:

Now that we have the task attribute and build file setup, we need to update the code to use the collection. In order to keep things simple, I'm not going to touch the current CombineFiles() method.  Instead, I want to ensure the information I will be getting from the FileSet object will be able to be valid and fit into that method call.

To do this, we need to change the name of the ValidateInputDirectory() method to ValidateInputFile().  This will ensure the name of the method will not confuse other developers.  In addition, we want to change the logic of this method slightly by changing Directory.Exists() to File.Exists().  We will be working with files at this point instead of directories so testing we want to make sure we test for the proper item.

   1:  /// <summary>
   2:  /// Validates the Input Directory Value
   3:  /// </summary>
   4:  /// <param name="path">The directory location to validate</param>
   5:  private void ValidateInputFile(string path)
   6:  {
   7:      if (!File.Exists(path))
   8:      {
   9:          throw new BuildException("The input directory of " + path + " does not exist.");
  10:      }
  11:  }

Next, we need to update the GetInputFiles() method.  This method returns an array of strings representing the file paths.  These paths are then passed to the CombineFiles() method.  We want to keep this signature.  Inside of the method, we need to instantiate an item to hold our string array; like a List<string>.  Next, we need to loop through the FileSet.FileNames collection and add each entry into our string list.  Lastly, we want to make sure the files are validated through our ValidateInputFile() method before returning the string array.  Once all of this is done, the method should look like the following:

   1:  /// <summary>
   2:  /// Retrieves a listing of files from a provided directory
   3:  /// </summary>
   4:  /// <returns>A string array of all file paths.</returns>
   5:  private string[] GetInputFiles()
   6:  {
   7:      List<string> output = new List<string>();
   8:   
   9:      foreach (string path in _logFileSet.FileNames)
  10:      {
  11:          ValidateInputFile(path);
  12:          output.Add(path);
  13:      }
  14:   
  15:      return output.ToArray();
  16:  }

 

Looking Ahead:

At this point, we have updated our NAnt task from using a single input directory to combine all files in the directory to a more robust solution that allows us to specify which files to include and exclude.  We can compile the code and install the task as described in the previous segment for testing.  In the next post in this series, I'll be diving into writing a custom element collection that can be added to our NAnt task in order to provide more opportunities.

 

Source Code:


kick it on DotNetKicks.comShout it

Thursday, June 4, 2009

Writing a Custom NAnt Task (Part 1)

Over the past month, I have been working on writing the SchemaSpy Task for NAnt located on CodePlex.  When I have began to create a few custom tasks for NAnt in the past, I found very little documentation about how to create one.  Much of the documentation surrounding how to create a custom task is based solely on examples without any supporting information.  While this works very well for basic things, there were some experiences I had to work through while writing the task for SchemaSpy that I would talk about them here.  In this, the first of four parts, we'll be going over how to write your first task and add some simple attributes to it. 

To make the example a bit more practical than "Hello World", we'll be creating a log combiner task. In addition to attempting to make a more practical example, the accompanying source code contains both a C# and VB examples and has been refactored to assist in readability.

 

Getting Started Creating the Log Combining Task:

In order to get started we'll need to make sure we have downloaded NAnt.  Once that is downloaded, we can open up Visual Studio and create a new Class Library project.  In this example, we'll call the project NAntLogCombiner.

New Project Window

Once the project is created we need to do 4 things:

  1. Rename Class1.cs to LogCombinerTask.cs
  2. Add a reference to the NAnt.Core.dll
  3. Add the NAnt.Core and NAnt.Core.Attributes namespaces to our file
  4. Have our LogCombinerTask class inherit from the NAnt.Core.Task class and implement its abstract members.

After these 4 steps are completed, we'll be left with code that looks similar to the code below.

using System.IO;
using NAnt.Core;
using NAnt.Core.Attributes;


namespace NAntLogCombiner
{
public class LogCombinerTask : Task
{
protected override void ExecuteTask()
{
throw new System.NotImplementedException();
}
}
}

The NAnt.Core.Task abstract class only requires 1 method to be overridden.  The ExecuteTask() method is called by the NAnt build engine and is where we will need to place our code.  Since we are going to be combining text files, we need 2 things; a directory that holds the files we want to read, and the output file.  For the time being, we'll hard code these variables.  After writing the code that consumes the input files and writes their contents to the output file, we have the following in our LogCombinerTask:


protected override void ExecuteTask()
{
string inputDirectory = @"..\logs\";
string outputFile = "combined.txt";

string[] inputFiles = Directory.GetFiles(inputDirectory);

using (StreamWriter outputStream = File.CreateText(outputFile))
{
foreach (string file in inputFiles)
{
outputStream.Write(ReadFile(file));
}
}
}

private string ReadFile(string fileToRead)
{
StringBuilder returnString = new StringBuilder();

using (StreamReader inputStream = File.OpenText(fileToRead))
{
while (inputStream.Peek() > 0)
{
returnString.AppendLine(inputStream.ReadLine());
}
}

return returnString.ToString();
}

While we're missing a few things (like validating the directory exists, making the paths configurable, etc.), the task builds and will work under its current constraints.  Before we can test this inside of NAnt though, we have to configure the class and project a bit more.


Decorating the Class for NAnt:


While we have a working class for combining text files located in a single directory, it is not something that can be consumed by NAnt.  NAnt provides an assortment of attributes that can be used to decorate class files in order to inform NAnt how each class (and it's members as we'll see) maps to the markup located in a build file.

In our simple class that we have so far, we only have to add the TaskName attribute onto the class declaration. This attribute tasks just the string name of our task that will represent the element inside of a build file.  I'm going to name the task "logCombiner" to be consistent with the casing of other tasks as well as the name and purpose of our project.  With the attribute added, the class declaration part of our code now looks like the following:


[TaskName("logCombiner")]
public class LogCombinerTask : Task

Naming the Assembly:


With our class properly decorated, we're ready to build our task in preparation for testing.  The way that NAnt imports tasks is through dynamic loading of assemblies and then using reflection.  This is a fairly common practice; however, NAnt only imports assemblies that meet the following pattern; "NAnt.*.Tasks.dll".  Our project, by default, does not output it's assembly into this naming convention.  We can easily remedy this by opening the project properties and setting the Assembly Name to "NAnt.LogCombiner.Tasks.dll" as shown in the image below.

Project Properties



Testing the Assembly:


Now that the Assembly is named correctly for NAnt, we can build the project and test is in NAnt.  We can import our task into NAnt by dropping the DLL into NAnt's bin directory or using NAnt's loadtasks task.  For simplicity sake, I'm going to assume our NAnt.LogCombiner.Tasks.dll has been copied to NAnt's bin directory. 


Once the task has been imported, we can call our task in a build file.  Below is an example build file that only calls our new task.


<?xml version="1.0" encoding="utf-8"?>
<project name="Log Combiner" default="default" basedir=".">
<description>This is an example build file.</description>

<target name="default" description="Default Task">
<logCombiner />
</target>
</project>

Before we run this build file, we need to make sure that we have a directory above the current directory called "logs" that contain our text files.  Once our log files are in place, we can run our build file where it will sequentially read the files from the directory and merge them into the combined.txt file.


Congratulations on creating your first NAnt task; however, it's pretty generic and not very configurable.  Let's enhance our task now by moving where we declare our variables for the input directory and output file into task variables instead of being hard coded.



Adding Task Attributes:


In order to add attributes to our task so we can declare where the input folder or output file is located, we simply have to add a couple of properties to our class file and decorate them accordingly.  Let's update our LogCombiner class by adding an InputDirectory property as well as an OutputFile property and update the code to use them appropriately.  In addition to implementing the properties, we also need to decorate them with the appropriate attributes so NAnt will know how to map the XML-based attributes to our object. 


[TaskAttribute("inputDirectory", Required = true)]
[StringValidator(AllowEmpty = false)]
public string InputDirectory { get; set; }

[TaskAttribute("outputFile", Required = true)]
[StringValidator(AllowEmpty = false)]
public string OutputFile { get; set; }

Each of the two properties have 2 attributes associated with it.  The first, TaskName, defines what the task attribute is called inside of the build file and if it's required or not.  This name does not have to match the name of the property.  The second, is a StringValidator to help ensure the integrity of the data that is passed into it.  For the example, the validation on both properties is to just ensure that an empty string is not passed into each attribute.


With these updates made, the only things we have left to do is recompile and deploy the dll, update the build file, and test.  Below is the updated build file:


<?xml version="1.0" encoding="utf-8"?>
<project name="Log Combiner" default="default" basedir=".">
<description>This is an example build file.</description>

<target name="default" description="Default Task">
<logCombiner inputDirectory="..\logs\" outputFile="combined.txt" />
</target>
</project>

Looking Ahead:


In this part of this series, we created a simple task with a few attributes to enhance the customization.  In the next segments, we'll look into what it takes to add our own types for children nodes similar to the NAnt fileset tags to add file separators as well as allow for including and excluding different files.



Source Code:



kick it on DotNetKicks.comShout it

Wednesday, May 27, 2009

SchemaSpy for NAnt

Earlier this year, I talked about a couple of tools to help automate pieces of the development process in some fashion.  One such tool was SchemaSpy, a Java-based database documentation tool.  SchemaSpy is a tool that I love working with; however, always was annoyed of using it in a manual method.  I streamlined this a little bit using a batch script but it still didn't feel quite right when I hooked it up to my automated deployments.  So, I did what any process driven developer would do; I made it work more naturally into NAnt.

I started a custom NAnt task for SchemaSpy.  Given the complexity of using SchemaSpy to begin with, I decided to not seek to have it added to the NAnt contrib or similar libraries around the web.  However, even though I wanted it separate, I did decide to open source it.  So, I create a CodePlex project to host my NAnt task for SchemaSpy.  In it's current version, it only has the bare minimum of options that I have required; however, I am hoping to continue working on it until it's feature complete.  If you are looking for a great way to document your databases in associated with your automated builds with NAnt, take a look at it since it hopefully will provide you options for what you need.

I'm always open for feedback and recommendations on the task.  Hopefully I'll be posting the next release with in the week.


kick it on DotNetKicks.comShout it

Wednesday, May 13, 2009

Expecting More From Example Code

While I'm typing this post, I'm actually on vacation.  A road trip to a destination in hopes to relax and unwind from my daily routines.  However, I had to bring the laptop for informational purposes (of course).  On the road, I was thinking about some of the posts that I have created and also of those that I refer back to.  I also compared a lot of these posts against the increased visibility toward the S.O.L.I.D. principles of Object Oriented design.  The more I thought about it, the more I became inspired on the question of should people who read blogs and go to presentations expect more from the authors?

In my opinion, the presentations and the traditional style of technical blog posts are fine.  I feel like they teach a concentrated concept in a, hopefully, straight forward manner.  Some topics provide more practical implementation; however, how many times have you ran across a demo where the variables were only named Foo and Bar?  Presenting a concept is great and effective; however, should more be provided in the examples of in a supplemental post on how to apply the information "properly"?

For example, I wrote a post on using jQuery for different types of visual effects of your elements last year.  It was a very simple post with a moderate amount of code that focused on the topic and didn't deviate.  I, as the author, left it up to the reader to apply this knowledge into their own works.  What if I was to go 1 step further and do a live demo similar to blogs like Beckelman.net and JankoAtWarpSpeed.com?  Would that help push the concept better?  What if I supplied a zip file with files that apply the concepts learn in a more real-world scenario?  A lot of my more recent posts include VS projects; however, it is usually a direct reflection of the code inside of the post.  In that jQuery effects posts, I could have easily created a simple directory structure containing the proper files in a traditional/standard method (i.e. separating the CSS and JavaScript into their own directories, etc.).  With regards to my .Net posts, should I make sure that all of my code is commented? Refactored properly to meet the SOLID principles (sans my F# code due to it being functional and not oo)? Include Unit Tests?  I know a lot of people, including myself, who learned some skill by taking someone else's code and then tinkering and tweaking it to learn how things work based on their changes.  I'm wondering if more people would used Unit Testing or "proper" OO design if more of the example code in the blogsphere came with such.

I said earlier that presentations and the traditional blog post is fine as is.  The reason why I say this yet question the example code is that the extra stuff (i.e. Unit Tests, Full Comments, properly design class structures) can sometimes sidetrack the post or presentation.  Questions often rise about these ancillary items as opposed to the concept you are attempting to teach.  So, with that said, I still believe that the posts and presentations are fine.

Should we expect more from the Example Code?  In my opinion, yes.  I know I have a lot of work to do since adding such polish is time consuming; however, if it can assist in improving the overall quality of the code someone else uses, then it'd be worth it.

What do you think?  Should those of us that read blog posts and presentations expect more from the example code tied to such?  Should those of us that author posts provide a more comprehensive look at their code in order to encourage better practices?

What do you think?


kick it on DotNetKicks.comShout it

Wednesday, April 8, 2009

Automating Features vs. Fundamentals

I have been in a lot of meetings over the past few years where inefficiencies in a few practices and policies were being discussed.  In terms of software development, there's a lot of things that can be done to improve how the code is written, deployed, and tested.  The meetings began talking about these possible options; however, after about a 10 minute discussion in the first meeting, everyone seemed intent on designing the tools specific to their domain and not the groundwork that is common across all domains.  Now, nothing is wrong for discussing features to standardize an approach and not reinvent the wheel every time; however, I have learned that the features are domain specific (albeit some may be more common than others) and it's the recreation of the fundamental code that saps a developer's energy, focus, and motivation usually. 

Years have went by and still the same people talk about the features without giving any thought into the remedial code that is common and takes the longest usually.  Between debating the aspects of the features and evaluating tools, such turned into analysis paralysis very quickly with very few realizing it.  Soon, the feature advocates turned more into idea zealots; preaching their views while not provided any acknowledgement to other opinions.

Because of all of this talk and no action, I began to run a few tests with some of my colleagues.  There were 3 projects that shared the same core project foundation in it's design but each developer was going to create their own.  For the sake of this story, we'll say that this was going to be a "professional" Hello, World console application that had to handle industry standard command line arguments (i.e. -? and a few others).  Each person took about 5-6hrs writing the basis of their console application; setting the proper output color schemes and the console output for the help information and other fundamental requirements.  Finally it took them about 1hr to do the actual project specific logic. Now, in this example, one could say that Developer-A creates it and then passes it along to the others in a copy/paste sort of way; however, the test needed to be done not only for time purposes but for implementation purposes since all 3 application bases were not consistent.

From these examples, we refined the foundations and eventually abstracted it into a Visual Studio project template.  The next time they they had an opportunity to use the template, it saved them that much time yet again.  This template was only the fundamentals and didn't have any actual features (no consistent way to do things like emailing or FTP services or error handling).  Later versions have added these features into it (or provided a model to make it easy to do a custom implementation) but the original versions were just the fundamentals.

In the end of this experience, the idea zealots were still preaching their features without recommending any solution to their implementation and we became more efficient by automating the fundamentals.  If you're looking to improve efficiency, try to find remedial, repetitive areas in your process and see about automating them.  Start with the fundamentals and then add features to it.  There'll be changes; however, if you Develop > Refactor > and then Abstract what works, you'll be in a much better place than if you stayed in a meeting room and debated about what features should be in the initial version of the script or template.  My Rule of Thumb from experience: Version 0 of any automation script or template should only have the minimum amount of features possible and focus on the fundamentals.


kick it on DotNetKicks.comShout it

Wednesday, April 1, 2009

How I'm Learning F# - Working with .Net Objects and Properties

Over the past few months, I've been hearing more and more about the use of functional programming concepts and also languages like Haskell and F#.  While some of the initial musings that I've read revolved around how the concepts have been around for decades and that it makes financial and scientific applications easier to read and write, I couldn't find a good reason to start learning it for my typical line-of-business application design and development job or even some of my basic hobby projects.  Nonetheless, I kept getting drawn to the concept and have decided to focus on learning it.

This post marks the fourth entry of a series that I'll be writing to discuss how I'm going about learning F#.  While I'm not saying that my method of learning is ideal and should be followed by others, I'm just reporting how I'm going about doing it.  Over the course of this series, my goal is to provide other .Net developers a(nother) resource for learning the F# language as well as apply the language into some non-financial or scientific scenarios.

 

Series Table of Contents:

  1. Finding Resources
  2. Writing the First Application
  3. Interacting with the .Net Framework
  4. Working with .Net Objects and Properties

 

Project Overview:

In this post, I'm going to interact with libraries in the .Net framework to illustrate how to you'll be able to apply F# to functionality that normally may do in another language. Unlike the last project in this series that just brushed on using some static methods of the System.IO.File class, this post will be focusing on the various steps it takes to send an email message and working with the properties of the System.Net.Mail.MailMessage class.  In this example, we'll be doing the following:

  1. Instantiating a new MailMessage object.
  2. Set the To, From, Body, and Subject properties of the object
  3. Attach a text file to the message.
  4. Send the message via a SMTP server.

Throughout the course of this project, we'll be dealing with a large amount of F# syntax as well as a couple of assemblies from the .Net Framework.  This post will hopefully be pretty straight forward in the steps outlined above.

 

Starting With The Values:

To keep this example simple, I'm going to be declaring our To Address, From Address, Subject, and SMTP Server values first thing.  We will be using these values as we begin to set the object properties of our MailMessage object shortly; however, to allow for easier visibility, I'm setting these up prior.

let ToAddress = "MyToAddress@domain.com"
let FromAddress = "MyFromAddress@domain.com"
let Subject = "F# Email Example"
let SMTPServer = "127.0.0.1"

 

Instantiating Objects in F#:

Now that we have our values established, let's open up the System.Net.Mail namespace and instantiate our own MailMessage object. To do this, we do the following snippet of code:

open System.Net.Mail

let mm = new MailMessage()
mm.To.Add(ToAddress)
mm.From <- new MailAddress(FromAddress)
mm.Subject <- Subject
mm.Body <- "Hello, F# Email"

Here we begin to see some new syntax in F#.  The syntax to instantiating a new MailMessage object is very similar to that of C# or VB.Net in that we are setting a value using the new keyword. Next we are calling the MailMessage's To collection proptery's method of Add() to add our address we assigned initially.  After that, we are assigning the MailMessage.From property to a new instance of a MailAddress Object.  Since object properties that are not read-only are mutable (or can change), we use the <- operator to assign a value to the property.  This action is repeated to assign the Subject and Body properties on the last lines of the snippet.

 

Attaching a File:

The last item we have to do with our MailMessage object is to add the attachment.  The file we're going to use is a local text file.  For simplicity sake, we'll use the relative path to the file and assign it to a value first.  Then we'll add the file as a new attachment to our MailMessage object.

let filePath = @".\commadelimitedfile.txt"
mm.Attachments.Add(new Attachment(filePath))

 

Sending the Email:

We now have a perfectly good MailMessage object sitting in memory.  The only thing that's left to do is to send it out.

let client = new SmtpClient()
client.Host <- SMTPServer
client.Send(mm)

The Full Project Source:

   1: #light
   2:  
   3: let ToAddress = "MyToAddress@domain.com"
   4: let FromAddress = "MyFromAddress@domain.com"
   5: let Subject = "F# Email Example"
   6: let SMTPServer = "127.0.0.1"
   7:  
   8: open System.Net.Mail
   9:  
  10: let mm = new MailMessage()
  11: mm.To.Add(ToAddress)
  12: mm.From <- new MailAddress(FromAddress)
  13: mm.Subject <- Subject
  14: mm.Body <- "Hello, F# Email"
  15:  
  16: let filePath = @".\CommaDelimitedFile.txt"
  17: let att = new Attachment(filePath)
  18: mm.Attachments.Add(att)
  19:  
  20: let client = new SmtpClient()
  21: client.Host <- SMTPServer
  22: client.Send(mm)

 

Summary

This has been a very short and simple example on how to interact with the .Net framework objects and their properties.  At this point, some basic and typical tasks that are used in applications could be transposed to F# if you so desired.  With F# being a concise language, it has some benefits over traditional object-oriented languages.


kick it on DotNetKicks.comShout it

Wednesday, March 25, 2009

jQuery, JSON, and ASMX 2.0 Services

A few weeks ago, I had a project that was grounded in the .Net Framework v2.0 and Visual Studio 2005.  The requirements were very focused on usability and speed so AJAX and jQuery were high on my list to use.  Through the project, I learned that there isn't a large amount of information in one place that tells you how to setup a ASP.Net solution that uses jQuery and ASMX services to effectively transmit json data back and forth from the client.  Because of this, I'll attempt to fill this void since there are still many developers and companies out there that have not been able to upgrade to Visual Studio 2008.

In this post, I'll discuss the process of building a plain ASP.Net 2.0 web application project (not web site project) , setting up the necessary entries in the web.config file to utilize the ASP.Net 2.0 AJAX Extensions v1.0, and use jQuery at the client side of the transfers.    In addition, I'll also show to to use the JavaScriptSerializer class and how to write your own custom converter for your objects.

 

A Note About This Post:

This post is focused on Visual Studio 2005 with the ASP.Net 2.0 AJAX Extensions v1.0.  The v3.5 of the extensions that came with ASP.Net v3.5 and Visual Studio 2008 have a few changes.  While I will try to point out the differences, the core of this post is going to be focused on Visual Studio 2005 web application projects and v1.0 of the AJAX extensions.  While we all love focusing on the latest and greatest, I'm aware that there are a large number of companies and professionals out there that are locked into using the older version of the software for a number of reasons.

 

Additions to VS2005 Used in This Post:

This post will be using the following additions to VS2005.  Below are the items and links to their respective installers:

 

Code Downloads for This Post:

 

Creating and Configuring a New WAP:

Now that we have the required additions established, we're ready to create a new ASP.Net 2.0 Web Application Project.  I'm not going to get into the details of how to do this; however, I want to stress that I'm NOT choosing an ASP.Net AJAX Enabled Web Application.  I'm just choosing to create a new, basic ASP.Net Web Application Project.  Now that the project has been created, we need to add a few references and add some information into the Web.Config file.

In the Solution Explorer, we'll need to add a reference to the AJAX Extensions v1.0 library, System.Web.Extensions.dll.  By default, this library is located at C:\Program Files\Microsoft ASP.Net\ASP.Net 2.0 AJAX Extensions\v1.0.61025\ directory.  After adding this reference, we can update our Web.Config file by including a reference to the ScriptHandlerFactory HttpHandler using the following snippet inside of the <System.Web> config section:

   1: <remove verb="*" path="*.asmx"/>
   2: <add verb="*" 
   3:      path="*.asmx" 
   4:      validate="false" 
   5:      type="System.Web.Script.Services.ScriptHandlerFactory, 
   6:            System.Web.Extensions, 
   7:            Version=1.0.61025.0, 
   8:            Culture=neutral, 
   9:            PublicKeyToken=31bf3856ad364e35"/>

By adding the AJAX Extensions reference and updating the web.config file, we're now ready to enable our ASP.Net Web Services (ASMX) to be called from JavaScript.

 

Setting Up an ASMX Web Service:

We have our new WAP setup and configured, it's time to write a simple ASMX web service and configure it so that it will be able to return JSON.  To do this, let's add a web service to our project called JsonService.asmx and add the following code snippet to replace some of the defaults Visual Studio gives you:

   1: using System.Collections.Generic;
   2: using System.Web.Script.Services;
   3: using System.Web.Script.Serialization;
   4:  
   5: [WebService(Namespace = "http://YourNamespaceHere.com")]
   6: [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
   7: [ScriptService()]
   8: public class JsonService : System.Web.Services.WebService
   9: {
  10:     [WebMethod]
  11:     [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
  12:     public string GetCustomer()
  13:     {
  14:         // Method Body
  15:     }
  16: }

Lines 1-3, I imported 3 additional namespaces to use in the code.  System.Collections.Generics will be used in the next section.  System.Web.Script.Services allow us to decorate the service and it's methods as Script methods for the ScriptHandlerFactory to use when making AJAX calls to and from the client.

Line 7 decorates the web service with the [ScriptService()] attribute.

Line 11 decorates the GetCustomer() web method with the [ScriptMethod()] attribute.  This attribute tells the ScriptHandlerFactory that this method is allowed to be called from an Ajax Client.  The properties inside of the attribute, ResponseFormat = ResponseFormat.Json, tells the ScriptHandlerFactory to send the response stream as a json string and not XML or Soap.  If a response to the web service that is not formatted as json, the response will be returned as XML.

At this point, we can create the body of our web method in any fashion as long as it returns a string.  If you are only passing base types, you can skip down to the Talking to the Server Using jQuery section; however, if you want to pass something a bit more complex, I recommend you continue to the next section.

 

Using the JavaScriptSerializer and a Custom Converter:

While passing base types is easy enough, it can be important to pass objects back and forth from the client.  In order to assist with this, the ASP.Net AJAX Extensions v1.0 comes with the JavaScriptSerializer object.  This object has the ability to serialize certain objects into strings representing json objects.  This sounds great and the answer to all of our problem!  Too bad it is very limiting in it's natural state.  It CAN convert Arrays of base types and (from what I can tell) any framework classes that implements IEnumerable<T>.  I haven't experimented with some of the more obscure generic collections; however, I do know that it does concern List<T> and Dictionary<S,V> just fine. 

In order to use the JavaScriptSerializer, you simply instantiate it and call its Serialize() method, passing the object that you wish to serialize.  If this is a string array or an object of type Dictionary<string,string>, it will do all of the heavy conversion for you and give you a nice little string to return to the client as shown below:

   1: // Method Body
   2: Dictionary<string, string> customerInfo = new Dictionary<string, string>();
   3: customerInfo.Add("FirstName", "John");
   4: customerInfo.Add("LastName", "Doe");
   5: customerInfo.Add("EmailAddress", "JohnDoe@Domain.Com");
   6: customerInfo.Add("PhoneNumber", "555-555-1212");
   7:  
   8: return new JavaScriptSerializer().Serialize(customerInfo);

In this code snippet, I have instantiated a new Dictionary<string,string> generic object and populated with the property information for a customer.  Lastly, I instantiate a new JavaScriptSerializer object and call it's Serialize method, passing our customer information into it.  The JavaScriptSerializer will create following string from our dictionary:

   1: {"FirstName":"John","LastName":"Doe","EmailAddress":"JohnDoe@Domain.Com","PhoneNumber":"555-555-1212"}

This is just a simple string that, technically, we could have concatenated ourselves; however, you see how it converts the name-value pairs of the Dictionary object and turns them into a json Object with properties and string values.

Seems pretty simple.  Now, let's turn our customer Dictionary into a CustomerInfo object with the same four properties.  Below is the class definition:

   1: public class CustomerInfo
   2: {
   3:     public string FirstName { get; set; }
   4:     public string LastName { get; set; }
   5:     public string EmailAddress { get; set; }
   6:     public string PhoneNumber { get; set;}
   7: }

Now, if we replace our Dictionary object with our CustomerInfo object we get code that looks similar to the following, easier to read, snippet:

   1: // Method body
   2: CustomerInfo custInfo = new CustomerInfo();
   3: custInfo.FirstName = "John";
   4: custInfo.LastName = "Doe";
   5: custInfo.EmailAddress = "JohnDoe@Domain.Com";
   6: custInfo.PhoneNumber = "555-555-1212";
   7:  
   8: return new JavaScriptSerializer().Serialize(custInfo);

Sadly, running the above code will give you the following error when you attempt to run it:

CircularReferenceError

Since the JavaScriptSerializer doesn't know the definition of our CustomerInfo object, we get this Circular Reference error.  This causes us to go down one of two roads.  We can either turn the object manually back into our Dictionary object, or we can write a custom JavaScriptConverter for our CustomerInfo Class.  The nice thing about a custom converter is that once it's setup to our JavaScriptSerializer, we can then have it convert any number of CustomerInfo classes (or collections of CustomerInfo objects) we may need.

 

Writing A Custom JavaScriptConverter

Writing our own custom JavaScriptConverter is not as difficult as one may first assume.  Inside the System.Web.Script.Serialization namespace, we are provided with an abstract base class that helps us outline the definition and gets us started quickly.  In our project, let's add another class called CustomerInfoConverter.  Have the class inherit from the JavaScriptConverter class and right-click on the class name and select "Implement Abstract Class".  What you get is the following code snippet:

   1: public class CustomerInfoConverter : JavaScriptConverter
   2: {
   3:     public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
   4:     {
   5:         throw new Exception("The method or operation is not implemented.");
   6:     }
   7:  
   8:     public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
   9:     {
  10:         throw new Exception("The method or operation is not implemented.");
  11:     }
  12:  
  13:     public override IEnumerable<Type> SupportedTypes
  14:     {
  15:         get { throw new Exception("The method or operation is not implemented."); }
  16:     }
  17: }

 

The first method the JavaScriptConverter makes us define is the Deserialize method.  This method is used by the JavaScriptSerializer to convert a json object from a client into the expected type of the web method uses as a parameter.  The JavaScriptSerializer automatically converts the json object into a Dictionary<string, object> collection.  Inside this method, you could add our mappings between the Dictionary keys to a new CustomerInfo object's properties and finally return that new instance of the CustomerInfo object.  Below is the method body:

   1: CustomerInfo cust = new CustInfo();
   2: cust.FirstName = dictionary["FirstName"].ToString();
   3: cust.LastName = dictionary["LastName"].ToString();
   4: cust.EmailAddress = dictionary["EmailAddress"].ToString();
   5: cust.PhoneNumber = dictionary["PhoneNumber"].ToString();
   6:  
   7: return cust;

 

The second method the JavaScriptConverter makes us define is the Serialize method.  This method is used during the actual serialize process.  Here, we're creating a new Dictionary<string, object> collection for our CustomerInfo class.  Since our CustomerInfo class will be boxed through the method's parameter, we'll need to properly cast it before we can start adding the values to the Dictionary object.  Below is the method body:

   1: // Cast the obj parameter
   2: CustomerInfo cust = obj as CustomerInfo;
   3:  
   4: if (cust != null)
   5: {
   6:     Dictionary<string, object> result = new Dictionary<string, object>();
   7:     result.Add("FirstName", cust.FirstName);
   8:     result.Add("LastName", cust.LastName);
   9:     result.Add("EmailAddress", cust.EmailAddress);
  10:     result.Add("PhoneNumber", cust.PhoneNumber);
  11:  
  12:     return result;
  13: }
  14:  
  15: // If the obj doesn't convert for some reason, return an empty dictionary.
  16: return new Dictionary<string, object>();

 

The last item the JavaScriptConverter makes us define is the SupportedTypes property of type IEnumerable<Type>.  This property should be a collection of types that this converter supports.  Since we are using this converter only for our CustomerInfo class, the property can be simplified to the following line of code:

   1: get { return new Type[] { typeof(CustomerInfo) }; }

 

Now that we have our converter built, we can attach it to our serializer to get our json string as shown below:

   1: CustomerInfo cust = new CustomerInfo();
   2: cust.FirstName = "John";
   3: cust.LastName = "Doe";
   4: cust.EmailAddress = "JohnDoe@Domain.com";
   5: cust.PhoneNumber = "555-555-1212";
   6:  
   7: JavaScriptSerializer jss = new JavaScriptSerializer();
   8: jss.RegisterConverters(new CustomerInfoConverter[] { new CustomerInfoConverter() });
   9: return jss.Serialize(cust);

 

Talking to the Server using jQuery:

Now that we have our server-side infrastructure setup, we can begin to write our AJAX client-side code using jQuery.  Compared to all of the code we've written, this is the easier part.  Below is the JavaScript/jQuery code that can be used to call our web service:

   1: function GetCustomerFromServer()
   2: {
   3:     $.ajax({
   4:         type: "POST",
   5:         url: "/JsonService.asmx/GetCustomer",
   6:         dataType: "json",
   7:         data: "{}",
   8:         contentType: "application/json; charset=utf-8",
   9:         success: function(msg){
  10:             var custInfo = eval("(" + msg + ")");
  11:             alert(custInfo.FirstName);
  12:         }
  13:     });
  14: }

In this client-side code, we're making a HTTP Post call to our web service by calling the web method of it directly.  We are stating that we are sending and receiving data in json format.  Despite the fact that our GetCustomer() web method does not take any parameters, we still need to send an empty json object in order to have json returned. 

Lastly, we write an anonymous method to handle the successful message returned to us.  The message is evaluated in order to be turned into a json object on the client side.  We then validate the object by echoing the FirstName property in an alert box.

One thing to remind, this is for ASP.Net 2.0 Web Services.  The response from ASP.Net 3.5 Web Services IS different in that the response message (msg) has it's content in a property only called "d".  So instead of eval("(" + msg + ")"), it would be eval("(" + msg.d + ")").

Summary:

This post has covered a large amount of steps to get a json-based web service infrastructure setup using jQuery and ASP.Net 2.0 web services.  After a lot of low level research and asking questions to people, I realized that there was not a single location for this information. Hopefully, this post will help fill that gap.


kick it on DotNetKicks.comShout it