Manual WebJob SDK function binding - c#

I have a Service Bus queue that I want to consume off of. All of the samples I have found recommend writing something like this:
class Program
{
private static String ServiceBusConnectionString = ConfigurationManager.ConnectionStrings["Microsoft.ServiceBus.ConnectionString"].ConnectionString;
static void Main()
{
var jobHostConfiguration = new JobHostConfiguration
{
ServiceBusConnectionString = ServiceBusConnectionString,
};
var jobHost = new JobHost(jobHostConfiguration);
jobHost.RunAndBlock();
}
}
public class QueueItem
{
public String Name;
public Int64 Id;
}
public class Functions
{
public void ProcessQueueItem([ServiceBusTrigger("queue-name")] QueueItem queueItem)
{
// TODO: process queue item
}
}
The problem with the above code is that the queue name is hard coded in my program. I want to be able to get the queue name from configuration like I do with the queue connection string. Unfortunately, attributes can only be passed compile time constants, so I can't pass in some statically initialized string that comes from configuration.
I looked around a bit and was unable to find any other way to setup Service Bus function binding. Is it just not possible?

You can use a custom INameResolver to accomplish what you want.
One of the official samples here covers exactly your scenario. Take a look the ShutdownMonitor function in Functions.cs and then at the ConfigNameResolver class.

Related

How to get the correct instance from IServiceProvider

