Showing posts with label WWW. Show all posts
Showing posts with label WWW. Show all posts

2009-02-12

ASP.NET Development server and PayPal instant payment notification

I am currently developing part of a website where I pass cart information to PayPal to accept payment. PayPal will call back a "secret URL" on my website to allow me to confirm the cart details haven't been tampered with.

I hate installing software I don't need. This is especially the case with software like IIS which just feels so intrusive, which is why I use the ASP.NET Development Server when creating websites. The problem with this server is that it only accepts connections from the local machine. My computer is behind a hardware firewall and my web server wont accept remote connections, so how can I test my PayPal IPN call back?

Simple. Obviously I have to open a port on my firewall and direct it to my machine. So I opened port 80. Then I used this tool to list on port 80 and redirect all traffic to port 10101 (the port my ASP.NET development server was listening on). The result is that a remote computer can now make a HTTP request to my computer, and I can receive it + debug it in Visual Studio without having to install IIS.

The great thing about this port forwarder is that it is only 44KB in size and doesn't require any installation!

2008-03-10

Test Driven MVC and ECO

I have decided that mocking IEcoServiceProvider is not the way to go. Your controller will use the mocked provider during testing but


     
  1. You don’t want to have to mock every service the provider may return, it’s a lot of work!

  2.  
  3. You don’t want your controller using a mocked service, and then the EcoSpace using the real one!



At first I was mocking every possible service request. IUndoService, IPersistenceService, IOclService, etc. I get bored typing them out in this blog, so doing it in tests was really annoying me. I decided I would instead only mock the service in question. So if I were ensuring that an action wont save an object with broken constraints I would mock GetEcoService<IConstraintProvider> and ensure that I always got a broken constraint.

The problem was that the test to ensure I can save a valid object would then invoke the IPersistenceService.UpdateDatabaseWithList method. In my EcoSpace I have decorated my persistence service so that it checks every object in the update list to ensure it has no invalid constraints. At the point it asks for IConstraintProvider it is using the real IEcoServiceProvider and as a result it gets the real IConstraintProvider. In short the object would only save if it were really valid, and not if my mocked constraint provider pretended it was.

Ultimately I found it much easier just to register the mock service on the EcoSpace. To do this all I had to do was to expose a public method on the EcoSpace like so

#if DEBUG
 public void RegisterMockService(type serviceType, object serviceInstance)
 {
  RegisterEcoService(serviceType, serviceInstance);
 }
#endif


Now I can replace any service on the EcoSpace, so even the EcoSpace itself will get mocked services during testing. This is a lot less work!


Talking of a lot less work, although I was initially annoyed that the new field test for the ASP .NET web extensions had switched from using interfaces back over to using objects it turns out that testing is actually much easier than it was previously. Scott Hanselman posted some testing classes on his blog recently, it came out all screwy so I will repost the corrected version below (I hope he doesn’t mind). Testing is now as easy as this...

[TestClass]
public class AccountTests
{
 //This is a descendant of my real EcoSpace,
 //but I replace the persistence mapper with
 //PersistenceMapperMemory after construction so that
 //no DB access is needed.
 MemoryEcoSpace EcoSpace;
 AccountController Controller;
 MockRepository Mocks;
 FakeViewEngine FakeViewEngine;

 [TestInitialize]
 public void SetUp()
 {
  EcoSpace = new MemoryEcoSpace();
  EcoSpace.Active = true;

  Mocks = new MockRepository();
  FakeViewEngine = new FakeViewEngine();
  Controller = new AccountController();
  Controller.ViewEngine = FakeViewEngine;
  using (Mocks.Record())
  {
   Mocks.SetFakeControllerContext(Controller);
  }
 }

 [TestMethod]
 public void Create()
 {
  using (Mocks.Playback())
  {
   Controller.Create();
   Assert.AreEqual("Create", FakeViewEngine.ViewContext.ViewName);
  }
 }

