Saturday, 3 October 2009

Integrating Paypal Payment Pro (Direct Payment)

I've recently had need to integrate PayPal payments on a website, using their Payments Pro interface. It's actually very straightforward, although initally didn't appear so as the information they provide on their website and forums is rather confusing. This is primarily due to the different versions of their API that have been provided historically, plus there are a number of different techniques to access it.

All I need was a simple request to authorise a set of card details, getting back a response to allow the order to either be processed appropriately.

Pulling together with my own code a few different examples online, the Paypal documentation and some advice from their technical support led me to this function - which I'll post in the hope of helping rather than hindering someone else in the same position!


    1 private void makePayPalPayment()


    2 {


    3   //Open log file


    4   TextWriter objLogFile;


    5   objLogFile = File.AppendText(ConfigurationManager.AppSettings["PaypalLogFilePath"]);


    6   try


    7   {


    8     objLogFile.WriteLine("Paypal request commenced: " + DateTime.Now.ToString());


    9 


   10     //Get details for request   


   11     // - get paypal endpoint and authorisation details


   12     string endPoint = ConfigurationManager.AppSettings["PayPalEndPoint"];


   13     string username = ConfigurationManager.AppSettings["PayPalUserName"];


   14     string password = ConfigurationManager.AppSettings["PayPalPassword"];


   15     string signature = ConfigurationManager.AppSettings["PayPalSignature"];


   16     // - get IP of current user


   17     string ip = Request.ServerVariables["REMOTE_ADDR"];


   18     // - get customer details (from form fields)


   19     string cardType = ddlCardType.SelectedItem.Value;


   20     string cardNumber = txtCardNumber.Text;


   21     string expDate = ddlExpiryDateMonth.SelectedItem.Value + ddlExpiryDateYear.SelectedItem.Value;


   22     string securityCode = txtSecurityCode.Text;


   23     string firstName = txtFirstName.Text;


   24     string lastName = txtLastName.Text;


   25     string street = txtStreet.Text;


   26     string city = txtCity.Text;


   27     string state = txtState.Text;


   28     string zip = txtZip.Text;


   29     string countryCode = ddlCountry.SelectedItem.Value;   


   30     // - get order details (hardcoded in this example)


   31     decimal orderAmount = 10.0;


   32     string currencyCode = "GBP";


   33 


   34     //Build request


   35     StringBuilder request = new StringBuilder();


   36     request.Append("VERSION=52.0");


   37     request.Append("&USER=" + HttpUtility.UrlEncode(username));


   38     request.Append("&PWD=" + HttpUtility.UrlEncode(password));


   39     request.Append("&SIGNATURE=" + HttpUtility.UrlEncode(signature));


   40     request.Append("&METHOD=DoDirectPayment");


   41     request.Append("&PAYMENTACTION=Sale");


   42     request.Append("&AMT=" + orderAmount.ToString("F"));


   43     request.Append("&IPADDRESS=" + HttpUtility.UrlEncode(ip));


   44     request.Append("&CREDITCARDTYPE=" + HttpUtility.UrlEncode(cardType));


   45     request.Append("&ACCT=" + HttpUtility.UrlEncode(cardNumber));


   46     request.Append("&EXPDATE=" + HttpUtility.UrlEncode(expDate));


   47     request.Append("&CVV2=" + HttpUtility.UrlEncode(securityCode));


   48     request.Append("&FIRSTNAME=" + HttpUtility.UrlEncode(firstName));


   49     request.Append("&LASTNAME=" + HttpUtility.UrlEncode(lastName));


   50     request.Append("&STREET=" + HttpUtility.UrlEncode(street));


   51     request.Append("&CITY=" + HttpUtility.UrlEncode(city));


   52     request.Append("&STATE=" + HttpUtility.UrlEncode(state));


   53     request.Append("&ZIP=" + HttpUtility.UrlEncode(zip));


   54     request.Append("&INVOICEID=" + HttpUtility.UrlEncode(order.ID.ToString()));


   55     request.Append("&COUNTRYCODE=" + HttpUtility.UrlEncode(countryCode));


   56     request.Append("&CURRENCYCODE=" + HttpUtility.UrlEncode(currencyCode));


   57     objLogFile.WriteLine("Request: " + request.ToString());


   58 


   59     //Set up flags for success of operation


   60     bool success = false;


   61     string errorMessage = "";


   62 


   63     //Make Paypal request


   64     HttpWebRequest webRequest = WebRequest.Create(endPoint) as HttpWebRequest;


   65     webRequest.Method = "POST";


   66     webRequest.ContentType = "application/x-www-form-urlencoded";


   67     webRequest.ContentLength = request.Length;


   68     StreamWriter writer = new StreamWriter(webRequest.GetRequestStream());


   69     writer.Write(request.ToString());


   70     writer.Close();


   71 


   72     //Get Paypal response


   73     HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();


   74 


   75     //Check status


   76     if (webRequest.HaveResponse && (webResponse.StatusCode == HttpStatusCode.OK || webResponse.StatusCode == HttpStatusCode.Accepted))


   77     {


   78       //Response received, so parse into name value pairs for processing


   79       StreamReader reader = new StreamReader(webResponse.GetResponseStream());


   80       string responseString = reader.ReadToEnd();


   81       objLogFile.WriteLine("Response: " + responseString);


   82       reader.Close();


   83       char[] ampersand = { '&' };


   84       char[] equalsign = { '=' };


   85       string[] pairs = responseString.Split(ampersand);               


   86       for (int i = 0; i < pairs.Length; i++)


   87       {


   88         string[] pair = pairs[i].Split(equalsign);


   89         //One pair will be the acknowledgement (ACK) - if this is not set to failure we know the payment has been authorised


   90         if (pair[0].ToLower() == "ack" && HttpUtility.UrlDecode(pair[1]).ToLower() != "failure")


   91         {


   92           success = true;


   93         }


   94         //One or more messages wil be returned in the case of a failure, so build up list of messages to present to user (or developer :-))


   95         if (pair[0].Length > 13 && pair[0].Substring(0,13).ToLower() == "l_longmessage")


   96         {


   97           errorMessage += "<li>" + HttpUtility.UrlDecode(pair[1]) + "</li>";


   98         }


   99       }


  100     }


  101 


  102     //Display appropriate message to user


  103     if (success)


  104     {


  105       // *** process authorised order ***


  106     }


  107     else


  108     {


  109       lblResultMessage.Text = "Sorry, but your payment transaction cannot be proceed. Please review the following messages and try again, or select another payment method.";


  110       lblResultMessage.Text += "<ul>" + errorMessage + "</ul>";


  111     }


  112   }


  113   finally


  114   {


  115     objLogFile.WriteLine("");


  116     objLogFile.Close();


  117   }


  118 }


