Showing posts with label NServiceBus. Show all posts
Showing posts with label NServiceBus. Show all posts

Thursday, February 10, 2011

NServiceBus assembly scanning: avoiding unintended consequences

The default startup behavior for a NServiceBus endpoint is to scan all assemblies in the deployment directory for any types it might be interested in, for example, message modules and message handlers. This is really handy, but occasionally it can lead to unintended consequences. Fortunately, the With method has a few overloads and one of them takes a list of assemblies to scan. If you want to prevent loading types from any stray assembly that might make it into your deployment directory, change your initialization code to look like this:

var assemblies = GetType().Assembly
   .GetReferencedAssemblies()
   .Select(n => Assembly.Load(n))
   .ToList();
assemblies.Add(GetType().Assembly);
NServiceBus.Configure.With(assemblies);

This restricts the assembly scanning process to only the assemblies that your endpoint explicitly references. We can extract this code to an extension method so we can use it in other endpoints:

public static IEnumerable<Assembly> ReferencedAssemblies(this Type type)
{
   var assemblies = type.Assembly
      .GetReferencedAssemblies()
      .Select(n => Assembly.Load(n))
      .ToList();
   assemblies.Add(GetType().Assembly);
   return assemblies;
}

Now our endpoint initialization looks like this:

NServiceBus.Configure.With(GetType()
   .ReferencedAssemblies());

Monday, December 6, 2010

Using AsyncController with NServiceBus

With NServiceBus sometimes you do not want to allow a web request to complete until you have received some response from your message handler. For example, suppose you have defined the following message handler that returns a response code on success:

public class TestMessageHandler :
    IHandleMessages<TestMessage>
{
    public IBus Bus { get; set; }

    public void Handle(TestMessage message)
    {
        //Do something interesting...
        Bus.Return(0);
    }
}

This is a perfect scenario for using the asynchronous page API. It allows you to execute a potentially long-running operation on a separate thread outside of the IIS thread pool. While this thread is executing, the original request thread is returned to the thread pool. As a result, the asynchronous page API is great for I/O bound operations, since you keep threads in the thread pool ready to service requests and the new thread spends most of its time blocked on I/O. NServiceBus supports the asynchronous page API out of the box:

Bus.Send(new TestMessage()).RegisterWebCallback(callback, state);

Unfortunately, RegisterWebCallback does not work with ASP.NET MVC. If you are using ASP.NET MVC, you can use AsyncController, which was introduced in ASP.NET MVC 2. The following code illustrates how you might use AsyncController to wait for a response the message handler:

public class TestController : AsyncController
{
    private IBus bus;

    public TestController(IBus bus)
    {
        this.bus = bus;
    }

    public void IndexAsync()
    {
        AsyncManager.OutstandingOperations.Increment();

        bus.Send(new TestMessage()).Register(Callback);
    }
    public ActionResult IndexCompleted()
    {
        return View();
    }
    
    private void Callback(int responseCode)
    {
        AsyncManager.OutstandingOperations.Decrement();
    }
}

As you can see, the AsyncController API is a little awkward (though it is an improvement over the asynchronous page API, in my opinion), but it is a small price to pay for scalability.

Saturday, August 14, 2010

Ctrl-C and the NServiceBus generic host

Ten posts and almost a whole year later, my very first post about NServiceBus is still by far my most popular. So here is some more NServiceBus-y goodness.

Last week a coworker came to me asking for help with a self-hosted NServiceBus application. He had a small console application set up to test NServiceBus and it worked beautifully in all but one case. When he killed the application with Ctrl-C, the message being processed was always lost. At first I thought maybe the application was not using a transactional endpoint, but it was configured to do so. I did not see the same behavior with the generic host running in console mode, so I figured the generic host was handling Ctrl-C somehow. A quick search turned up the CancelKeyPress event. I opened up the generic host in reflector, and, on a hunch, navigated to the TopShelf.Internal.Hosts namespace (the generic host internalizes another excellent open source project called TopShelf). Sure enough, the TopShelf console host handles the CancelKeyPress event, and ultimately allows all of the worker threads in NServiceBus to finish handling messages before shutting down.

One question this brought up is what happens if someone pulls the plug on my machine after a message has been received, but before it has been processed? My impression was that a transactional endpoint would take care of this, but now I'm not so sure.

Wednesday, November 25, 2009

Configuring NServiceBus In Web Applications

My team recently started using NServiceBus for a new project. It has gone extremely well for the most part. Messaging can be tricky, and a tool like NServiceBus can lower the barrier to entry and gently guides developers towards messaging best practices.

Like any tool, NServiceBus has a learning curve, and there were a few problems along the way. The biggest was an issue with configuring web applications as endpoints. Each web application used the fluent configuration API in the Application_Start event handler like so:


NServiceBus.Configure.With()...


It worked fine in almost all cases. However, when one application was deployed to IIS, the configuration code was throwing inexplicable errors. My team lost about a week trying to figure out what was going on, and eventually started investigating workarounds in order to meet the deadline. Workarounds usually result in their own unique set of problems, which I desperately wanted to avoid. Finally, a long session with reflector revealed the problem:



NServiceBus.Configure.WithWeb()...


The difference between the two methods is subtle. The former tells NServiceBus to scan AppDomain.BaseDirectory for assemblies, while the latter tells NServiceBus to scan AppDomain.DynamicDirectory. The reason this affected one application in particular was because of how that application is deployed. Different versions of the application are deployed in virtual directories under the same web application in IIS, so there was a mismatch between some assembly versions.

The NServiceBus samples demonstrate the use of the WithWeb method. Had we paid more attention to the samples, we might have completely avoided this headache.