 [TestMethod]
 public void CreateUpdate_ConfirmationEmailDoesNotMatch()
 {
  using (Mocks.Playback())
  {
   Controller.CreateUpdate("Mr", "Peter", "Morris", "me@home.com", "");
   Assert.IsTrue(ValidationHelper.ErrorExists(Controller.ViewData, NotASausageWebsite.Constants.ErrorMessages.ConfirmationEmailAddressDoesNotMatchEmailAddress));
  }
 }
}


public static class ValidationHelper
{
 public static bool ErrorExists(IDictionary<string, object> viewData, string errorMessage)
 {
  if (!viewData.ContainsKey(NotASausageWebsite.Constants.ViewDataKeys.Global.ErrorMessages))
   return false;
  List<string> errors = (List<string>)viewData[NotASausageWebsite.Constants.ViewDataKeys.Global.ErrorMessages];
  return errors.IndexOf(errorMessage) >= 0;
 }
}



Nice and easy! If you want the testing code from Scott here it is:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using Rhino.Mocks;
using System.Web.Mvc;
using System.Web.Routing;
using System.Collections.Specialized;

namespace Tests.Helpers
{
public static class MvcMockHelpers
{
public static HttpContextBase FakeHttpContext(this MockRepository mocks)
{
HttpContextBase context = mocks.PartialMock<HttpContextBase>();
HttpRequestBase request = mocks.PartialMock<HttpRequestBase>();
HttpResponseBase response = mocks.PartialMock<HttpResponseBase>();
HttpSessionStateBase session = mocks.PartialMock<HttpSessionStateBase>();
HttpServerUtilityBase server = mocks.PartialMock<HttpServerUtilityBase>();

SetupResult.For(context.Request).Return(request);
SetupResult.For(context.Response).Return(response);
SetupResult.For(context.Session).Return(session);
SetupResult.For(context.Server).Return(server);

mocks.Replay(context);
return context;
}

public static HttpContextBase FakeHttpContext(this MockRepository mocks, string url)
{
HttpContextBase context = FakeHttpContext(mocks);
context.Request.SetupRequestUrl(url);
return context;
}

public static void SetFakeControllerContext(this MockRepository mocks, Controller controller)
{
var httpContext = mocks.FakeHttpContext();
ControllerContext context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);
controller.ControllerContext = context;
}

static string GetUrlFileName(string url)
{
if (url.Contains("?"))
return url.Substring(0, url.IndexOf("?"));
else
return url;
}

static NameValueCollection GetQueryStringParameters(string url)
{
if (url.Contains("?"))
{
NameValueCollection parameters = new NameValueCollection();

string[] parts = url.Split("?".ToCharArray());
string[] keys = parts[1].Split("&amp;".ToCharArray());

foreach (string key in keys)
{
string[] part = key.Split("=".ToCharArray());
parameters.Add(part[0], part[1]);
}

return parameters;
}
else
{
return null;
}
}

public static void SetHttpMethodResult(this HttpRequestBase request, string httpMethod)
{
SetupResult.For(request.HttpMethod).Return(httpMethod);
}

public static void SetupRequestUrl(this HttpRequestBase request, string url)
{
if (url == null)
throw new ArgumentNullException("url");

if (!url.StartsWith("~/"))
throw new ArgumentException("Sorry, we expect a virtual url starting with \"~/\".");

SetupResult.For(request.QueryString).Return(GetQueryStringParameters(url));
SetupResult.For(request.AppRelativeCurrentExecutionFilePath).Return(GetUrlFileName(url));
SetupResult.For(request.PathInfo).Return(string.Empty);
}

}
}



 public class FakeViewEngine : IViewEngine
 {
  public void RenderView(ViewContext viewContext)
  {
   ViewContext = viewContext;
  }

  public ViewContext ViewContext { get; private set; }

 }

2008-02-22

Custom config sections

The website I am writing will sell some software I have already written. In addition it will sell "Collateral", which is basically support files for the software. The software itself will only run if it finds a license, which is an RSA signed binary file containing information such as the email address of the licensee. In addition some kinds of collateral will also be RSA signed with the licensee’s email address so that it will only work for that user, but not all collateral types are signed, for example a Character is a custom file format and is signed but a WAV file will not be signed.