Monday, 21 September 2009

MVC/Jquery Calendar

One of the main pluses of working with the ASP.Net MVC framework is of course the separation of concerns it promotes with the split between the controller and the view in the UI. This is not only apparent in the standard method of having the controller obtain the data required for display, and passing this to a view for display as XHTML. The same clear separation of purpose can be made use of when the output of the view is other formats too.

In a recent project I've had need to represent a calendar of events - a simple side bar to the website that displays a calendar view with days that have events associated with them highlighted. Clicking on the day displays the list of events below.

In implementing this feature I made use of the controller and view pattern in three distinct ways.
  1. Having a controller render an XHTML page view
  2. Having a controller render JSON data for use from an AJAX request.
  3. Having a controller render a partial view to support an AJAX page update.
Application Structure

The back-end of this MVC application consisted of a database table of events, accessed via a stored procedure and an ADO.Net repository layer. My controller made calls to obtain data in the form of custom business entities from this repository layer via a service layer.

The controller consists of three methods as illustrated in listing 1.

Listing 1: Controller methods


    1  /// <summary>


    2  /// Renders calendar partial view


    3  /// </summary>


    4  /// <returns></returns>


    5  public ActionResult Calendar()


    6  {


    7      return View();


    8  }


    9 


   10  /// <summary>


   11  /// Renders events for month in JSON format (for AJAX page injection)


   12  /// </summary>


   13  /// <param name="year">Year</param>


   14  /// <param name="month">Month</param>


   15  /// <returns></returns>


   16  public JsonResult ListAsJson(int year, int month)


   17  {


   18      IList<Event> events = eventsService.GetEventList(year, month, 0);


   19      return this.Json(events);


   20  }


   21 


   22  /// <summary>


   23  /// Returns partial view with events for selected day (for AJAX page injection)


   24  /// </summary>


   25  /// <param name="year">Year</param>


   26  /// <param name="month">Month</param>


   27  /// <param name="day">Day</param>


   28  /// <returns></returns>


   29  public ActionResult EventsForDay(int year, int month, int day)


   30  {


   31      IList<Event> events = eventsService.GetEventList(year, month, day);


   32      return View(events);


   33  }




The first, Calendar(), is pretty much as a simple as a controller method gets. Initially I had this retrieving the event data and passing it to the view, but when I realised I wanted to go the AJAX route for display as the calendar is paged from month to month, I decided not to retrieve any data for the initial display. So this simply renders the view.

As the calendar is displayed on most pages of the website, it's called via a RenderAction method coded into the site's master page.

The second ListAsJson() retrieves the events for the supplied year and month and renders them in JSON format. This format, which is native JavaScript of course, can be used directly in the client side code that makes an AJAX call as the months are selected in the calendar.

Finally, EventsForDay() similarly retrieves event data, this time for a specified day. The event data is supplied to a partial view which consists of not a whole XHTML template, but just a fragment. This is then injected into the page using JavaScript.

Listing 2: View XHTML


    1 <div id="calendar-render"></div>


    2 


    3 <script src="<%= Url.Content("~/js/calendar.js") %>" type="text/javascript"></script>


    4 


    5 <div id="calendar-events-display">


    6     <h3></h3>   


    7     <div id="calendar-events-display-events"></div>


    8 </div>




As the major part of the work is via AJAX calls from JavaScript, the view is very simple. Really it just contains a couple of placeholders for the calendar control and event list to be rendered.

