How to host NServiceBus in ASP.NET Core - c#

I recently upgraded from NServiceBus 5x to 6.0.0-beta0004 to be able to host a ASP.NET Core application (whose main function is to listen to NServiceBus messages). I'm having problems with the startup of the host as the endpoint doesn't seem to subscribe to the publisher.
I am using the pubsub example to fix the problem. Apart from the projects that are in there by default, I added one extra project, a fresh ASP.NET Core project (based on the full .NET framework). I tried to use the exact same NServiceBus configuration, but instead of using the app/web.config, I am using the following configuration:
public class ConfigurationSource : IConfigurationSource
{
public T GetConfiguration<T>() where T : class, new()
{
UnicastBusConfig config = new UnicastBusConfig()
{
MessageEndpointMappings = new MessageEndpointMappingCollection()
};
var endpointMapping = new MessageEndpointMapping
{
AssemblyName = "Shared",
Endpoint = "Samples.PubSub.MyPublisher"
};
config.MessageEndpointMappings.Add(endpointMapping);
return config as T;
}
}
The Startup class was extended with the following code:
public IConfigurationRoot Configuration
{
get;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddMvc();
services.AddSingleton(this.GetBus());
}
private IEndpointInstance GetBus()
{
LogManager.Use<DefaultFactory> ().Level(NServiceBus.Logging.LogLevel.Info);
var endpointConfiguration = new EndpointConfiguration("Samples.PubSub.Subscriber3");
endpointConfiguration.UseSerialization<JsonSerializer>();
endpointConfiguration.DisableFeature<AutoSubscribe>();
endpointConfiguration.UsePersistence<InMemoryPersistence>();
endpointConfiguration.SendFailedMessagesTo("error");
endpointConfiguration.EnableInstallers();
// Skip web.config settings and use programmatic approach
endpointConfiguration.CustomConfigurationSource(new ConfigurationSource());
var endpointInstance = Endpoint.Start(endpointConfiguration).Result;
endpointInstance.Subscribe<IMyEvent>().Wait();
return endpointInstance;
}
The Message Handler is identical to the other Subscriber projects in the solution:
public class EventMessageHandler : IHandleMessages<IMyEvent>
{
static ILog log = LogManager.GetLogger<EventMessageHandler>();
public Task Handle(IMyEvent message, IMessageHandlerContext context)
{
log.Info($"Subscriber 2 received IEvent with Id {message.EventId}.");
log.Info($"Message time: {message.Time}.");
log.Info($"Message duration: {message.Duration}.");
return Task.FromResult(0);
}
}
If I run the sample, I notice Subscriber1 and Subscriber2 are subscribed perfectly and they receive messages if I execute some of the commands in the Publisher console application. However Subscriber3 doesn't appear to be doing anything. No exceptions are thrown and in this sample I don't seem to find any relevant log information that could lead to misconfiguration.
Has anyone tried a similar setup with ASP.NET Core & NServiceBus 6 and if so, what should I do next?
Update 04/04/2017
There are some updates that provide some interesting insights:
https://groups.google.com/forum/#!topic/particularsoftware/AVrA1E-VHtk
https://particular.net/blog/nservicebus-on-net-core-why-not

Related

.NET 6 attach to windows session switch