So this website needs to sell software + provide a license. It also needs to sell collateral, some of which will require signing and some of which will not.

Software and Collateral are both types of Product, and you can buy a Product. The problem is how should I deal with the 3 different types of licensing (license file, signed binary, no license)? In addition to this should I really create a concrete descendant of the "Software" class just to implement a way of providing a license? Erm, no!

If I were to add a concrete class for every type of product it would mean I have to change the model for the website everytime a new product was released, what a pain! From the bottom-up this is what I did.

01: A separate assembly MyWebSite.Licensing with the following two interfaces in it (Edition is linked to Software, so we can buy a license for different editions, Std, Pro, etc):

public interface ISoftwareLicenseGenerator
{
  byte[] Generate(Edition edition, CustomerRole customer);
  string FileExtension { get; }
}

public interface ICollateralLicenseGenerator
{
  byte[] Generate(Collateral collateral, CustomerRole customer);
  string FileExtension { get; }
}


02: Another separate assembly with implementations for the interfaces in them. This assembly is then named something like "MyWebSite.Licensing.SoftwareName".

03: In the web.config file I can then add the following:

<licenseGenerators>
  <software>
    <add uniqueID="SoftwareName" type="MyNameSpace.SoftwareLicenseGeneratorClassName, NameOfAssembly"/>
  </software>
  <collateral>
    <add uniqueID="CollateralTypeName" type="MyNameSpace.CollateralLicenseGeneratorClassName, NameOfAssembly"/>
  </collateral>
</licenseGenerators>


This is parsed when the application is first run, giving me access to a list of generators available which I keep in a Dictionary<string, ......> so that I can look them up by name. If you directly add this kind of section to your web.config your app will not start, so I thought I’d show how to add custom sections + access their values from code....


01: Create a descendant of System.Configuration.ConfigurationSection

public class LicenseGeneratorsSection : ConfigurationSection
{
  public LicenseGeneratorsSection()
  {
  }

  [ConfigurationProperty("software")]
  public SoftwareLicenseGeneratorElementCollection SoftwareLicenseGenerators
  {
    get
    {
      return (SoftwareLicenseGeneratorElementCollection)this["software"];
    }
  }

  [ConfigurationProperty("collateral")]
  public CollateralLicenseGeneratorElementCollection CollateralLicenseGenerators
  {
    get
    {
      return (CollateralLicenseGeneratorElementCollection)this["collateral"];
    }
  }
}

This defines a section with no attributes + 2 element collections, one named "software" and one named "collateral". Next a class is needed for each which descends from System.Configuration.ConfigurationElementCollection so that System.Configuration knows what kind of object to add to these collections when we do <add ......./> within <software> or <collateral>.


02: Element collection, I will list the source only for one...

public class SoftwareLicenseGeneratorElementCollection : ConfigurationElementCollection
{
  //Creates a new instance for the xml <add uniqueID="X" type="Y"/>
  protected override ConfigurationElement CreateNewElement()
  {
    return new SoftwareLicenseGeneratorElement();
  }

  //Gets the key value, in this case the UniqueID property, this avoids duplicates
  protected override object GetElementKey(ConfigurationElement element)
  {
    return ((SoftwareLicenseGeneratorElement)element).UniqueID;
  }
}


03: Finally (for the config settings classes) a class is needed to define the attributes UniqueID and Type.

public class SoftwareLicenseGeneratorElement : ConfigurationElement
{
  public SoftwareLicenseGeneratorElement()
  {
  }

  [ConfigurationProperty("uniqueID", IsRequired = true, IsKey = true)]
  public string UniqueID
  {
    get { return (string)this["uniqueID"]; }
    set { this["uniqueID"] = value; }
  }

  [ConfigurationProperty("type", IsRequired = true)]
  public string Type
  {
    get { return (string)this["type"]; }
    set { this["type"] = value; }
  }
}