Listing 3: JavaScript (jquery) AJAX code


    1 var selectableDates;


    2 var months = new Array(13);


    3 months[0] = "January";


    4 months[1] = "February";


    5 months[2] = "March";


    6 months[3] = "April";


    7 months[4] = "May";


    8 months[5] = "June";


    9 months[6] = "July";


   10 months[7] = "August";


   11 months[8] = "September";


   12 months[9] = "October";


   13 months[10] = "November";


   14 months[11] = "December";


   15 


   16 //Hook-up the jquery datepicker


   17 $(document).ready(function() {


   18     var today = new Date();


   19     getEventsForMonth(today.getUTCFullYear(), today.getMonth() + 1);


   20 });


   21 


   22 //Set up the datepicker


   23 function initializeDatePicker(startDate) {


   24 


   25     //Check if current month - if so use current day rather than 1st of the month


   26     var today = new Date();


   27     if (startDate.getUTCFullYear() == today.getUTCFullYear() && startDate.getMonth() == today.getMonth())


   28         startDate = today;


   29 


   30     $("div#calendar-render").datepicker('destroy'); // Destroy if previously created so render will call again when events wired up


   31     $("div#calendar-render").datepicker({


   32         defaultDate: startDate,


   33         beforeShowDay: renderDay,


   34         onSelect: showEvents


   35     }).datepicker("option", { onChangeMonthYear: getEventsForMonth });


   36 }


   37 


   38 //Render a day as selectable if there's an event on that day


   39 function renderDay(date) {


   40     return [isDateSelectable(date), ""];


   41 }


   42 


   43 //Checks to see if date to be rendered is in the list of selectable dates


   44 function isDateSelectable(date) {


   45     for (var i = 0; i < selectableDates.length; i++) {


   46         if (selectableDates[i].getDate() == date.getDate())


   47             return true;


   48     }


   49     return false;


   50 }


   51 


   52 //Makes AJAX call to get events list for month and populates the selectableDates array


   53 function getEventsForMonth(year, month, inst) {


   54     $("div#calendar-events-display").hide();


   55     selectableDates = new Array();


   56     $.getJSON("/Events/ListAsJson?year=" + year + "&month=" + month, null, function(data) {


   57         var i = 0;


   58         $.each(data, function(index, evt) {


   59             selectableDates[i] = extractDateFromJSONResponse(evt.Start);


   60             i++;


   61         });


   62         initializeDatePicker(new Date(year, month - 1, 1));


   63     });


   64 }


   65 


   66 //Extracts date object from JSON formatted date response


   67 function extractDateFromJSONResponse(jsonDate) {


   68     return new Date(parseInt(jsonDate.substr(jsonDate.indexOf("/Date(") + 6, jsonDate.length - 8)));


   69 }


   70 


   71 //Show events for selected day


   72 function showEvents(dateText, inst) {


   73     var d = new Date(dateText);


   74     var day = d.getDate();


   75     var month = d.getMonth() + 1;


   76     var year = d.getUTCFullYear();


   77     $("div#calendar-events-display").hide();


   78     $("div#calendar-events-display h3").html(day + " " + months[month - 1] + " " + year);


   79     $.get("/Events/EventsForDay?year=" + year + "&month=" + month + "&day=" + day, null, function(data, textStatus) {


   80         $("div#calendar-events-display-events").html(data);


   81         $("div#calendar-events-display-events div:nth-child(odd)").addClass("alt");


   82         $("div#calendar-events-display-events div div:nth-child(odd)").removeClass("alt");


   83         $("div#calendar-events-display").show("fast");


   84     });       


   85 }




The JavasScript code itself makes use of the Jquery UI DatePicker that can be custom styled to provide a very neat client side calendar control. It provides events such as for day rendering and navigating from month to month, that I've hooked into in order to make the AJAX requests to retrieve the events for each month, and for rendering the listing of events for a day when a particular date is selected.

Tuesday, 25 August 2009

Validation Framework with MVC Support (Part 2) - Tests

Following on from the previous post describing a validation framework I'm planning to use on MVC projects, this post extends the development to incorporate a range of tests.