I am migration an app from .NET Framework to .NET 6. It starts Kestrel web server internally and it is intended to run as a Windows Service - in the old version that was achieved using Topshelf library, so I continued using it. One of the requirements is to listen for session switch events and send messages to third-party application.
In the old version of the application it was achieved again with the help of Topshelf. However, in the callback there was used static DI container to get access to services:
return HostFactory.New((hostConfigurator) =>
{
// ...
hostConfigurator.EnableSessionChanged();
hostConfigurator.Service<WindowService>((serviceConfigurator) =>
{
serviceConfigurator.ConstructUsing(hostSettings => new WindowService());
// ...
serviceConfigurator
.WhenSessionChanged((serviceHelper, arguments) => serviceHelper.SessionChange(arguments));
// ...
});
// ...
});
And this is the callback:
private void SessionChange(SessionChangedArguments changeDescription)
{
var sessionService =
DependencyInjectionContainer.GetRequiredService<IUserSessionMonitoringService>();
if (sessionService == null)
{
LOG.Warning(...);
return;
}
sessionService.SessionChanged(...);
}
So, here's the problem: in the callback a static DependencyInjectionContainer class is used to get the needed service/s. However in the new version of the app this approach won't work as this static class was abandoned. Also, I don't even have access to built-in .NET DI Container, because during the time the Topshelf is configured, the container isn't build.
As further steps I investigated what is Topshelf using under the hood for this functionality - that's SystemEvents.SessinSwitch event. I tried to attach an event handler to it from a hosted service, in which the needed services are injected. For the sake of the test, I just wanted to log some text in a file from the event handler. However, when sign out/in there's nothing in the file from those logs.
public class SystemSessionMonitoringHostedService : IHostedService
{
private readonly IUserSessionMonitoringService userSessionMonitoring;
private readonly ILogger<SystemSessionMonitoringHostedService> logger;
public SystemSessionMonitoringHostedService(
IUserSessionMonitoringService userSessionMonitoring,
ILogger<SystemSessionMonitoringHostedService> logger)
{
this.userSessionMonitoring = userSessionMonitoring;
this.logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
SystemEvents.SessionSwitch += OnSessionChanged;
return Task.CompletedTask;
}
private void OnSessionChanged(object sender, SessionSwitchEventArgs eventArgs)
{
var json = JsonConvert.SerializeObject(eventArgs);
logger.LogCritical($"Object: {json}");
}
}
Does anyone know if those SystemEvents works in .NET Core/5+? Am I missing something? Suggestions for workarounds are also welcome.

NServiceBus In MVC Core - Sending with Dependency Injection

I am currently attempting to send messages to an Azure ServiceBus queue via NServiceBus 7.1.9.
We are using
dotNet Core 2.0
NServiceBus 7.1.9
NServiceBus.MSDependencyInjection 0.1.4
NServiceBus.Persistence.AzureStorage 2.3.0
NServiceBus.Transport.AzureServiceBus 1.1.1
However, messages appear to send, but never arrive at the destination queue.
We are attempting to use the default Microsoft Dependency Injection, which again, appears to configure correctly, but doesn't send any messages.
In startup.cs we configure the service and add it to the DI container
private void ConfigureNServiceBus(IServiceCollection services)
{
// Sending Service
services.AddScoped<IServiceBusSender, ServiceBusSender>();
// Create endpoint
var endpointConfiguration = new EndpointConfiguration("LinkGreenEndpoint");
// Set Queue Name
var context = Configuration["ServiceBusContext"]?.ToLower() ?? "local";
endpointConfiguration.OverrideLocalAddress($"horticulture.nservicebusbackend.{context}");
// Use Azure ServiceBus Queue
var transport = endpointConfiguration.UseTransport<AzureServiceBusTransport>();
transport.ConnectionString(Configuration["ConnectionStrings:LinkGreenServiceBus"]);
// ConnectionStrings:LinkGreenServiceBus= "Endpoint=sb://xxx.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=xxx"
endpointConfiguration.UsePersistence<AzureStoragePersistence>();
endpointConfiguration.UseContainer<ServicesBuilder>(customizations =>
{
customizations.ExistingServices(services);
});
var endpoint = NServiceBus.Endpoint.Start(endpointConfiguration).GetAwaiter().GetResult();
// Add to Dependency Injection Container
services.AddScoped(typeof(IEndpointInstance), x => endpoint);
}
To send a message we use ServiceBusSender
public class ServiceBusSender : IServiceBusSender
{
private readonly IEndpointInstance _endpointInstance;
public ServiceBusSender(IEndpointInstance endpointInstance)
{
_endpointInstance = endpointInstance;
}
public Task Send(ICommand message)
{
// Also tried _endpointInstance.SendLocal(); - gives "handler not found"
return _endpointInstance.Send(message);
}
}
And an example of a command that we send:
public class CloneSupplierItemCommandBase : ICommand
{
public int BuyerId { get; set; }
public IdAndQuantity[] CloneItems { get; set; }
}
We currently use NServiceBus v5.0.0 in .NET 4.5 with this ServiceBus endpoint successfully.
Found the issue and managed to get messages posted to a queue.
Not 100% sure on which of the following was the solution
Set the name of the queue on creation to the endpoint
var context = Configuration["ServiceBusContext"]?.ToLower() ?? "local";
var endpointConfiguration = new EndpointConfiguration($"horticulture.nservicebusbackend.{context}");
Add the endpoint name to the Send() command
return _endpointInstance.Send(_endpoint, message);
It appears your endpoint configuration in the ConfigureNServiceBus method does not have any routing defined. Without this, the endpoint will not know where to deliver command messages.
I suspect you got it to work because you added the destination endpoint name in the Send() command directly. This works, but it can become unmanageable when you have a lot of endpoints and/or you want to modify the routing at run-time.
The better approach is to configure routing during start up. See Command Routing on Particular's documentation site.

