Web app subscribed to events, but not handling them - nservicebus

My web process sends commands to the domain and handles events (to push through SIgnalR to browsers). It's running ASP.NET MVC3 and .NET 4 on IIS Express / Win7 x64
At random, and sometimes right from the start, it decides to stop distributing events to my handlers. The events are in the web endpoint's journal queue, but there is no other trace of them. They're missing from the log entirely, my breakpoints in the handlers are never hit, and both the endpoint and the error queue are empty.
Func<Type, bool> isMessage = t => typeof(IMessage).IsAssignableFrom(t);
Func<Type, bool> isCommand = t => typeof(ICommand).IsAssignableFrom(t);
Func<Type, bool> isEvent = t => typeof(IEvent).IsAssignableFrom(t);
Configure.WithWeb()
.NinjectBuilder(Kernel)
.DefineEndpointName("Hospital.Web")
.DefiningMessagesAs(isMessage)
.DefiningCommandsAs(isCommand)
.DefiningEventsAs(isEvent)
.Log4Net()
.JsonSerializer()
.MsmqTransport()
.UnicastBus()
.LoadMessageHandlers()
.PurgeOnStartup(true)
.CreateBus()
.Start();
Here's the NSB parts of my web.config:
<MsmqTransportConfig ErrorQueue="error" NumberOfWorkerThreads="1" MaxRetries="5" />
<UnicastBusConfig>
<MessageEndpointMappings>
<add Messages="Hospital.Commands" Endpoint="Hospital.CommandHandlers" />
<add Messages="Hospital.Events" Endpoint="Hospital.CommandHandlers" />
</MessageEndpointMappings>
</UnicastBusConfig>
Here's one of my handlers:
public class StatsHandler :
IHandleMessages<PatientAdmitted>,
IHandleMessages<BedAssigned>,
IHandleMessages<PatientDischarged>
{
private static readonly ILog Log = LogManager.GetLogger(typeof (StatsHandler));
private readonly IConnectionManager _connectionManager;
public StatsHandler(IConnectionManager connectionManager)
{
_connectionManager = connectionManager;
}
protected dynamic Clients
{
get { return _connectionManager.GetClients<StatsHub>(); }
}
public void Handle(PatientAdmitted message)
{
Log.DebugFormat("Admitted {0} {1}", message.FirstName, message.LastName);
Clients.patientAdmitted();
}
public void Handle(BedAssigned message)
{
Log.DebugFormat("Assigned {0} to {1}", message.PatientId, message.Bed);
Clients.bedAssigned();
}
public void Handle(PatientDischarged message)
{
Log.DebugFormat("Discharged {0}", message.PatientId);
Clients.patientDischarged();
}
}
I've also asked this question in the NServiceBus group. There's a log file attached to that message.

There was a bug in RC4 that could have caused this behavior, can you try RC5 and see if that helps?

Related

Correct way to use Serilog with WebApi2