To recap, I've set up a domain model structure that consists of:
  • Entities - simple classes representing the fields and relationships of my business objects. They consist of properties and methods that act only on those properties (i.e. they have no external dependencies.
  • Repository layer - a number of data access methods that provide CRUD methods for retrieving and data from the database and instantiating the entities, and persisting their changes back.
  • Service layer - a layer consisting of methods that sit between the UI and repository layer, passing method calls and objects in between.
These classes combine to provide validation at three levels:
  • Entity validation - these are C# methods defined on the entity object itself to internally validate its fields. They will consist of checks for required fields, range checking and internal field comparisons.
  • Service validation - these are checks made against external dependencies. Examples might be user authorisation or arbitrary business rules such as "customer records can only be saved on a Friday".
  • Database validation - basically wrappers around database exceptions, such as errors caused due to deleting records where foreign key constraints are violated, or entry of duplicate key values.
In order to test the classes created for each entity type, I've set up four folders, each for different types of tests:
  • Entity tests - unit tests on the entity self-validation rules
  • Service tests - unit tests on the service layer, using a mocked repository
  • Controller tests - unit tests on the MVC controller class, using a mocked service layer
  • Integration tests - tests that hit the database, running against a known set of data
An example of each follows.

Customer Entity Test

This test prepares a customer object with a known validation error (a missing name). The rule violations are queried and asserted to check there is a single correct exception reported.


    1 private Customer PrepareCustomer()


    2 {


    3     return new Customer


    4     {


    5         Id = 1,


    6         Name = "Test Customer"


    7     };


    8 }


    9 


   10 [Description("Entity Test"), TestMethod]


   11 public void Customer_InvalidCustomerWithMissingName_GeneratesException()


   12 {


   13     //Arrange


   14     Customer customer = PrepareCustomer();


   15 


   16     //Act


   17     customer.Name = "";


   18 


   19     //Assert


   20     Assert.AreEqual(1, customer.GetRuleViolations().Count);


   21     Assert.AreEqual("Name is a required field.", customer.GetRuleViolations()[0]);


   22 }




Customer Service Test

This unit test acts on a method in the service layer. The idea here is to isolate the function and test it without dependencies - i.e. by mocking the repository layer to the test doesn't actually hit the database. In other words, by mocking the repository, we can say given a defined behaviour in the repository method, does the service method respond appropriately. Here we are checking that the correct exceptions are thrown given and valid and invalid Customer object.


    1 private CustomersService service = new CustomersService(MockCustomersRepository());


    2 


    3 static ICustomersRepository MockCustomersRepository()


    4 {


    5     //Set up test data


    6     IList<Customer> customers = new List<Customer>


    7             {


    8                 new Customer {Id = 2, Name = "Drama"},


    9                 new Customer {Id = 1, Name = "Entertainment"}


   10             };


   11 


   12     // Generate an implementor of IProductsRepository at runtime using Moq


   13     var mockCustomersRepository = new Moq.Mock<ICustomersRepository>();


   14     mockCustomersRepository.Setup(x => x.UpdateCustomer(It.IsAny<Customer>())).Verifiable();


   15 


   16     return mockCustomersRepository.Object;


   17 }


   18 


   19 private Customer PrepareCustomer()


   20 {


   21     return new Customer


   22     {


   23         Id = 1,


   24         Name = "Test Customer"


   25     };


   26 }


   27 


   28 [Description("Service Test"), TestMethod]


   29 public void CustomerService_UpdateValidCustomerWithValidAccount_GeneratesNoExceptions()


   30 {


   31     // Arrange


   32     Customer customer = PrepareCustomer();


   33     User user = new User { Id = 1, Role = new Role { Id = 1, Name = "Administrator" } };


   34 


   35     // Act: Request the update method for customer that will pass validation and with valid account


   36     service.UpdateCustomer(customer, user);


   37 


   38     // Assert: Check the results


   39 }


   40 


   41 [Description("Service Test"), TestMethod]


   42 public void CustomerService_UpdateCustomerWithMissingName_GeneratesRuleExceptions()


   43 {


   44     // Arrange


   45     Customer customer = PrepareCustomer();


   46     customer.Name = "";


   47     User user = new User { Id = 1, Role = new Role { Id = 1, Name = "Administrator" } };


   48 


   49     // Act: Request the update method for customer that will fail validation due to missing name


   50     bool ok = false;


   51     try


   52     {


   53         service.UpdateCustomer(customer, user);


   54         ok = true;


   55     }


   56     catch (RuleException ex)


   57     {


   58         // Assert: Check the results


   59         Assert.AreEqual(1, ex.Errors.Count);


   60         Assert.AreEqual("Name is a required field.", ex.Errors[0]);


   61     }


   62     Assert.IsFalse(ok);


   63 }




Customer Integration Test

This test involves the database, and confirms that the correct information is written to and retrieved from it.


    1 [Description("Integration Test"), TestMethod]


    2 public void CustomerIntegration_UpdateValidCustomerWithValidAccount_UpdatesCustomerDetails()


    3 {


    4     //Arrange           


    5     Customer customer = service.GetCustomer(1);


    6     User user = new User { Id = 1, Role = new Role { Id = 1, Name = "Administrator" } };


    7 


    8     //Act           


    9     customer.Name = "Test Customer Changed";


   10     service.UpdateCustomer(customer, user);


   11     customer = service.GetCustomer(1);


   12 


   13     //Assert


   14     Assert.AreEqual("Test Customer Changed", customer.Name);


   15 


   16     //Revert


   17     customer.Name = "Test Customer";


   18     service.UpdateCustomer(customer, user);


   19     customer = service.GetCustomer(1);


   20     Assert.AreEqual(1, customer.Id);


   21     Assert.AreEqual("Test Customer", customer.Name);


   22 }




Customer Controller Test

In this test we are mocking the service layer, and testing the behaviour of the controller. Again we can say that given a mocked behaviour of the service layer, do the controller methods respond with the appropriate ActionResults (redirects or view rendering).


    1 private ICustomersService CustomersService = MockCustomersService();


    2 


    3 static ICustomersService MockCustomersService()


    4 {


    5     //Set up test data


    6     IList<customer> customers = new List<customer>


    7     {


    8         new customer {Id = 1, Name = "Test Customer"},


    9         new customer {Id = 2, Name = "Test Customer Two"}


   10     };


   11 


   12     // Generate an implementor of ICustomersService at runtime using Moq


   13     var mockCustomersService = new Moq.Mock<ICustomersService>();


   14     mockCustomersService.Setup(x => x.GetCustomer(1).Returns(Customers[0]);


   15     mockCustomersService.Setup(x => x.UpdateCustomer(It.Is<Customer>(t => t.Name == "Invalid"), null)).Throws(new RuleException(new NameValueCollection { { "Name", "Invalid" } }));


   16     return mockCustomersService.Object;


   17 }


   18 


   19 [Description("Controller Test"), TestMethod]


   20 public void CustomersController_EditValidCustomer_ReturnsCorrectRedirectAction()


   21 {


   22     // Arrange: get controller


   23     CustomersController controller = new CustomersController(CustomersService, usersService);


   24     ContextMocks mocks = new ContextMocks(controller);


   25 


   26     // Act: Request the edit action for Customer that will pass validation


   27     var result = controller.Edit(new Customer { Id = 3, Name = "Valid" }, null, null);


   28 


   29     // Assert: Check the results


   30     Assert.IsNotNull(result);


   31     Assert.IsInstanceOfType(result, typeof(RedirectToRouteResult));


   32     Assert.AreEqual("Index", ((RedirectToRouteResult)result).RouteValues["action"]);


   33 }


   34 


   35 [Description("Controller Test"), TestMethod]


   36 public void CustomersController_EditInvalidCustomer_ReturnsViewWithErrorsInModelState()


   37 {


   38     // Arrange: get controller


   39     CustomersController controller = new CustomersController(CustomersService, usersService);


   40     ContextMocks mocks = new ContextMocks(controller);


   41 


   42     // Act: Request the edit action for Customer that will fail validation          


   43     ActionResult result = null;


   44     Customer Customer = CustomersService.GetCustomer(3, CustomerLoad.LoadCustomerOnly);


   45     Customer.Name = "Invalid";


   46     result = controller.Edit(Customer, null, null);


   47 


   48     // Assert: Check the results


   49     Assert.IsNotNull(result);


   50     Assert.IsInstanceOfType(result, typeof(ViewResult));


   51     Assert.AreEqual("Edit", ((ViewResult)result).ViewName);


   52     Assert.IsFalse(controller.ModelState.IsValid);


   53     Assert.AreEqual(1, controller.ModelState.Count);


   54 }




Conclusion

As you can see, this is a very simple, yet fairly comprehensive set of tests on the domain model. Although there's a fair bit of work involved for each set of classes, the patterns are straightforward and hence should be relatively easy for a developer or team to adhere to. Following it I hope is going to lead to a robust domain model, with a consistent and reliable means of validating business rules.

One last note on the integration tests. As they involve hitting the database, they aren't classed as unit tests - they suffer from external dependencies that may lead to brittleness, and they will be a little slower to run due to the need to connect for real to the database. They are therefore a bit more painful to maintain - they can quickly become out of synch with the data, leading to false negative results, and untrustworthy tests.

It's going to take a bit of discipline to maintain this I'm sure - but the way I'm looking to do this is that once I had the bulk of my database structure built, I used the database publishing wizard to generate a script of the full schema and data. This I amended slightly to add statements to drop and create the database, and then checked the file into the solution.

From now on, following any updates to the database schema, I must also make the change to the script. Doing this means that when I come to run the integration tests that are coded to expect particular data values in return, I just need to run the script before running the tests.

Sunday, 9 August 2009

Validation Framework with MVC support

The most recent project I've started to work on is going to require a number of web front-ends for a business. There will be a single database, from which information will be drawn and written to in various ways on the different websites. This situation suggests a robust means of applying and enforcing business rules and validation within a common business logic layer; promoting code re-use across the sites and avoiding the maintenance headache of having rules in the UI layer.

Due to a delayed start of the project, I've had a bit of time to investigate some different options to solve this - and to have a hard look at some of the techniques that have been used in the past where, if I'm honest, this wasn't always best achieved.

CSLA.Net

Firstly, I spent a bit of time with CSLA.Net. This is a robust, tried-and-tested framework for implementing your business objects. It's very powerful and I'm sure would prove a good solution. However in the end I decided against it - for two reasons.

The first was that it was proving tricky to use in a testable manner. It could well be that with perseverance I could have got around this, but it seems that the architecture employed (static methods, encapsulated business and data access) made separation of concerns and mocking/unit testing difficult.

Second was really just a nagging feeling that a lot of the features I would never be likely to use - particular those geared to Windows Forms. The mechanism supports n-level undo, remoting objects and the retrieval and saving of large object graphs - none of which I could see a likely scenario that I would require in the medium term.

Validation Framework

One great thing CSLA.Net would have provided though was a consistent means of handling business object validation - and so this is what I looked to replicate in the solution.

Much of the credit here really needs to go to the author of the recently released ASP.Net MVC Framework book from APress. I can certainly recommend this book highly - it does a great job of explaining not only the MVC framework, but also the patterns techniques deployed in the domain model layer, namely repositories and inversion of control/dependency injection.

The crux of the validation framework I've employed on this project, and no doubt on an ongoing basis, is that a particular business object or entity (e.g. a Customer object) may be validated agaist three groups of criteria:
  1. Self-validation. This would be anything that the simply entity class can validate about itself (e.g. required fields, ranges of valid values, internal field comparisons)
  2. External validation. This would be business rules enforced based on factors outside of the immediate properties and methods of the class (e.g. user authorisations)
  3. Database validation. These are really a special class of external validation that is enforced in the database (e.g. dependent records for deletions, unique fields).
Code wise my domain model layer is implemented as a single C# class library (though some might choose to split into three projects, one for each component).

The business objects or entities are deliberately implemented as simply as possible. They only contain properties, methods that can act on internal fields only (e.g. an Order class that calculated it's own total would be a good candidate), and methods to implement an interface I've set up to enforce consistent self-validation:


    1 using System;


    2 using System.Collections.Specialized;


    3 


    4 namespace DomainModel.Interfaces


    5 {


    6     /// <summary>


    7     /// Interface for all entity classes that perform self-validation


    8     /// </summary>


    9     interface IValidatingEntity


   10     {


   11         NameValueCollection GetRuleViolations();


   12     }


   13 }




    1 using System;


    2 using System.Collections.Specialized;


    3 using DomainModel.Interfaces;


    4 


    5 namespace DomainModel.Entities


    6 {


    7     /// <summary>


    8     /// Class representiting a Title entity and it's self-validation rules


    9     /// </summary>


   10     public class Customer : IValidatingEntity


   11     {


   12         public int Id { get; set; }


   13         public string Name { get; set; }


   14 


   15         /// <summary>


   16         /// Checks class internal members are valid


   17         /// </summary>


   18         /// <returns>Dictionary of any valuations (property/message)</returns>


   19         public NameValueCollection GetRuleViolations()


   20         {


   21             NameValueCollection errors = new NameValueCollection();


   22 


   23             if (string.IsNullOrEmpty(Name))


   24                 errors.Add("Name", "Name is a required field.");


   25             else


   26                 if (Name.Length > 100)


   27                     errors.Add("Name", "The Name field must be 100 characters or less.");


   28 


   29             return errors;


   30         }


   31     }


   32 }




My data access layer is implemented using repositories for each area of the application - in this case a Customer repository that implements an interface:


    1 using System;


    2 using DomainModel.Entities;


    3 


    4 namespace DomainModel.Repository.Abstract


    5 {


    6     /// <summary>


    7     /// Interface for Customers repository


    8     /// </summary>


    9     public interface ICustomersRepository


   10     {


   11         IList<Customer> GetCustomerList();


   12         Customer GetCustomer(int id);


   13         void UpdateCustomer(Customer title);


   14     }


   15 }




    1 using System;


    2 using System.Data;


    3 using System.Data.SqlClient;


    4 using DomainModel.Entities;


    5 using DomainModel.Repository.Abstract;


    6 


    7 namespace DomainModel.Repository.Concrete


    8 {


    9     /// <summary>


   10     /// Customers repository implemented using SQL ADO.Net data access methods


   11     /// </summary>


   12     public class SqlCustomersRepository : ICustomersRepository


   13     {


   14         private string connectionString;


   15 


   16         public SqlCustomersRepository(string connectionString)


   17         {


   18             this.connectionString = connectionString;


   19         }       


   20 


   21         /// <summary>


   22         /// Persists a Customer object to the database


   23         /// </summary>


   24         /// <param name="customer">Customer object</param>


   25         public void UpdateCustomer(Customer customer)


   26         {


   27             using (SqlConnection cn = new SqlConnection(connectionString))


   28             {


   29                 SqlCommand cm = cn.CreateCommand();


   30                 cm.CommandType = CommandType.StoredProcedure;


   31                 cm.CommandText = "updateCusomter";


   32                 cm.Parameters.AddWithValue("@id", customer.Id);


   33                 cm.Parameters.AddWithValue("@name", customer.Name);


   34                 cn.Open();


   35                 cm.ExecuteNonQuery();


   36                 cn.Close();


   37             }


   38         }


   39     }


   40 }




Finally, acting as the bridge between the repository and the UI layer sits a service layer:


    1 using System;


    2 using DomainModel.Entities;


    3 


    4 namespace DomainModel.Service.Abstract


    5 {


    6     /// <summary>


    7     /// Interface for Customers service


    8     /// </summary>


    9     public interface ITitlesService


   10     {


   11         IList<Customer> GetCustomerList();


   12         Customer GetCustomer(int id);


   13         void UpdateCustomer(Customer title);


   14     }


   15 }




    1 using System;


    2 using System.Collections.Generic;


    3 using System.Linq;


    4 using System.Text;


    5 using System.Collections.Specialized;


    6 using DomainModel.Entities;


    7 using DomainModel.Service.Abstract;


    8 using DomainModel.Repository.Abstract;


    9 using DomainModel.Exceptions;


   10 using DomainModel.Interfaces;


   11 


   12 namespace DomainModel.Service.Concrete


   13 {


   14     /// <summary>


   15     /// Customers service layer


   16     /// </summary>


   17     public class CustomersService : ICustomersService


   18     {


   19         private ICustomersRepository repository;


   20 


   21         public CustomersService(ICustomersRepository repository)


   22         {


   23             this.repository = repository;


   24         }


   25 


   47         /// <summary>


   48         /// Validates (at entity, service and database level) and saves a Customer object


   49         /// </summary>


   50         /// <param name="customer">Customer object</param>


   51         /// <param name="user">User making the update</param>


   52         public void UpdateCustomer(Customer customer, User user)


   53         {


   54             NameValueCollection errors = customer.GetRuleViolations();


   55             GetServiceViolations(ref errors, user);


   56             if (errors.Count > 0)


   57                 throw new RuleException(errors);


   58             try


   59             {


   60                 repository.UpdateCustomer(customer);


   61             }


   62             catch (Exception ex)


   63             {


   64                 if (GetDatabaseViolations(ex, ref errors))


   65                     throw new RuleException(errors);


   66                 else


   67                     throw (ex);     // Throw original error if not handled


   68             }           


   69         }


   70 


   71         /// <summary>


   72         /// Checks any service level business rules


   73         /// </summary>


   74         /// <param name="errors">Existing collection of errors</param>


   75         private void GetServiceViolations(ref NameValueCollection errors, User user)


   76         {


   77             if(!user.IsInRole("Edit"))


   78                 errors.Add("Authorisation", "Your account is not authorised to perform this action.");


   79         }


   80 


   81         /// <summary>


   82         /// Handles a database exception triggered after a save.


   83         /// </summary>


   84         /// <param name="ex">Exception triggered by database save</param>


   85         /// <param name="errors">Existing collection of errors</param>


   86         /// <returns>Flag for if any handled database exceptions found</returns>


   87         private bool GetDatabaseViolations(Exception ex, ref NameValueCollection errors)


   88         {


   89             bool handledError = false;


   90             if (ex.Message.IndexOf("UIX_Customers_Name") >= 0)


   91             {


   92                 errors.Add("Database Constraint", "Names of Customers must be unique.");


   93                 handledError = true;


   94             }


   95             return handledError;


   96         }


   97     }




Which can be then brought into the UI layer and represented in the by copying them to the ModelStateDictionary, and hence hooking up with the MVC Framework's standard means of displaying validation errors in views. This last step is best implemented as an extension method in the UI layer, to avoid fixing a dependency on MVC in the domain model.


    1 using System;


    2 using System.Collections.Generic;


    3 using System.Web;


    4 using System.Web.Mvc;


    5 using DomainModel.Exceptions;


    6 


    7 namespace WebUI.ExtensionMethods


    8 {


    9     /// <summary>


   10     /// MVC project extension methods


   11     /// </summary>


   12     public static class ExtensionMethods


   13     {


   14         /// <summary>


   15         /// Extends RuleException (defined in domain model) with a method to copy it's contents to the MVC


   16         /// ModelStateDictionary


   17         /// </summary>


   18         /// <param name="ex">Rule exception object to apply extension to</param>


   19         /// <param name="modelState">MVC model state dictionary</param>


   20         /// <remarks>


   21         /// Implemented in web project to avoid MVC dependency in domain model layer.


   22         /// </remarks>


   23         public static void CopyToModelState(this RuleException ex, ModelStateDictionary modelState)


   24         {


   25             foreach (string key in ex.Errors)


   26                 foreach (string value in ex.Errors.GetValues(key))


   27                     modelState.AddModelError(key, value);


   28         }


   29     }


   30 }




The resulting controller action, that calls the validation and save of the Customer object looks like this:


   42         [AcceptVerbs(HttpVerbs.Post)]


   43         public ActionResult Edit(Customer title)


   44         {


   45             if (ModelState.IsValid)


   46             {


   47                 try


   48                 {


   49                     titlesService.UpdateCustomer(title);


   50                 }


   51                 catch (RuleException ex)


   52                 {


   53                     ex.CopyToModelState(ModelState);


   54                 }


   55             }


   56             if (ModelState.IsValid)


   57                 return RedirectToAction("List");


   58             else


   59                 return View("Edit", title);


   60         }




In summary, I think this going to prove a simple but easily adhered to and and clear pattern for centralised business rules. Will post further once it has been put through its paces in a real application.

Wednesday, 29 July 2009

Interactive "Over-by-Over" Reports using the Guardian Open API

I recently applied for and was given a developer key for the Guardian Open Platform, an initiative from the Guardian newspaper to make available content and data for use by other developers and websites.

My idea was to pull in data from their excellent, and humourous, "over-by-over" coverage of the Ashes cricket, to display on an interactive chart. You would be able to select a particular batsmen and match, view their innings graphically and be able to hover over each over to see the score, state of play and commentary from the time.

And I'm pleased to say it came together very well. Please click to view the result - interactive over-by-over reports - and let me know your thoughts.

Finally, I've also set up a related blog post if you are interested in how it was put together.

Building Interactive "Over-by-Over" Reports

As mentioned in a related post, I've been granted a developer key for the Guardian Open Platform and have used it to put together a website displaying interactive cricket scores and reports. This post details how I've put this together.

Web Service Back End

The application is in two parts. My initial thoughts were to build this purely on the client side using jquery - the API provides JSON as well as XML response formats. But of course this would mean exposing my developer key to anyone who thought to "view source"... and I expected the Guardian wouldn't be too pleased with that. So instead I set up a web service, that will be called by the client side code AJAX style, in order to protect this information.

I also took this opportunity to simplify the output going to the client side code to what I really needed, in order to ease the complexity of the JavaScript manipulations.

The API provides a means of loading the full details of any piece of content via an ID, and a URL of the form:
http://api.guardianapis.com/content/item/[item]?api_key=[key]

So first step was to find the IDs for each of the matches. I found the easiest way to do this was not to actually use the API explorer, but to find the page on the site, view source and search for
"http://www.guardian.co.uk/email/". The number that comes after this URL (used for the "send to friend" feature), is the ID I wanted.

Having stored these in my web.config file, I built a web service that takes the test match number (1-5), accesses each of the 5 reports (once for each day) and loads them into an XMLTextReader:


    1 string apiKey = ConfigurationManager.AppSettings["APIKey"];


    2 int match = 0;


    3 if (int.TryParse(Request.QueryString["match"], out match))


    4 {


    5     string[] contentItems = GetContentItemsForMatch(match);


    6     foreach (string contentItem in contentItems)


    7     {


    8         string url = "http://api.guardianapis.com/content/item/" + contentItem + "?api_key=" + apiKey;


    9         XmlTextReader reader = new XmlTextReader(url);


   10         ...




As it turns out, although the content is well tagged as XML, the body copy all sits within a single node (though paragraphs are marked up). So I extracted the node I needed:

    1 if (reader.NodeType.ToString() == "Element" && reader.Name == "body")


    2     body = reader.ReadElementContentAsString();




And then carried out some string manipulations to tidy up the content and extract it into a JSON collection of the form:
{overs: [
{
day : 1,
innings : 1,
over : 1,
score : "England 2-0 (Strauss 1, Cook 1)",
batsmen: [
{name : "Strauss", score : 1},
{name : "Cook", score : 1}
],
text : "It'll be Mitchell Johnson to bowl The First Ball..."
}
]}
I discovered the API seems to have some form of restriction on frequency of requests, as I would sometimes find my request for day 4 or 5 would fail with a "403 forbidden" message. To get around this I added some error trapping via try/catch/wait/retry, and also some caching to prevent hitting the Guardian services too often.

Finally, having built up the JSON string using a StringBuilder, I output it to the response stream:

    1 Response.ContentType = "text/js";


    2 Response.Write(sb.ToString());





Client Side Code


The front-end of the application consists of a single page, with interaction with the web service achieved using JavaScript, and in particular, jquery.

The core of this is a function wired up to the button click that retrieves the JSON formatted data for the selected match, and parses it to pull out the over objects that correspond to the selected batsman's innings.
function setUpButtonClick() {
$("#show-button").click(function() {
var ddl = $("#match")[0];
var match = ddl.options[ddl.selectedIndex].value; //get selected match
$("#wait-icon").show(); //show ajax "please wait" graphic
$.getJSON("/interactive-obos/services/get-overs/?match=" + match, function(json) {
hideOverDetails(); ///hides any previously selected over information
displayInningsDetails(json.overs); //plots the innings details on the chart
$("#wait-icon").hide();
});
});
}
The function displayInningsDetails() takes the JSON collection over over information and plots it on the chart. This is just a div with a grid background image, and absolutely positioned elements are placed on top of it using jquery append statements.
function displayInningsDetails(overs) {
var ddl;
ddl = $("#batsman")[0];
var batsman = ddl.options[ddl.selectedIndex].value;
ddl = $("#innings")[0];
var innings = parseInt(ddl.options[ddl.selectedIndex].value);
var inningsOver = 0;
$("#display-panel").empty();
for (var i = 0; i < overs.length; i++) {
var j = getBatsmanInOver(overs[i],batsman,innings);
if (j > 0) {
inningsOver++;
$("#display-panel").append("<div class=\"over\" title=\"Runs: " + overs[i].batsmen[j-1].score + "\" style=\"left: " + ((inningsOver - 1) * OVER_WIDTH) + "px; height: " + ((overs[i].batsmen[j-1].score) * RUN_HEIGHT) + "px\"></div><div class=\"text\"><strong>Day " + overs[i].day + "</strong><strong>" + overs[i].score + "</strong>" + overs[i].text + "</div>");
}
}
applyOverHovers();
}

function getBatsmanInOver(over,player,innings) {
if (over.innings == innings && over.batsmen[0].name.toLowerCase() == player.toLowerCase())
return 1;
else
if (over.innings == innings && over.batsmen[1].name.toLowerCase() == player.toLowerCase())
return 2;
else
return 0;
}

And finally, the hover function to display the over information is set up. The text has already been written into a hidden div within the chart, so the steps are to find the one next to the visible bar, extract the HTML and write it to the panel.

function applyOverHovers() {
$("#display-panel div.over").hover(function() {
$("#text-panel div").html($(this).next("div.text").html()).animate({opacity: "show"}, "slow");
}, function() {
$(this).next("div.text").animate({opacity: "hide"}, "fast");
});
}

And that's broadly it! If you'd like to dig-in further, please feel free to download the source code and of course let me know your comments.

Monday, 20 July 2009

Setting Up Ruby on Rails on Vista

Possibly going slightly around the houses, but I'm sure similar to other developers who have started using ASP.Net MVC, I've recently started setting up and taking a look into Ruby On Rails. Only had an quick look so far, and had a read through the rails guides to get started - but already can see why it's proved such a popular environment for web development, how it's influenced the development of ASP.Net MVC, and how it's come to promote the MVC pattern more widely in web development circles.

Also probably oddly for a RoR developer, but similar to others from a Microsoft background, I'm running Vista. There are some useful posts out there for setting up RoR on Vista, but as I found a few issues that weren't covered figured it worth noting here.

MySQL Setup


Whilst setting up Rails I also installed MySQL to use as the database. The link provided above provides all the details for installing this. Note though that when it comes to configuring the installation by running MySQLInstanceConfig.exe make sure to right-click and use the "Run as administrator" option - otherwise the latter parts of the wizard won't be able to complete.

There's also a useful recommended step to make the installation more secure by restricting access to the local machine. This is done by adding a line bind-address=127.0.0.1 to the [mysqld] section of my.ini. Note though that you first need to change the security settings to allow modification of this file (right-click on file, go to Properties, go to Security tab, select Edit and click the Modify option).

RoR With MySQL

Finally had a couple of issues with running a simple RoR application running MySQL. This firstly manifested itself with an error reporting that "libmysql.dll was not found". Copying this file from the MySQL install directory to ruby\bin seemed to fix that.

But still the setup wasn't very stable. Every few time I ran the application the WebBrick web server would crash with Ruby interpreter (CUI) 1.8.6 [i386-mswin32] has stopped working. This seemed to be only the case with the latest version of MySQL though. Removing this and installing the previous version (5.0) seemed to resolve this though, as others have found.