04: But before just putting the custom XML into web.config you need to declare this section and associate it with the new ConfigurationSection class

<configSections>
  <section name="licenseGenerators" type="MyWebsite.Configuration.LicenseGeneratorsSection, MyWebsite"/>
</configSections>

this tells ASP .NET to expect a custom section named "licenseGenerators" and to use the LicenseGeneratorsSection class in MyWebsite.dll to determine the structure of it, here it is again:


<licenseGenerators>
  <software>
    <add uniqueID="SoftwareName" type="MyNameSpace.SoftwareLicenseGeneratorClassName, NameOfAssembly"/>
  </software>
  <collateral>
    <add uniqueID="CollateralTypeName" type="MyNameSpace.CollateralLicenseGeneratorClassName, NameOfAssembly"/>
  </collateral>
</licenseGenerators>



To read the config section at runtime is really simple

LicenseGeneratorsSection licenseGeneratorsSection;
licenseGeneratorsSection =
  (LicenseGeneratorsSection)ConfigurationManager.GetSection("licenseGenerators");


Here is the entire class source for the static LicenseGenerator class

public static class LicenseGenerator
{
  static Dictionary<string, ISoftwareLicenseGenerator> SoftwareLicenseGenerators = new Dictionary<string, ISoftwareLicenseGenerator>();
  static Dictionary<string, ICollateralLicenseGenerator> CollateralLicenseGenerators = new Dictionary<string, ICollateralLicenseGenerator>();

  static LicenseGenerator()
  {
    //Read the web.config
    LicenseGeneratorsSection licenseGeneratorsSection;
    licenseGeneratorsSection =
      (LicenseGeneratorsSection)ConfigurationManager.GetSection("licenseGenerators");

    foreach (SoftwareLicenseGeneratorElement currentElement in licenseGeneratorsSection.SoftwareLicenseGenerators)
      SoftwareLicenseGenerators[currentElement.UniqueID] = (ISoftwareLicenseGenerator)GetInstance(currentElement.Type);

    foreach (CollateralLicenseGeneratorElement currentElement in licenseGeneratorsSection.CollateralLicenseGenerators)
      CollateralLicenseGenerators[currentElement.UniqueID] = (ICollateralLicenseGenerator)GetInstance(currentElement.Type);
  }

  private static object GetInstance(string typeName)
  {
    Type objectType = Type.GetType(typeName);
    return Activator.CreateInstance(objectType);
  }

  public static ICollateralLicenseGenerator GetCollateralLicenseGenerator(string uniqueID)
  {
    return CollateralLicenseGenerators[uniqueID];
  }

  public static string[] GetCollateralLicenseGeneratorIDs()
  {
    List<string> result = new List<string>();
    foreach (KeyValuePair<string, ICollateralLicenseGenerator> kvp in CollateralLicenseGenerators)
      result.Add(kvp.Key);
    result.Sort();
    return result.ToArray();
  }

  public static ISoftwareLicenseGenerator GetSoftwareLicenseGenerator(string uniqueID)
  {
    return SoftwareLicenseGenerators[uniqueID];
  }

  public static string[] GetSoftwareLicenseGeneratorIDs()
  {
    List<string> result = new List<string>();
    foreach (KeyValuePair<string, ISoftwareLicenseGenerator> kvp in SoftwareLicenseGenerators)
      result.Add(kvp.Key);
    result.Sort();
    return result.ToArray();
  }
}



This solution allows me to add new products at runtime without any problems. Any time a new type of license generator is required I just create a new assembly with a class that implements the correct interface and then register it in the web.config file!

2007-01-19

Testing a website with a root path

This post is really a reminder to myself, so that I can find the information quickly for the next time I reformat my hard drive!

To run a website with a root path in Visual Studio 2005 follow these steps:
  1. Tools->External tools menu
  2. Click Add
  3. Title = Webserver
  4. Command = C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\WebDev.WebServer.EXE
  5. Arguments = /port:8080 /path:$(ProjectDir)
  6. Tick "Use output window"