I am in search of the correct way to use serilog with aspnet webapi2.
As for now I initialize the global Log.Logger property like that :
public static void Register(HttpConfiguration config)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9200"))
{
IndexFormat = IndexFormat,
BufferBaseFilename = outputLogPath,
AutoRegisterTemplate = true,
AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv6,
CustomFormatter = new ElasticsearchJsonFormatter(renderMessageTemplate: false),
BufferFileCountLimit = NbDaysRetention
})
.MinimumLevel.ControlledBy(new LoggingLevelSwitch() { MinimumLevel = LogEventLevel.Information})
.Enrich.FromLogContext()
.Enrich.WithWebApiRouteTemplate()
.Enrich.WithWebApiActionName()
.CreateLogger();
//Trace all requests
SerilogWebClassic.Configure(cfg => cfg.LogAtLevel(LogEventLevel.Information));
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
Is there a more cleaner way to do it? I am wondering, if this might be a problem if I have to put some test in place for my controllers.
I have used the following code organization quite a lot for apps with Web API (and/or MVC). (note that it may be a bit outdated, as it is based on old versions of some packages, but you should be able to adapt it without too much work ...)
You will need quite a few packages, that you should be able to guess from the namespaces, but most importantly, install the WebActivatorEx package which provides a way to have code running at different moment of the app lifecycle
Our Global.asax.cs ended up looking like this :
public class WebApiApplication : System.Web.HttpApplication
{
// rely on the fact that AppPreStart is called before Application_Start
private static readonly ILogger Logger = Log.ForContext<WebApiApplication>();
public override void Init()
{
base.Init();
}
protected void Application_Start()
{
// WARNING : Some code runs even before this method ... see AppPreStart
Logger.Debug("In Application_Start");
// Mvc (must be before)
AreaRegistration.RegisterAllAreas();
// Web API
// ... snip ...
// some DependencyInjection config ...
// ... snip ...
// MVC
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
Logger.Information("App started !");
}
protected void Application_End(object sender, EventArgs e)
{
Logger.Debug("In Application_End");
ApplicationShutdownReason shutdownReason = System.Web.Hosting.HostingEnvironment.ShutdownReason;
Logger.Information("App is shutting down (reason = {#shutdownReason})", shutdownReason);
// WARNING : Some code runs AFTER Application_End ... see AppPostShutDown
}
}
and then several classes under the App_Start folder (some of which you can just ignore :P ) :
AppPreStart.cs
using XXX;
using Serilog;
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(AppPreStart), nameof(AppPreStart.PreApplicationStart))]
namespace XXX
{
/// <summary>
/// This runs even before global.asax Application_Start (see WebActivatorConfig)
/// </summary>
public class AppPreStart
{
public static void PreApplicationStart()
{
LogConfig.Configure();
var logger = Log.ForContext<AppPreStart>();
logger.Information("App is starting ...");
// ... snip ...
// very early things like IoC config, AutoMapper config ...
// ... snip ...
logger.Debug("Done with AppPreStart");
}
}
}
AppPostShutDown.cs
using XXX;
using Serilog;
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(AppPostShutDown), nameof(AppPostShutDown.PostApplicationShutDown))]
namespace XXX
{
/// <summary>
/// This runs even before global.asax Application_Start (see WebActivatorConfig)
/// </summary>
public class AppPostShutDown
{
private static ILogger Logger = Log.ForContext<AppPostShutDown>();
public static void PostApplicationShutDown()
{
Logger.Debug("PostApplicationShutDown");
// ... snip ...
// very late things like IoC dispose ....
// ... snip ...
// force flushing the last "not logged" events
Logger.Debug("Closing the logger! ");
Log.CloseAndFlush();
}
}
}
LogConfig.cs
using Serilog;
using Serilog.Events;
using SerilogWeb.Classic;
using SerilogWeb.Classic.Enrichers;
using SerilogWeb.Classic.WebApi.Enrichers;
namespace XXX
{
public class LogConfig
{
static public void Configure()
{
ApplicationLifecycleModule.LogPostedFormData = LogPostedFormDataOption.OnlyOnError;
ApplicationLifecycleModule.FormDataLoggingLevel = LogEventLevel.Debug;
ApplicationLifecycleModule.RequestLoggingLevel = LogEventLevel.Debug;
var loggerConfiguration = new LoggerConfiguration().ReadFrom.AppSettings()
.Enrich.FromLogContext()
.Enrich.With<HttpRequestIdEnricher>()
.Enrich.With<UserNameEnricher>()
.Enrich.With<HttpRequestUrlEnricher>()
.Enrich.With<WebApiRouteTemplateEnricher>()
.Enrich.With<WebApiControllerNameEnricher>()
.Enrich.With<WebApiRouteDataEnricher>()
.Enrich.With<WebApiActionNameEnricher>()
;
Log.Logger = loggerConfiguration.CreateLogger();
}
}
}
and then read the variable parts of the Log configuration from Web.config that has the following keys in AppSettings :
<!-- SeriLog-->
<add key="serilog:level-switch:$controlSwitch" value="Information" />
<add key="serilog:minimum-level:controlled-by" value="$controlSwitch" />
<add key="serilog:enrich:with-property:AppName" value="XXXApp" />
<add key="serilog:enrich:with-property:AppComponent" value="XXXComponent" />
<add key="serilog:enrich:with-property:Environment" value="Dev" />
<add key="serilog:enrich:with-property:MachineName" value="%COMPUTERNAME%" />
<add key="serilog:using:Seq" value="Serilog.Sinks.Seq" />
<add key="serilog:write-to:Seq.serverUrl" value="http://localhost:5341" />
<add key="serilog:write-to:Seq.apiKey" value="xxxxxxxxxxx" />
<add key="serilog:write-to:Seq.controlLevelSwitch" value="$controlSwitch" />
(and then we had Web.config transforms to turn it into a "tokenized" file for production
Web.Release.config
<!-- SeriLog-->
<add key="serilog:enrich:with-property:Environment" value="__Release_EnvironmentName__"
xdt:Transform="Replace" xdt:Locator="Match(key)"/>
<add key="serilog:write-to:Seq.serverUrl" value="__app_serilogSeqUrl__"
xdt:Transform="Replace" xdt:Locator="Match(key)"/>
<add key="serilog:write-to:Seq.apiKey" value="__app_serilogApiKey__"
xdt:Transform="Replace" xdt:Locator="Match(key)"/>
Some of the most important parts of this configuration is :
configure your logger as soon as possible
call Log.CloseAndFlush(); at the very end to be sure all your log events are stored/pushed
add Enrich.FromLogContext() from Serilog, and some enrichers from SerilogWeb.Classic and SerilogWeb.WebApi, they can turn out to be super useful.
logging to a log server that properly supports structured logging (writing to files just has too many drawbacks) ... we used Seq and were very very happy about it (installed locally on every dev machine, and then a centralized instance in production). It supports searching/querying and dashboards and also dynamic log level control.

NServiceBus Dependency Injection not working for Saga Timeouts

I am having problems getting NServiceBus 4.6.1 dependency injection working with Saga timeouts. I am using self-hosting in an ASP.NET web application and have property injection setup. It works when messages are sent from web controllers however, when a Timeout message is handled in the saga the same DI property is not being set and is null.
Here are the key bits of the setup:
Global.asax.cs
public class MvcApplication : System.Web.HttpApplication
{
public static IWindsorContainer Container { get; private set; }
protected void Application_Start()
{
ConfigureIoC();
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
DeviceManagerDbInitializer.Instance.InitializeDatabase();
ConfigureNServiceBus();
}
protected void Application_End()
{
if (Container != null)
{
Container.Dispose();
}
}
private static void ConfigureIoC()
{
Container = new WindsorContainer()
.Install(FromAssembly.This());
var controllerFactory = new WindsorControllerFactory(Container.Kernel);
ControllerBuilder.Current.SetControllerFactory(controllerFactory);
GlobalConfiguration.Configuration.DependencyResolver
= new WindsorDependencyResolver(Container);
}
private void ConfigureNServiceBus()
{
Configure.ScaleOut(s => s.UseSingleBrokerQueue());
Configure.Instance.PeekInterval(500);
Configure.Instance.MaximumWaitTimeWhenIdle(2000);
Feature.Enable<TimeoutManager>();
Feature.Enable<Sagas>();
IStartableBus startableBus = Configure.With()
.DefineEndpointName("MyQueue")
.CastleWindsorBuilder(Container) //using NServiceBus CastleWindsor 4.6.1
.UseTransport<AzureStorageQueue>()
.UseAzureTimeoutPersister()
.AzureSagaPersister()
.PurgeOnStartup(false)
.UnicastBus()
.LoadMessageHandlers()
.RunHandlersUnderIncomingPrincipal(false)
.Log4Net(new DebugAppender { Threshold = Level.Warn })
.RijndaelEncryptionService()
.CreateBus();
Configure.Instance.ForInstallationOn<Windows>().Install();
startableBus.Start();
}
}
Saga class
public class MySaga: Saga<MySagaData>,
IAmStartedByMessages<StartMySagaCommand>,
IHandleMessages<SomeMessage>,
IHandleTimeouts<SomeTimeout>
{
public DependentService MyInjectedService {get; set;}
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<StartMySagaCommand>( message => message.MyId).ToSaga( saga => saga.MyId );
ConfigureMapping<SomeMessage>( message => message.MyId).ToSaga( saga => saga.MyId );
ConfigureMapping<SomeTimeout>( message => message.MyId).ToSaga( saga => saga.MyId );
}
public void Handle(SomeMessage message)
{
// Here MyInjectedService is fine
MyInjectedService.DoSomething(message);
}
public void Timeout(SomeTimeout state)
{
// Here MyInjectedService is always null
MyInjectedService.DoSomething(state);
}
}
I have tried solutions found here, here and here but none of them fixed the issue.
I figured out the problem here. Dependency injection was not working in the Saga's timeout handler because the Castle.Windsor lifestyle was set to LifestylePerWebRequest, e.g.:
public class WindsorServicesInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register( Component.For<DependentService>()
.LifestylePerWebRequest() );
}
}
After changing the Lifestyle to LifeStyleTransient it started working. Any of the other 'non web request' lifestyles should work as well here.
In our setup the NServiceBus host is running under the web application and the regular message handlers were fine because they are being called in a controller action, e.g.:
[HttpPost]
public ActionResult DoSomething( int myId)
{
_bus.Send( "MyBus", new SomeMessage { MyId = something.MyId } );
return View();
}
When the saga handles the SomeMessage message for this it must still be part of the web request and Windsor resolves the dependency as normal. However, the timeouts are fired some time later (in this case five minutes) are they are not part of a web request. Windsor is not able to resolve the DependentService object and it stays null.

