Site Meter
 
 

Deleporter: Cross-Process Code Injection for ASP.NET

Deleporter is a little .NET library that teleports arbitrary delegates into an ASP.NET application in some other process (e.g., hosted in IIS) and runs them there. At the moment, it’s still pretty experimental, but I’ve found it useful so far.

Why would you want this? It’s mainly useful when you’re integration testing. Normally, integration tests can’t directly interact with your application’s code because it’s in a separate process (and possibly running on another machine). So, unlike unit tests, integration tests often struggle to test different configurations and have no option to use mocks.

Deleporter gets around that limitation. It lets you delve into a remote ASP.NET application’s internals without any special cooperation from the remote app (it just needs to include an extra IHttpModule in its config), and then you can do any of the following:

  • Cross-process mocking, by combining it with any mocking tool. For example, you could inject a temporary mock database or simulate the passing of time (e.g., if your integration tests want to specify what happens after 30 days or whatever)
  • Test different configurations, by writing to static properties in the remote ASP.NET appdomain or using the ConfigurationManager API to edit its <appSettings> entries.
  • Run teardown or cleanup logic such as flushing caches. For example,  recently I needed to restore a SQL database to a known state after each test in the suite. The trouble was that ASP.NET connection pool was still holding open connections on the old database, causing connection errors. I resolved this easily by using Deleporter to issue a SqlConnection.ClearAllPools() command in the remote appdomain – the ASP.NET app under test didn’t need to know anything about it.

Rails developers can already do this either by hosting their app in-process (e.g., with WebRat) or using a cross-process mocking tool. I’ve already talked about hosting ASP.NET (MVC) apps in-process with integration tests (the starting point for this code); now I wanted to get the same benefits while hosting my app on a real web server.

This library is built on regular .NET remoting. But unlike regular remoting, it run arbitrary delegates, not just methods specifically exposed by the remoting service. If the code for your delegate isn’t already loaded into the ASP.NET appdomain, Deleporter will serialize the code and send it across. This is really essential for being useful from an integration test suite, because the test code wouldn’t normally be in the real app.

Enough talk! Code now.

To use it, first download the binary or compile the source to get Deleporter.dll. Reference it from both your integration test project and the ASP.NET MVC/WebForms app you’re testing.

Next, to get your ASP.NET app to listen for commands, edit its Web.config file to reference this extra HTTP module:

<configuration>
  <system.web>
    <httpModules>
      <!-- If you're using Visual Studio's built-in web server, or IIS 5 or 6, add this: -->
      <add name="DeleporterServerModule" type="DeleporterCore.Server.DeleporterServerModule, Deleporter" />
    </httpModules>
  </system.web>
  <system.webServer>
    <modules>
      <!-- If you're using IIS 7+, add this: -->
      <add name="DeleporterServerModule" type="DeleporterCore.Server.DeleporterServerModule, Deleporter" />
    </modules>
  </system.webServer>
</configuration>

Now, when you next start up your ASP.NET app, it will listen on TCP port 38473 – a randomly-chosen default – for commands from your integration test suite.

In your integration test code, you can now run arbitrary delegates in the remote ASP.NET app, sending both data and code into it, and optionally pulling data back. As a trivial example, you can pull back the server’s time local time:

[Test]
public void ServerClockHasCorrectYear()
{
    DateTime serverTime = Deleporter.Run(() => DateTime.Now);
    Assert.AreEqual(DateTime.Now.Year, serverTime.Year, "The server's clock is way off");
}

You can use multi-statement lambdas, too, even if they capture locals from the containing method. Deleporter will send the captured locals into the remote app (as long as they’re serializable), and if the remote app edits them, it will pull back the new values and update your locals. In other words, anonymous methods and multi-statement lambdas work with captured local variables just as if all the code was running locally. Example:

[Test]
public void SendingAndUpdatingCapturedLocals()
{
    DateTime serverTime = default(DateTime);
    string fileToLocate = "~/web.config";
    string mappedPhysicalPath = null;
 
    Deleporter.Run(() => {
        // This code, which both reads and writes locals from the outer scope,
        // runs in the remote ASP.NET application
        serverTime = DateTime.Now;
        mappedPhysicalPath = HostingEnvironment.MapPath(fileToLocate);
    });
 
    Assert.AreEqual(DateTime.Now.Year, serverTime.Year);
    Assert.IsTrue(File.Exists(mappedPhysicalPath)); // Of course, this will only be true if the ASP.NET app runs on the same machine as the integration test code
}