Whenever you need the webserver running just open your project and go to Tools->Webserver.

2006-12-02

Adding runtime error messages to page validation

One of the requirements when writing a website I once created was the ability to model object constraints in OCL and then validating the current page against those constraints.

The problem with normal validators is that they are designed to validate individual controls rather than a list of constraints on an object. The approach I took was to create a validator which I can place on any web form and add as many errors to as I required.

The first step was to create a WebControl which supported IValidator

public class MultiValidator : WebControl, IValidator
{
}
I then added a list of strings to hold the error strings, and a method to add an error.
    private List Errors = new List();

public void AddError(string message)
{
Errors.Add(message);
}//AddError

When ASP .net validates a page it enumerates all IValidators within its own Validators property, and called IValidator.Validate(). To determine if the page is valid or not it then checks IValidator.IsValid.

To add custom error messages at runtime I decided to create a static validator class which always returns "false" from IsValidator.IsValid. For each error message in my MultiValidator I could then simply create an instance of one of these validators.
[ToolboxItem(false)]
internal class StaticValidator : IValidator
{
private string errorMessage;

#region IValidator
void IValidator.Validate()
{
}//Validate

bool IValidator.IsValid
{
get { return false; }
set { }
}//IsValid
#endregion

public string ErrorMessage
{
get { return errorMessage; }
set { errorMessage = value; }
}//ErrorMessage
}
Now that the StaticValidator was written, all I needed to do was to add the required IValidator implementations to my MultiValidator class.
#region IValidator
void IValidator.Validate()
{
isValid = (Errors.Count == 0);
foreach(string error in Errors)
{
StaticValidator validator = new StaticValidator();
validator.ErrorMessage = error;
Page.Validators.Add(validator);
Validators.Add(validator);
}//foreach errors
}//Validate

bool IValidator.IsValid
{
get { return isValid; }
set { isValid = value; }
}//IsValid
#endregion

Within a webform, I would now
  1. Set "CausesValidation" to false on my submit button
  2. Validate my object
  3. Call MultiValidator1.AddError() for each error encountered
  4. Call Page.Validate()
  5. Check Page.IsValid as normal
Using a ValidationSummary I could then display the broken constraints to the user for rectification. The whole source code is listed below....

MultiValidator.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace DroopyEyes.Web.Controls
{
public class MultiValidator : WebControl, IValidator
{
#region Members
private bool isValid = true;
private string errorMessage = "";
private List Errors = new List();
private List Validators = new List();
#endregion

#region IValidator
void IValidator.Validate()
{
isValid = (Errors.Count == 0);
foreach(string error in Errors)
{
StaticValidator validator = new StaticValidator();
validator.ErrorMessage = error;
Page.Validators.Add(validator);
Validators.Add(validator);
}//foreach errors
}//Validate

bool IValidator.IsValid
{
get { return isValid; }
set { isValid = value; }
}//IsValid
#endregion

protected override void OnInit(EventArgs e)
{
base.OnInit(e);
Page.Validators.Add(this);
}

protected override void OnUnload(EventArgs e)
{
if (Page != null)
{
Page.Validators.Remove(this);
foreach(IValidator validator in Validators)
Page.Validators.Remove(validator);
}//Page != null

base.OnUnload(e);
}


public void AddError(string message)
{
Errors.Add(message);
}//AddError


#region Properties
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
public string ErrorMessage
{
get { return errorMessage; }
set { errorMessage = value; }
}//ErrorMessage
#endregion
}
}
StaticValidator.cs
using System;
using System.ComponentModel;
using System.Web.UI;

namespace DroopyEyes.Web.Controls
{
[ToolboxItem(false)]
internal class StaticValidator : IValidator
{
private string errorMessage;

#region IValidator
void IValidator.Validate()
{
}//Validate

bool IValidator.IsValid
{
get { return false; }
set { }
}//IsValid
#endregion

public string ErrorMessage
{
get { return errorMessage; }
set { errorMessage = value; }
}//ErrorMessage
}
}