NService Bus : "Unable to set the value for key: ScaleOut.UseSingleBrokerQueue."

I got this type of error when using nservicebus.structuremap. This is my code.
EndPointConfig.cs
namespace NSBus.Server
{
using NServiceBus;
/*
This class configures this endpoint as a Server. More information about how to configure the NServiceBus host
can be found here: http://particular.net/articles/the-nservicebus-host
*/
public class EndpointConfig : IConfigureThisEndpoint, AsA_Server, UsingTransport<Msmq>, IWantCustomInitialization
{
public static IBus Bus { get; private set; }
public void Init()
{
ConfigureIocTool();
}
private static void ConfigureIocTool()
{
var container = new Container(y => y.Scan(scan =>
{
scan.TheCallingAssembly();
scan.AssemblyContainingType<SanelibRegistry>();
scan.AssemblyContainingType<CommonRegistry>();
scan.AssemblyContainingType<CoreRegistry>();
scan.WithDefaultConventions();
scan.LookForRegistries();
}));
Bus = Configure.With()
.StructureMapBuilder(container)
.MsmqSubscriptionStorage()
.PurgeOnStartup(false)
.UnicastBus()
.ImpersonateSender(false)
.CreateBus()
.Start(() => Configure.Instance.ForInstallationOn<NServiceBus.Installation.Environments.Windows>().Install());
}
}
}
this code running successfully but i got error after some time.
Since I am using NServiceBus.Host, i don't need to create the bus in your endpoint config:
my initialization becomes something like this:
Since the AsA_Server role is beign used, it already will set the purge queue on startup to false, use unicast bus, etc.
The bus will be created and will be available via DI in all the message handlers.
public class EndpointConfig : IConfigureThisEndpoint, AsA_Server, UsingTransport<Msmq>, IWantCustomInitialization
{
public void Init()
{
var container = new Container(y => y.Scan(scan =>
{
scan.TheCallingAssembly();
scan.AssemblyContainingType<SanelibRegistry>();
scan.AssemblyContainingType<CommonRegistry>();
scan.AssemblyContainingType<CoreRegistry>();
scan.WithDefaultConventions();
scan.LookForRegistries();
}));
Configure.With()
.StructureMapBuilder(container)
.MsmqSubscriptionStorage();
}
}
For more details see:
http://particular.net/articles/the-nservicebus-host (section built-in configurations) and also
http://particular.net/articles/containers
Also, for subscription storage, either RavenDB or NHibernate (sql storage) is recommended for production and not msmq.
Hope this helps,
Nikunj Balar