More realistic applications

So far I’ve only shown very artificial examples designed to illustrate the syntax. To give you a more realistic idea of how you could apply it, I can offer two sample applications.

  • A very simple ASP.NET MVC app called WhatTimeIsIt whose sole behaviour varies according to the date. Like all good ASP.NET MVC applications, it uses the Dependency Injection pattern (in this example, with Ninject) to get the current date & time from an IDateProvider – the default implementation of which just returns DateTime.Now.
    It has simple integration tests that use Deleporter and Moq to inject a mock IDateProvider across the process boundary while the app is running, and then verifies some behaviour that only happens during specific dates. It also demonstrates an easy way of tidying up after the tests run (restoring the original IDateProvider) automatically without cluttering the test code with teardown logic.
    Here’s how the cross-process mocking works:
    // Inject a mock IDateProvider, setting the clock back to 1975
    var dateToSimulate = new DateTime(1975, 1, 1);
    Deleporter.Run(() => {
        var mockDateProvider = new Mock<idateProvider>();
        mockDateProvider.Setup(x => x.CurrentDate).Returns(dateToSimulate);
        NinjectControllerFactoryUtils.TemporarilyReplaceBinding(mockDateProvider.Object);
    });
  • An enhanced version of the SpecFlow BDD-style integration test suite from my previous blog post. It now uses Deleporter to apply a mock repository to verify certain specifications.
    For example, one of the Gherkin files now specifies this scenario:
    Scenario: Most recent entries are displayed first
    	Given we have the following existing entries
    		| Name      | Comment      | Posted date       |
    		| Mr. A     | I like A     | 2008-10-01 09:20  |
    		| Mrs. B    | I like B     | 2010-03-05 02:15  |
    		| Dr. C     | I like C     | 2010-02-20 12:21  |
          And I am on the guestbook page
        Then the guestbook entries includes the following, in this order
    		| Name      | Comment      | Posted date       |
    		| Mrs. B    | I like B     | 2010-03-05 02:15  |
    		| Dr. C     | I like C     | 2010-02-20 12:21  |
    		| Mr. A     | I like A     | 2008-10-01 09:20  |

Gotcha: What you must remember to avoid confusing problems

One behaviour might surprise you at first. It’s certainly caught me out plenty of times already. Each time you edit and recompile your integration test code, you must also recycle your ASP.NET appdomain (e.g., by resaving its Web.config file, or the nuclear option – running iisreset) otherwise the old code will still be loaded into it. That’s because, as far as I’m aware, there’s no way to unload an assembly from a .NET appdomain. Once your delegates have been transferred over, they’re staying there and can’t automatically be replaced by newer versions.

This isn’t a problem if you’re running an integration suite through your continuous integration (CI) system – the app code would be recompiled and hence reset between test suite runs. It’s only something to watch out for during local development. In the Deleporter.Test.Client project I’ve shown a crude but working way of automatically recycling the ASP.NET appdomain when the test suite starts running; you may be able to work out a better way of automating this if you need one.

Important security note

You should not enable DeleporterServerModule on a production web site. It’s only supposed to be used in dev and QA environments, because it’s literally an invitation to upload and execute arbitrary code.

To reduce the chance of a mistake, DeleporterServerModule will refuse to run if your web site was compiled in release mode – it will demand that you remove the IHttpModule from your Web.config file. Technically you should be OK if your firewall doesn’t accept inbound connections on port 38473 (or whatever port you configure it to listen on) anyway. Obviously, no warranties, you use it at your own risk, etc.

License: Ms-Pl

kick it on DotNetKicks.com

