Wednesday, August 25, 2010

Getting Started with NServiceBus Part 3: Putting Messages on the Bus

Last time we analyzed the Music Store code to come up with a schema for our messages. The next order of business is to modify the Music Store code to put messages on the bus. In order to do this we must first edit our web.config file to configure NSB. First you have to add the custom config section:
Next we add that section to create an Endpoint Mapping. What this does is tell the bus that the given Endpoint will be accepting messages defined in our schema assembly.

    
      
    
  
In our case the Endpoint is the address to the queue that will be accepting our messages. If you want at this point you can create this as a local, private, transactional MSMQ queue. Also be sure to have the Distributed Transaction Coordinator service up and running. Now that we have configured our Endpoint, we have to bootstrap NServiceBus and keep it going for the duration of our ASP.NET application. We can do this in the Global.asax.cs file using NSB's fluent configuration:
protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            RegisterRoutes(RouteTable.Routes);

            Bus = NServiceBus.Configure.WithWeb()
                .Log4Net()
                .DefaultBuilder()
                .XmlSerializer()
                .MsmqTransport()
                    .IsTransactional(false)
                    .PurgeOnStartup(false)
                .UnicastBus()
                    .ImpersonateSender(false)
                .CreateBus()
                .Start();
        }
I'll explain each configuration item bit by bit:

  • WithWeb - tells NSB to scan a web directory instead of a regular one
  • Log4Net - next configure Log4Net for logging
  • DefaultBuilder - use the default IoC container
  • XmlSerializer - serialize objects using XML
  • MsmqTransport - configure MSMQ as our transportation protocol
  • IsTransactional(false) - we don't want to hang a web app up in transactions, so turn this off
  • PurgeOnStartup(false) - since there is no queue for us to purge, turn this off
  • UnicastBus - configure our bus to send single point to point messages
  • ImpersonateSender(false) - turn off impersonation
  • CreateBus() - tell the IoC to create an instance of the bus
  • Start() - bootstrap the bus

Now that we've bootstrapped, NSB we can get on to sending messages. Our first message will be used to add something to a cart in the database. We head to the the ShoppingCartController.AddToCart method and modify it to send a message instead of interacting directly with the database.

public ActionResult AddToCart(int id)
        {

            // Retrieve the album from the database
            var addedAlbum = storeDB.Albums
                .Single(album => album.AlbumId == id);

            // Add it to the shopping cart
            var cart = ShoppingCart.GetCart(this.HttpContext);
            
            //cart.AddToCart(addedAlbum);

            Helpers.ServiceAgent<IAddToCartCommand>.Send(
                c => 
                {
                    c.CartId = cart.GetCartId(this.HttpContext);
                    c.AlbumId = addedAlbum.AlbumId;
                });
           
            // Go back to the main store page for more shopping
            return RedirectToAction("Index");
        }
I've added a utility class to actually put the message on the bus using similar NSB semantics. I'm doing this so that I don't have an explicit reference to NSB in my Controller code. The utility is very simple:
public static class ServiceAgent<T> where T : ICommand
    {
        public static void Send(Action<T> messageConstructor)
        {
            if (null != messageConstructor)
                MvcApplication.Bus.Send<T>(messageConstructor);
        }
    }
We'll do the same thing for placing an order. We modify the code in CheckOutController.AddressAndPayment:
[HttpPost]
        public ActionResult AddressAndPayment(FormCollection values)
        {
            ...
                else
                {
                    //order.Username = User.Identity.Name;
                    //order.OrderDate = DateTime.Now;

                    //Save Order
                    //storeDB.AddToOrders(order);
                    //storeDB.SaveChanges();

                    //Process the order
                    var cart = ShoppingCart.GetCart(this.HttpContext);
                    //cart.CreateOrder(order);

                    Int32 syntheticId = Helpers.IdGenerator.Generate();

                    Helpers.ServiceAgent.Send(
                        c =>
                        {
                            c.OrderId = syntheticId;
                            c.CartId = cart.GetCartId(this.HttpContext);
                        });

                    
                    
                    return RedirectToAction("Complete", 
                        new { id = syntheticId });
                }
            ...
        }
Here we're using the same utility class to put a message on the bus. Note that I'm generating the Order Id on the client side. This application shows the order id back to the user after they have placed the order. In order to pull this off, we have to create the id client site. I've created another utility to generate the Order Id:
public static class IdGenerator
    {
        public static Int32 Generate()
        {
            byte[] buffer = Guid.NewGuid().ToByteArray();
            return BitConverter.ToInt32(buffer, 0);
        }
    }
Now if you fire up the client and try to add items to the cart, you won't see any data in the subsequent screen. This is because we haven't implemented the server side to our solution that handles the messages. When placing an order you should see and order id just like you normally would.

Next time we'll implement the message handlers and talk about how to host, configure, and run a NSB instance.

Code can be found HERE

2 comments:

  1. You could also probably lose the call to get the album, since all the message takes is the album id which is passed in

    ReplyDelete
  2. Paul, I think the call to get the album is included for validation purposes.

    ReplyDelete