Custom WCF service factory and hooking all calls

I would to intercept all post request to a custom WCF service (.net 3.5 SP1) in order to validate the presence of a specific header.
What I tried so far:
public class ServiceFactory : WebServiceHostFactory
{
protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var result = base.CreateServiceHost(serviceType, baseAddresses);
result.Opened += result_Opened;
return result;
}
private void result_Opened(object sender, EventArgs e)
{
var ctx = HttpContext.Current;
var request = ctx.Request;
if (request.HttpMethod == "POST")
{
// Validate if the request contains my header
if(request.Headers["MyHeader"] != "42")
throw new VeryBadThingsException("boom");
}
}
}
I also set up my svc files to use this factory.
This is sometimes working. Actually, not all my web services calls are hooked by the open event handler. The web service actual implementation is reached, so I suppose the problem is not the web service itself.
What should I do to correctly hook all incoming requests to my service?
PS: to descbribe a bit more my context, the service is hosted by SharePoint 2010. That means I can't change the web.config file (technically it's possible, but it's a pain to deploy and maintain).
And I acutally inherits the class Microsoft.SharePoint.Client.Services.MultipleBaseAddressWebServiceHostFactory
You should implement the IDispatchMessageInspector on the service side for this. The message instance passed to you in the AfterReceiveRequest method has a Headers property where you can check for your required headers.
Your current solution doesn't work for every call because it is only getting called when a new service host is opened. Once instantiated (and opened), that service host instance is servicing subsequent calls. But, because it is already opened, your code is not getting called on these subsequent calls.
You need to extend the WCF pipeline by adding a message inspector. The message inspector of the client will be responsible for adding the header and the message inspector of the server will be responsible for validating if the header exists.
Good practice: if you want to create custom headers, specify a custom namespace to ease lookup.
public static class WCFSOAPNamespaces
{
private const string root = "http://www.schemas.productname.com/";
public const string Headers = root + "headers/";
}
Server
The IDispatchMessageInspector handles all incoming messages to the server. This is the place where you will check for the existence of the header on the server.
public class DispatchMessageInspector : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
const string headerName = "nameOfTheHeader";
var someHeaderData = request.Headers.GetHeader<string>(headerName, WCFSOAPNamespaces.Headers);
//someHeaderData is the content that you want to check for every request. Attention: it throws System.ServiceModel.MessageHeaderException if the header doesn't exist
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState) { }
}
Client
The IClientMessageInspector handles messages on the client. If you need to add custom headers to the message, here is the place. If you do not need to add custom header, you can jump this first piece of code.
public class ClientMessageInspector : IClientMessageInspector
{
public void AfterReceiveReply(ref Message reply, object correlationState) { }
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
const string headerName = "nameOfTheHeader";
string headerContent = ""; //fill this variable with the content
var header = new MessageHeader<string>(headerContent ?? string.Empty);
var untyped = header.GetUntypedHeader(headerName, WCFSOAPNamespaces.Headers);
request.Headers.Add(untyped);
return null;
}
}
Both (client and server)
Even if you do not need a message inspector on the Client, you still need this configuration to add message inspection to your server-side application. More specifically, we need an EndpointBehavior to handle the MessageInspector. Then, we need to set the services endpoits to use this custom endpoint behavior.
In this example, I put the 2 inspectors in the same behavior, but you can create separate behaviors if you need.
public class EndpointBehavior : BehaviorExtensionElement, IEndpointBehavior
{
public EndpointBehavior() { }
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(new ClientMessageInspector());
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new DispatchMessageInspector());
}
public void Validate(ServiceEndpoint endpoint) { }
public override Type BehaviorType
{
get { return this.GetType(); }
}
protected override object CreateBehavior()
{
return new EndpointBehavior();
}
}
Then, set your Endpoint to use this behavior.
Programmatically
...
ServiceEndpoint endpoint;
...
endpoint.Behaviors.Add(new EndpointBehavior());
Config
...
<services>
<service name="...">
<endpoint address="..." binding="..." contract="..." behaviorConfiguration="endpointBehaviorName" />
</service>
...
<behaviors>
...
<endpointBehaviors>
<behavior name="endpointBehaviorName">
<customEndpointBehavior />
</behavior>
</endpointBehaviors>
</behaviors>
...
<extensions>
<behaviorExtensions>
<add name="customEndpointBehavior" type="FullNamespace.EndpointBehavior , AssemblyName" />
</behaviorExtensions>
</extensions>
From now on, all the requests will pass through this point.
Hope it helps.
Ok, I manage to swim between all objects, with the help of the code project article Add Custom Message Header in WCF 4 Calls.
Especially, it helped me to figure out how to properly attach a ServiceBehavior through code, using attributes.
I finally have this:
internal class ValidateSPFormDigestAttribute : Attribute, IServiceBehavior
{
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase host)
{
foreach (ChannelDispatcher cDispatcher in host.ChannelDispatchers)
{
foreach (EndpointDispatcher eDispatcher in cDispatcher.Endpoints)
{
eDispatcher.DispatchRuntime.MessageInspectors.Add(new ValidateSPFormDigestInspector());
}
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}
internal class ValidateSPFormDigestInspector : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
if (!SPUtility.ValidateFormDigest())
{
throw new FaultException(new FaultReason("Invalid form digest token"));
}
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
}
}
And I attach my custom behavior on the service directly:
[BasicHttpBindingServiceMetadataExchangeEndpoint]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
[ValidateSPFormDigest]
public class MyCustomService: IWidgetAdminService
The immediate benefits, is that I no more require creating a custom web service factory!