19 Responses to Deleporter: Cross-Process Code Injection for ASP.NET

  1. Very cool… actually it’s crazy cool! So how does the server know about the Moq? I am looking at the deleporter code and I am seeing an AssemblyProvider, is that doing it? If so, then I guess Deleporter has to run on the same machine?

    The RecycleServerAppDomain() method is acceptable in my opinion. I would have done it that way too.

    Very amazing stuff, great job.

  2. Steve

    Khalid – if you reference external libraries such as Moq, then Deleporter will load that code’s assembly across the remoting link in just the same way as it sends your delegates. It works by intercepting the AssemblyLoad event, so it only sends the assemblies that are actually required, not every assembly you’ve referenced.

    So, it still works fine even if your ASP.NET app is on a separate machine from where your integration tests are running.

  3. Very clever, Steve! Thank you for the original thinking and contributions to the community.

    Will this work with HttpContext related dependencies, like Session, Cache, etc.?

    At first I was confused about the WhatTimeIsIt test above because I didn’t see why you needed Deleporter. You said the test “then verifies some behaviour that only happens during specific dates”. After looking at the full test code on github, I see it’s scraping the HTML for a specific string in a specific DOM element, and you’d normally need a UI testing tool like Watir to do that.

    Watir, Watin, etc. can test how your web app works externally. I think Deleporter can be useful for integration testing the internals of controller actions. Of course, those tests will be very familiar with the code under test, and therefore more brittle. But sometimes that’s an acceptable trade off to see how the real web server will respond.

  4. Steve

    Joe, this isn’t a replacement for a UI automation tool like WatiN or Selenium. I’m assuming you’re using one of those, and then Deleporter gives you an additional way to reach directly into the running ASP.NET app’s internals (*during the WatiN tests*) to change config, flush caches, inject mocks etc. – whatever you need to enable effective integration testing. Sorry if that wasn’t clear.

    The WhatTimeIsIt demo project only uses HTML scraping to make it easier for people to run without taking a dependency on WatiN etc. The other demo project shows how you can use it more realistically with WatiN and SpecFlow.

    Since it lets you run delegates inside the real ASP.NET application, it certainly does let you access its HttpApplicationState, Cache, etc, if you want. (Session is a bit trickier, because that’s associated with a specific session ID.)

  5. Pingback: The Morning Brew - Chris Alcock » The Morning Brew #557

  6. Pingback: As a Geek I want to blog « The World is mine

  7. Tim

    Hi Steve,

    This looks great! I’ve been spiking a bit with it and would like to inspect the HttpContext in the remote web app. However, any call to HttpContext.Current inside the deleporter delegate returns null. Maybe the HttpContext.Current call is run in the context of the test assembly rather than the web app?

    Any tips or guidance would be much appreciated. I’m sure I’m going about this in the wrong way.

    Cheers.

  8. Pingback: Week 3 – Stuck | My Care Tracker

  9. Alexey Zuev

    Very cool stuff! Can’t wait to read the code, to find out how this magic is implemented.

    May I ask, whether mocking repositories is your usual approach for data-driven BDD-style integration tests?

    At the project I’m working on, unit testing is used a lot. And now we are looking at the BDD as a way to write integration tests, because a lot of bugs are missed by unit tests. Mocking repositories is compelling way to manage test configuration, but I have concern that it will reduce value of those integration tests.

  10. Very cool.

    The WhatTimeIsIt sample works fine using the Visual Studio Development Server (Cassini), but doesn’t work with IIS Express. I’m running VS 2010 SP1.

    I get YSOD and “Only one usage of each socket address (protocol/network address/port) is normally permitted”.

    To replicate, open WhatTimeIsIt and verify it works with Cassini. Change the Web project to use IIS Express, and change the web.config as per above (by moving the line from httpModules to modules) and boom, YSOD.

  11. Marek

    The behaviour described by Matt happens also when running with the “normal” IIS 7.5 on Windows 7.

  12. James

    Steve,
    I’m also getting the “Only one usage of each socket address (protocol/network address/port) is normally permitted” exception that Matt described.
    I’m running IIS Express 7.5 on Windows 7, FYI.

    Do you know of a workaround for this issue?

  13. The project my team and I are about to start on is going to be done with full out BDD.

    I am definitely going to be looking at deleporter to be able to forge authenticated users (or unauthenticated for that matter) into my application.

  14. kjetil

    Also experiencing the “Only one usage of each socket address (protocol/network address/port) is normally permitted” issue.

    Have anyone found a fix for this?

  15. I’m also getting the “Only one usage of each socket address (protocol/network address/port) is normally permitted” exception that Matt described.
    I’m running IIS Express 7.5 on Windows 7, FYI.

    Did anyone find the resolution to this?

  16. dee

    Same error:
    “Only one usage of each socket address (protocol/network address/port) is normally permitted”

  17. I was also getting the “Only one usage of each socket address…” error. I cloned the source from github, recompiled, and used that build. The problem went away.

  18. Stuart Leitch

    The “Only one usage of each socket address…” error was fixed in Steve’s April 16, 2010 GitHub Commit

  19. Hey Steven,
    love the project, are you still active on it?
    Github looks very quiet, so I was wondering if it “just works”, or if you have moved on.
    Cheers,
    Martin