How to stop a MassTransit bus in an ASP.NET Core server?

I am studying MassTransit and ASP.NET Core, dependancy injection and successfully implemented something that works. I plan to use the Kestrel web server.
So I had to configure my ASP.NET core project this way in the Startup.cs.
public void ConfigureServices(IServiceCollection services) {
...
var bus = Bus.Factory.CreateUsingRabbitMq(sbc => {
var host = sbc.Host(new Uri(address), h => {
h.Username("guest");
h.Password("guest");
});
});
services.AddSingleton<IBus>(bus);
services.AddScoped<IRequestClient<GetTagRequest, GetTagAnswer>>(x =>
new MessageRequestClient<GetTagRequest, GetTagAnswer>(x.GetRequiredService<IBus>(), new Uri(address + "/gettagrequest"), timeOut));
bus.Start(); // <= ok. how is the bus supposed to stop ?
...
Although this works fine, no tutorial mentioned anything about setting bus.Stop() in an ASP.NET core project. I read in MassTransit documentation that a running bus could prevent a graceful exit.
Is this a major concern for a Kestrel web server? I have read about process recycling and I am afraid a running bus would compromise this.
At which place can I place that bus.Stop() in an ASP.NET Core project ?
You can use ApplicationLifetime events. Just make your IBus object class level variable.
public class Startup
{
private IBus _bus;
public void ConfigureServices(IServiceCollection services) {
/* ... */
_bus = Bus.Factory.CreateUsingRabbitMq ...
/* ... */
}
public void Configure(IApplicationLifetime appLifetime)
{
appLifetime.ApplicationStarted.Register(() => _bus.Start());
appLifetime.ApplicationStopping.Register(() => _bus.Stop());
}
}
There is IApplicationLifetime in .NET Core, which has several CancellationToken properties, including ApplicationStopped. So when you need to do something after asp.net application is shutdown and all requests are processed (like stopping your bus) - you can do it like this:
// lifetime will be injected to Configure from DI container
public void Configure(IApplicationBuilder app, IApplicationLifetime lifetime) {
// subscribe to ApplicationStopped
lifetime.ApplicationStopped.Register(OnApplicationStopped);
// the rest
}
private void OnApplicationStopped() {
_bus.Stop();
}
It's always good to explicitly release resources even on process shutdown. For example, some message might still be in transition when the process will be killed after shutdown. Doing explicit dispose will allow this transition to complete.
To add to the existing answers:
If you use the MassTransit.AspNetCore's IServiceCollection.AddMassTransit() extension method, there's no need for a class level IBus instance. Startup's Configure() supports DI, so you can do this instead:
public void Configure(IApplicationLifetime appLifetime, IBus bus)
{
appLifetime.ApplicationStarted.Register(() => bus.Start());
appLifetime.ApplicationStopping.Register(() => bus.Stop());
}
If you don't want to use the package, you can still register IBus with the DI container (like in the question) and request it from Configure().
The ASP.NET Core DI sample uses an IHostedService instead:
public class BusService : IHostedService
{
private readonly IBusControl _busControl;
public BusService(IBusControl busControl)
{
_busControl = busControl;
}
public Task StartAsync(CancellationToken cancellationToken) =>
_busControl.StartAsync(cancellationToken);
public Task StopAsync(CancellationToken cancellationToken) =>
_busControl.StopAsync(cancellationToken);
}
The service is registered as:
services.AddSingleton<IHostedService, BusService>();
For more information on IHostedService, here's the doc page. I'm not sure I like the idea of having a background service just to stop the bus. Anyway, the sample repo I picked this example from is worth referring to.
The third option is to do it yourself in Main(). Something like:
var host = CreateWebHostBuilder(args).Build();
var bus = host.Services.GetRequiredService<IBusControl>();
await bus.StartAsync();
await host.RunAsync();
await bus.StopAsync();

ASP.NET Core Configuration Section in Startup

I am migrating a ASP.NET 5 RC1 project to ASP.NET Core, and have come across an interesting issue I've not yet seen, or found a solution for.
In order to use configuration settings within Startup I have previously retrived the configuration the following way
// Works fine for DI both in ASP.NET 5 RC1 and ASP.NET Core
services.Configure<SomeConfigurationClass>(Configuration.GetSection("SomeConfigurationSection"));
// How I previous retrieved the configuration for use in startup.
// No longer available in ASP.NET Core
var someConfigurationToUseLater = Configuration.Get<SomeConfigurationClass>("SomeConfigurationSection");
After updating to ASP.NET Core 1.0 it seems Configuration.Get<T>() is no longer available.
I have tried updating the code to use Configuration.GetValue<T>() however this does not seem to work with objects and will only work when providing a path to a value. This has left me with a workaround for most of my configuration classes like so
var someConfigurationName = "someConfiguration";
var someConfigurationClass = new SomeConfigurationClass()
{
Value1 = Configuration.GetValue<string>($"{someConfigurationName}:value1"),
Foo = Configuration.GetValue<string>($"{someConfigurationName}:foo"),
Bar = Configuration.GetValue<string>($"{someConfigurationName}:bar")
};
However this is an issue when the configuration class contains an array of objects. In my case an array of Client objects
public class ClientConfiguration
{
public Client[] Clients { get; set; }
}
With the following configuration
"configuredClients": {
"clients": [
{
"clientName": "Client1",
"clientId": "Client1"
},
{
"clientName": "Client2",
"clientId": "Client2"
}
]
}
Where this would previously bind to the Clients property of my configuration class no problem, I can no longer find a way of doing so in ASP.NET Core 1.0
Updated Answer
For ASP Core 1.1.0 generic model binding is now done using Get:
var config = Configuration.GetSection("configuredClients").Get<ClientConfiguration>();
Original Answer
How about this:
var config = Configuration.GetSection("configuredClients").Bind<ClientConfiguration>();
With ASP.NET Core 2.0 (basically Core 1.1+), the IConfiguration is injected to Startup, and that can be used within ConfigureServices() and Configure() methods.
As shown in the accepted answer, the configuration can be bound to an object. But if just one value is required, the key based approach works well.
The IConfiguration still works with colon : separated string keys. And for array, use 0-based index. Or use the the generic getValue<T>() method with same keys. See example below:
var clientId2 = Configuration["configuredClients:clients:1:clientId"]?.ToString();
var clientName1 = Configuration.GetValue<string>("configuredClients:clients:0:clientName");
To use the same configuration values in other classes (e.g. Controllers)
Either inject the IConfiguration and use the same key-based approach like above. Or
Register an instance of the strongly-typed configuration object with the DI container, and inject that object directly into client classes.
Sample code below:
//In Startup.ConfigureServices()
var clientConfig = Configuration.GetSection("configuredClients")
.Get<ClientConfiguration>();
services.AddSingleton(clientConfig);
//Controller
public class TestController : Controller
{
IConfiguration _configStore;
ClientConfiguration _clientConfiguration;
public TestController(IConfiguration configuration,
ClientConfiguration clientConfiguration)
{
_configStore = configuration;
_clientConfiguration = clientConfiguration;
}
public IActionResult Get()
{
//with IConfiguration
var clientId1 = _configStore
.GetValue<string>("configuredClients:clients:0:clientId");
//with strongly typed ClientConfiguration
var clientName1 = _clientConfiguration.Clients[0]?.ClientName;
return new OkObjectResult("Configuration test");
}
}
More examples here.
You don't read the configuration manually generally in ASP.NET Core yourself, instead you create an object that matches your definition. You can read more on that in the official documentation here.
E.g.
public class MyOptions
{
public string Option1 { get; set; }
public int Option2 { get; set; }
}
public void ConfigureServices(IServiceCollection services)
{
// Setup options with DI
services.AddOptions();
services.Configure<MyOptions>(Configuration);
}
Then you just inject the options IOptions<MyOptions> where you need them.
If you want to get first "clientName"(expected "Client1"), just write:
Configuration.GetSection("configuredClients")["clients:0:clientName"];
Update for comment
Install .NET Core 1.0.1 and go with #TomMakin's way.

Why does my MVC website hang on startup when using Nimbus with Azure Service Bus

I have been using MassTransit in my application and I have decided to see how Nimbus differs.
I am trying to write a proof of concept system, using Azure Service Bus and Nimbus with Autofac.
So far I have successfully written a Console Application which is configured to use Nimbus using Azure Service Bus and Autofac. I have followed all the examples that I can find online, in particular the PizzaSample from the Nimbus Samples.
This console application successfully starts up, configures the Bus, connects to Azure Service Bus and creates me a new Queue as expected in my Azure Service Bus. The application then begins to publish "Heartbeat" events, which seem to show that this application works fine.
Next I move onto a simple off the shelf, Visual Studio Templated MVC Application with no authentication.
I have again added Autofac, Nimbus, and Nimbus.Autofac and configured as per the above PizzaSample Web application. When I start the application, as expected Global.asax.cs is invoked, which configures Autofac and configures my Bus. When the Autofac container is built, my Bus is AutoActivated and the Instance of my Bus is Started. I can also see in Azure Service Bus that a queue for my web application has also been created successfully. However, this is where my application now hangs.
Upon further debugging, I seem to be able to hit my default controller and action (Home/Index) and I can step completely through all the code in this controller right upto and passed my "return View()". So all appears normal. However the view is never rendered and the browser sits at "Loading" until I kill "IIS Express".
If I amend my Web Applications Nimbus configuration to take out the Instance.Start(), and run the application without starting the bus, the application runs fine as expected, but obviously without the bus being started so I can't send/publish any messages.
Web Application Code
Global.asax.cs
public class MvcApplication : HttpApplication
{
protected void Application_Start()
{
ContainerConfig.Configure();
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
protected void Application_End()
{
ContainerConfig.TearDown();
}
}
ContainerConfig.cs
public static class ContainerConfig
{
private static IContainer _container;
public static void Configure()
{
var builder = new ContainerBuilder();
builder.RegisterModule<NimbusModule>();
builder.RegisterControllers(Assembly.GetExecutingAssembly());
_container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(_container));
}
public static void TearDown()
{
_container.Dispose();
}
}
NimbusModule.cs
public class NimbusModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
var connectionString = ConfigurationManager.ConnectionStrings["AzureServiceBus"].ConnectionString;
// You'll want a logger. There's a ConsoleLogger and a NullLogger if you really don't care. You can roll your
// own by implementing the ILogger interface if you want to hook it to an existing logging implementation.
builder.RegisterType<ConsoleLogger>()
.AsImplementedInterfaces()
.SingleInstance();
var messageContactAssembly = typeof(MyTestEvent).Assembly;
var handlerTypesProvider = new AssemblyScanningTypeProvider(messageContactAssembly);
builder.RegisterNimbus(handlerTypesProvider);
builder.Register(componentContext => new BusBuilder()
.Configure()
.WithConnectionString(connectionString)
.WithNames("Nimbus.Web", Environment.MachineName)
.WithTypesFrom(handlerTypesProvider)
.WithAutofacDefaults(componentContext)
.Build())
.As<IBus>()
.AutoActivate()
.OnActivated(c => c.Instance.Start())
.SingleInstance();
}
}
MyTestEvent.cs (From an independent referenced contract assembly)
public class MyTestEvent : IBusEvent
{
public MyTestEvent(Guid id)
{
Id= id;
}
public Guid Id { get; private set; }
}
HomeController.cs
public class HomeController : Controller
{
private readonly IBus _bus;
public HomeController(IBus bus)
{
_bus = bus;
}
public ActionResult Index()
{
_bus.Publish(new MyTestEvent(Guid.NewGuid()));
return View();
}
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
I obviously have some configuration wrong somewhere but I have spent hours looking at this and bashing my head against a wall. The closest that I can get to is thinking its something to do with the bus being started Async and perhaps somewhere the application is waiting for the Bus? But this is just a huge assumption and we all know assumptions are evil.
Has anyone seen this happen before and can offer any advice?

Categories

Resources