NServiceBus Simple Retry Mechanism, how do I debug (based on AsyncPages sample)

Hi
I have a website where a user signs up we need to add them to a legacy system via a soap webservice call. these webservice calls are queue'd for retry asynchronously...
I have copied the AsyncPages sample from the NServiceBus download.
However the messages seem to flit in and out of the queue without being handled (nor are they put on the error queue). (the message shows in the journal)
my website is configured like so:
<UnicastBusConfig>
<MessageEndpointMappings>
<add Messages="FooComMessages" Endpoint="FooComSignupService"/>
</MessageEndpointMappings>
</UnicastBusConfig>
And I have this test code on a button click:
Global.Bus.Send(new CreateMemberContractRequest[]
{new CreateMemberContractRequest()
{birthdate = DateTime.Parse("4 oct 1975")}
}
);
My message:
namespace FooComMessages
{
[Serializable]
public class CreateMemberContractRequest : IMessage
{
public DateTime birthdate { get; set; }
//.... snip....//
}
}
My Handler Config:
<MsmqTransportConfig InputQueue="FooComSignupService" ErrorQueue="FooComSignupServiceErrors" NumberOfWorkerThreads="1" MaxRetries="20" />
<UnicastBusConfig>
<MessageEndpointMappings>
<add Messages="FooComMessages" Endpoint="FooComSignupService" />
</MessageEndpointMappings>
</UnicastBusConfig>
My Handler Code:
namespace FooComSignupService
{
public class CreateMemberContractRequestHandler : IHandleMessages<CreateMemberContractRequest>
{
public IBus Bus { get; set; }
private static readonly ILog Logger = LogManager.GetLogger(typeof(CreateMemberContractRequestHandler));
public void Handle(CreateMemberContractRequest message)
{
Logger.Info("------------------------------------\nHandling CreateMemberContractRequest. id="+message.webdatabaseid);
throw new NotImplementedException();
}
}
}
EDIT:
I'm using NServiceBus.Host.exe to host the FooComSignupService
Does anyone have any idea what I could be doing wrong?
I can't hit any breakpoints in the Handle method either.
Cheers.
Murray.