I'm trying to handle a number of subscriptions in and Azure.Messaging.SericeBus.
The Docs suggest that I should register my ServiceBusClient, ServiceBusSender and ServiceBusProcessor for DI.
For the latter, it means I need an instance for each subscription, so I have something like this...
services.AddSingleton(provider =>
{
var options = provider.GetService<IOptions<WorkerOptions>>()
.Value;
var client = provider.GetService<ServiceBusClient>();
var subscription = // code to determine the subscription to use
return client.CreateProcessor(options.Topic, subscription);
}
Now I need to instantiate the processors and that's where I come unstuck. In this example I'm using an IServiceProvider but I think I'm going to have the same problem just using DI and constructor injection.
var processor = MyServiceProvider.GetService<ServiceBusProcessor>()!;
How do I get a specific ServiceBusProcessor?
I thought I should be able to "name" each instance but that doesn't appear to be possible.
What am I missing?
With .NET Core DI, you need to use separate types to discern between the injection targets. One way to do this is to create a dedicated class for each subscription, e.g.
public abstract class ProcessorProvider
{
private readonly ServiceBusProcessor _proc;
public ProcessorProvider(ServiceBusProcessor proc)
{
_proc = proc;
}
public virtual ServiceBusProcessor Processor { get => _proc; }
}
public class ProcessorProviderA : ProcessorProvider
{
public ProcessorProviderA(ServiceBusProcessor proc): base(proc) {}
}
public class ProcessorProviderB : ProcessorProvider
{
public ProcessorProviderB(ServiceBusProcessor proc): base(proc) {}
}
In your classes, you do not inject the processor directly, but rely on the different classes that provide the processor, e.g.
public class ClassThatReliesOnSubscriptionA
{
private readonly ServiceBusProcessor _proc;
public ClassThatReliesOnSubscriptionA(ProcessorProviderA procProv)
{
_proc = _procProv.Processor;
}
}
// Same for subscription B
This way, you can add a registration for IProcessorProviderForSubscriptionA and IProcessorProviderForSubscriptionB like this:
services.AddSingleton<ProcessorProviderA>(provider =>
{
var options = provider.GetService<IOptions<WorkerOptions>>()
.Value;
var client = provider.GetService<ServiceBusClient>();
var subscription = // Access subscription A
var proc = client.CreateProcessor(options.Topic, subscription);
return new ProcessorProviderA(proc);
}
services.AddSingleton<ProcessorProviderB>(provider =>
{
var options = provider.GetService<IOptions<WorkerOptions>>()
.Value;
var client = provider.GetService<ServiceBusClient>();
var subscription = // Access subscription B
var proc = client.CreateProcessor(options.Topic, subscription);
return new ProcessorProviderB(proc);
}
This way the inversion of control container can discern between the types that are required by the classes (ClassThatReliesOnSubscriptionA in this sample). Please note that above code is a sample that can give you an outline on how to solve the problem. You can optimize the code further, e.g. by moving common steps into ProcessorProvider. In order to improve "mockability" in unit tests, you could also use marker interfaces instead of the classes.

Use current Serilog ILogger instance to format a message into a string (but not print it)

I have a custom LogEntry class that is designed for easy serialization. There are some log entries which will occur when doing an operation on an object that I want to send to the user. I also want to send those same entries to the console/whatever serilog sinks that are configured. My current approach looks like this:
public static void Info(this Case c, ILogger log, string message, params object[] values)
{
log.Information(message, values);
var formattedMessage = string.Empty; // TODO: use serilog to get the string.
// This is what I'm asking for help on!
var entry = new LogEntry
{
LogLevel = LogLevel.Info,
Message = formattedMessage,
PhaseType = c.CurrentPhase // <- it would be convenient if I could enrich
// the current serilog log with this info,
// but I don't know how to do that either.
};
c.Log.Add(entry);
}
Where my Case class is a POCO ready to be sent into newtonsoft for serializing. For the sake of completeness, the Case class contains this definition:
public class Case
{
// ...
public List<LogEntry> Log { get; set; } = new List<LogEntry>();
}
Perhaps my approach is entirely wrong. Hopefully I've given enough context to explain what I'm trying to accomplish. If this question takes me down a happier path: how do I create a custom temporary sink for an ILogger instance?
One option is to collect the Serilog LogEvents created by a logger call and use them to construct the rendered messages.
Here's an executable sketch of the general idea.
using System;
using System.Collections.Generic;
using Serilog;
using Serilog.Core;
using Serilog.Events;
// dotnet add package Serilog.Sinks.Console
class LogEventCollection : ILogEventEnricher
{
// Note, this is not threadsafe
public List<LogEvent> Events { get; } = new List<LogEvent>();
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory _)
{
Events.Add(logEvent);
}
}
class Program
{
static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
var collection = new LogEventCollection();
// Create an `ILogger` with the collector wired in. This
// also works with `LogContext.Push()`.
var collected = Log.ForContext(collection);
collected.Information("Hello");
collected.Information("World");
// One entry for each call above
foreach (var evt in collection.Events)
{
Console.WriteLine(evt.RenderMessage(null));
}
}
}
Output:
[14:23:34 INF] Hello
[14:23:34 INF] World
Hello
World

Configuring MassTransit's Ninject bindings after initialisation

I want to configure MassTransit at one point in my code (using WebActivator) and configure the message handlers in another (a Ninject module). Is there a way I can achieve this? The documentation here shows how to perform what I need in one step, but to do anyhting else, it looks like I need to get an instance of a ServiceBusConfigurator, which doesn't seem to be available from the preexisting IServiceBus
Configuration and Creation of the IServiceBus cannot be separated.
That means, the only option you have is to gather the configuration information some more time before creating the bus.
As the doc you linked states, the meta data information made available by ninject is not sufficient to create the subscriptions. This basically means that you've got to create your own metadata model. Let's make an example, which can be used with single registrations but also with convention based registrations:
Hint: You should treat the following code snippets as psuedo code as i've written them from memory. It's highly likely that it won't compile.
Metadata Model
public class SubscriptionMetadata
{
public SubscriptionMetadata(Type consumer)
{
if(!typeof(IConsumer).IsAssignableFrom(consumer))
{
string message = string.Format(
"{0} does not implement {1}",
typeof(IConsumer).Name,
consumer.Name);
throw new ArgumentOutOfRangeException("consumer", message);
}
this.ConsumerType = consumer;
}
public Type ConsumerType { get; private set; }
}
Registration of Metadata
Now this can be used like this in a Ninject module:
Bind<SubscriptionMetadata>()
.ToConstant(new SubscriptionMetadata(typeof(FooConsumer));
If you're going to use it a lot i'd recommend writing an extension method:
public static class SubscriptionBindingExtensions
{
public static void BindConsumer<T>(this IBindingRoot bindingRoot)
where T : IConsumer
{
Bind<SubscriptionMetadata>()
.ToConstant(new SubscriptionMetadata(typeof(T));
}
}
and usage (#Module):
BindConsumer<FooConsumer>();
IServiceBus creation
Now you would adapt the IServiceBus creation as follows:
var kernel = new StandardKernel();
// 2nd Step left out: load all IModule`s ..
var bus = ServiceBusFactory.New(sbc =>
{
//other configuration options
foreach(var metadata in kernel.GetAll<SubscriptionMetadata>())
{
sbc.Subscribe(subs =>
{
subs.Consumer(metadata.ConsumerType, kernel)
});
}
});
Convention based binding of Consumers
It can also be used in conjunction with conventions by leveraging the IBindingCreator interface. If you wish, i can post an example.

Introducing ASP.Net OWIN to an existing TopShelf Windows Service

I have an existing Windows Service that starts up, reads some configs, spins up some threads and does some useful work.
Currently I log that work (through TopShelf.Log4Net / Log4Net) to disk (or console).
I was thinking it would be nice to expose an API to allow people to query the service (and in the future maybe even config it on the fly)
I'm having difficulty figuring out an appropriate way to plumb the two together though. My existing windows service has a bunch of worker threads and it knows the context of what work is being done, stats and progress and things like that.
But in the context of an ApiController actually handling a request I can't see an obvious/easy means of getting at those stats. I tried passing some object references in the Properties of the IAppBuilder in my Startup class, but any time I explicitly do the config myself I seem to lose the default MVC routes that I had.
Anyone have any suggestions on integrating OWIN into/on top of an existing service?
EDIT: Added code:
So I have a very basic TopShelf runner;
HostFactory.Run(x =>
{
x.Service<MyService>(s =>
{
s.ConstructUsing(name => new MyService());
s.WhenStarted(lb => lb.Start());
s.WhenStopped(lb => lb.Stop());
});
x.RunAsLocalSystem();
And within MyService I had a method StartWorkers() that goes off, starts up a bunch of workers, and keeps track of them.
public class MyService
{
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private List<Worker> workers { get; set; }
private List<Thread> _threads { get; set; }
public MyService()
{
StartWorkers();
}
Now, I want to be able to query for the status of those workers from API requests, so I was thinking I could do something like;
public class MyService
{
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private List<Worker> workers { get; set; }
private List<Thread> _threads { get; set; }
private IDisposable _webServer { get; set; }
public MyService()
{
StartWorkers();
StartWebServer();
}
private void StartWebServer()
{
const string baseAddress = "http://localhost:10281/";
_webServer = WebApp.Start<Startup>(url: baseAddress);
log.DebugFormat("Loaded webserver at address={0}", baseAddress);
}
All of the OWIN code right now is vanilla from the samples.
When I test this code, I can hit the /api/values sample endpoint which is served from the ValuesController, and all is well.
Now where I'm missing a piece of glue is how to access anything useful in my application from my controller. I can do something naive like making the Worker list public static;
public class MyService
{
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public static List<Worker> workers { get; set; }
In which case from my controller I can do something like;
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
// nonsense/test code
return MyService.workers.Select(i => i.ErrorsEncountered.ToString()).ToArray();
}
There must be a better way of access the internals of my application without manipulation visbility.
What I've seen is that I can pass objects into the Properties of the IAppBuilder when using WebApp.Start(). These are visible then from the route configuration, and I see to be able to access them from within my ApiController, e.g.;
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
var someWorker = base.ControllerContext.Configuration.Properties["someWorkerReference"];
The problem I have when I go down that route is that 'WebApp.Start(url: baseAddress);' works and my routes all function, but using the WebApp.Start() method and passing in an Action causes my routes to break
So instead of spending time fixing the routes, I wanted to see if there's an obvious/known/official means of passing "context" into the web server
So this is where having a container is super helpful. If you have something like public class MyService : IStatusProvider {} then you can register both MyService and IStatusProvider to the same instance you. How to use DI container when OwinStartup talks about using OWIN & dependency injection. And you would add the container setup to start of the program, changing s.ConstructUsing(name => new MyService()); to
s.ConstructUsing(name => {
var container = new Container();
// container setup
return container.resolve<MyService>(); // method depends on your container
});

Can Automapper be used in a console application?

Is it possible to use automapper in a console application?
Its Getting Started Page suggests the bootstrapper class be called from Application start up, but there are no further details about a class to add and call from Main().
How do I go about using this in a simple console app?
You can initialize Automapper in the console startup, there's no limitations; the Application_start is the startup place for a web program in .net/iis, ie code that is called only once. Any configuration that you must call at the start of a web project goes in this method.
edit for comment: if you don't want to create your mappings on the fly, but would rather have a place to initialize all your mappings, just create a function called InitializeAutomapper and make the Mapper.Configure<X, Y> calls in here. Then in your Main() method, just call the function. There are lots of ways to handle configuration, but this is the simpler way to handle it.
code sample
class Program
{
static void Main(string[] args)
{
// the app is starting here
InitializeAutomapper();
// we're configured, let's go!
DoStuff();
}
static void InitializeAutomapper()
{
AutoMapper.Mapper.CreateMap<TypeA, TypeB>();
AutoMapper.Mapper.CreateMap<TypeC, TypeD>();
AutoMapper.Mapper.CreateMap<TypeE, TypeF>();
}
}
I know that this is an old question, but if you found this I want to add an update: Automaper does not allow static initialization anymore.
You can check more here
Below, I'm providing a full example of how to use it on a console app. Hope this might be helpful for someone in the future.
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<MyClass, MyClassDTO>();
});
IMapper mapper = config.CreateMapper();
var myClass = new MyClass(){
Id = 10,
Name = "Test"
};
var dst = mapper.Map<MyClass, MyClassDTO>(myClass);
Console.WriteLine(dst.Id);
}
}
class MyClass
{
public int Id {get;set;}
public string Name {get;set;}
}
public class MyClassDTO
{
public int Id {get;set;}
public string Name {get;set;}
}
Do not forget to include using AutoMapper;
Yes, but it appears to have a dependency on System.Web, which must be included too.
(See Mysterious disappearing reference for more details)

Categories

Resources