I have a saga I'm trying to run with masstransit that requires a refit client, and i want to have the client dependency injected into the saga.
I'm using MassTransit 5.2.3 with MassTransit.Extensions.DependencyInjection 5.2.3 to setup as follows:
...
var serviceCollection = new ServiceCollection();
serviceCollection.AddRefitClient<IClient>(...).ConfigureHttpClient(...);
serviceCollection.AddMassTransit(c =>
{
c.AddSaga<MySaga>();
});
serviceCollection.AddScoped<MySaga>();
serviceCollection.AddSingleton<ISagaRepository<MySaga>, MessageSessionSagaRepository<MySaga>>(x => new MessageSessionSagaRepository<MySaga>());
var serviceProvider = serviceCollection.BuildServiceProvider();
var bus = Bus.Factory.CreateUsingAzureServiceBus(cfg =>
{
var host = ...;
...
cfg.ReceiveEndpoint(host, "MyQueue", e =>
{
...
e.Saga<MySaga>(serviceProvider);
});
});
bus.Start();
...
the code for the saga is:
class MySaga :
ISaga,
InitiatedBy<IStep1>,
Orchestrates<IStep2>
{
private readonly IClient _client;
public Guid CorrelationId { get; set; }
public MySaga(IClient client)
{
_client = client;
}
public async Task Consume(ConsumeContext<IStep1> context) {...}
public async Task Consume(ConsumeContext<IStep2> context) {...}
}
This causes a "Failed to create the saga connector for MyNamespace.MySaga" exception, with inner exception "ConfigurationException: The saga MyNamespace.MySaga must have either a default constructor and a writable CorrelationId property or a constructor with a single Guid argument to assign the CorrelationId"
MassTransit focus for sagas had been moved to state machine sagas, so overall Automatonymous sagas are preferred. For the "classic" sagas, the requirement is given to you in the error message. You can use the code from the Injection_Specs.cs
var refitClient = ...;
var serviceCollection = new ServiceCollection();
serviceCollection.AddMassTransit(c =>
{
c.AddSaga<MySaga>();
});
serviceCollection.AddScoped<MySaga>();
serviceCollection.AddSingleton<ISagaRepository<MySaga>, MessageSessionSagaRepository<MySaga>>(x => new MessageSessionSagaRepository<MySaga>());
var serviceProvider = serviceCollection.BuildServiceProvider();
var bus = Bus.Factory.CreateUsingAzureServiceBus(cfg =>
{
var host = ...;
...
cfg.ReceiveEndpoint(host, "MyQueue", e =>
{
...
e.Saga<MySaga>(serviceProvider,
x => x.UseExecute(ctx => ctx.Saga.Client = refitClient));
});
});
bus.Start();
Of course, you will need to have the Client public property in the saga class and you won't be using the constructor injection. You also need those two constructors that the error message is telling you to have:
class MySaga :
ISaga,
InitiatedBy<IStep1>,
Orchestrates<IStep2>
{
public IClient Client { get; set; }
public Guid CorrelationId { get; set; }
public MySaga()
{
}
public MySaga(Guid correlationId)
{
CorrelationId = correlationId;
}
public async Task Consume(ConsumeContext<IStep1> context) {...}
public async Task Consume(ConsumeContext<IStep2> context) {...}
}
Design consideration
One thing that I have to add is that the Saga in MassTransit is close to the Process Manager pattern. It means that sagas should not have any logic except the orchestration logic and should not do anything else except for handling messages. So, I won't recommend using things like REST API clients inside sagas. If the saga needs to get some data to make a decision about message routing, it should use messages to get that data.
Related
I'm going around in circles with this. Is there any way to use the Castle Windsor dependency injection adapter (https://github.com/volosoft/castle-windsor-ms-adapter) with Azure Function apps?
The code example on the adaptor github page is for ASP.NET Core but I would have imagined that there'd be a way to specify the service provider used for a function app, too?
There have been no official documents on this but there is a feature request made by DavidJFowler
Please find the sample code from the above feature request.
The IJobActivatorEx implementation:
public class CastleWindsorJobActivator : IJobActivatorEx
{
private readonly WindsorContainer container;
public CastleWindsorJobActivator(WindsorContainer container) => this.container = container;
public T CreateInstance<T>(IFunctionInstanceEx functionInstance)
{
var disposer = functionInstance.InstanceServices.GetRequiredService<ScopeDisposable>();
disposer.Scope = container.BeginScope();
return container.Resolve<T>();
}
// Ensures a created Castle.Windsor scope is disposed at the end of the request
public sealed class ScopeDisposable : IDisposable
{
public IDisposable Scope { get; set; }
public void Dispose() => this.Scope?.Dispose();
}
public T CreateInstance<T>()
{
var disposer = container.Resolve<ScopeDisposable>();
disposer.Scope = container.BeginScope();
return container.Resolve<T>();
}
}
Startup class:
[assembly: FunctionsStartup(typeof(FunctionApp2.Startup))]
namespace FunctionApp2
{
public class Startup: FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var container = new WindsorContainer();
container.Register(Component.For<IScoped1>().ImplementedBy<Scoped1>().LifestyleScoped())
.Register(Component.For<IScoped2>().ImplementedBy<Scoped2>().LifestyleScoped())
.Register(Component.For<ISingleton1>().ImplementedBy<Singleton1>())
.Register(Component.For<ISingleton2>().ImplementedBy<Singleton2>());
// register function classes in container
var functions = Assembly.GetExecutingAssembly().GetTypes().Where(t =>
t.GetMethods().Any(m => m.GetCustomAttributes(typeof(FunctionNameAttribute), false).Any()));
foreach (var function in functions)
{
container.Register(Component.For(function).LifestyleScoped());
}
builder.Services.AddScoped<CastleWindsorJobActivator.ScopeDisposable>()
.AddSingleton<IJobActivator>(new CastleWindsorJobActivator(container));
container.AddServices(builder.Services);
}
}
}
Example function:
public class Function1
{
public Function1(IScoped1 scoped1, IScoped2 scoped2, ISingleton1 singleton1, ISingleton2 singleton2)
{
}
[FunctionName("Function1")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
return name != null
? (ActionResult)new OkObjectResult($"Hello, {name}")
: new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}
}
For now, this feature isn't officially released but you can follow other dependency injection patterns. You can raise a feature request from Microsoft QnA or from Azure Portal.
I am having an ASP.net core 3.0 app and I want to see if I can register some of my Orleans Cluster Clients asynchronously on app startup, due to the fact the creation and making the connections to Orleans Cluster are heavy. According to this article I created my own IHostedService, but when I implemented startAsync method I am not sure how to get the autofac container which I am using in Startup.cs and update it with my clients registrations. I have read this but see my below code, still I don't see the clients are getting registered. Is it doable or am I missing anything here? thanks!
Startup.cs
...
public static IServiceProvider ConfigureServices(IServiceCollection services)
{
var coreBuilder = new ContainerBuilder();
// other autofac registrations...
services.AddHostedService<MyService>();
coreBuilder.populate(services);
var container = coreBuilder.Build();
var serviceProvider = new AutofacServiceProvider(container);
return serviceProvider;
}
MyService.cs
public MyService : IHostedService
{
private readonly IServiceProvider _serviceProvider;
public MyService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
// get the autofac container from Startup.cs and update with cluster client registrations?
using(var scope = this._serviceProvider.GetRequiredService<ILifeTimeScope>()
.BeginLifeTimeScope(builder => do registration here...)) {}
}
// noop
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
You cannot update the DI container on-the-fly like that. Once it's built, it's built.
You have another option: make a factory class that caches the clients, initialize them in the background, then retrieve them from the factory.
class MyService
{
// ...
}
class MyServiceFactory
{
private ConcurrentDictionary<string, MyService> _instances = new ConcurrentDictionary<string, MyService>();
public async Task<MyService> CreateAsync(string key)
{
if (_instances.TryGetValue(key, out var service))
{
return service;
}
// perform expensive initialization
// ...
service = new MyService();
_instances[key] = service;
return service;
}
}
class MyServiceInitializer: BackgroundService
{
private MyServiceFactory _serviceFactory;
public MyServiceInitializer(MyServiceFactory serviceFactory)
{
_serviceFactory = serviceFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await _serviceFactory.CreateAsync("first instance");
await _serviceFactory.CreateAsync("second instance");
}
}
Register the factory as singleton, (or make Instances a static property).
services.AddSingleton<MyServiceFactory>();
services.AddHostedService<MyServiceInitializer>();
Then resolve an instance you need. It will resolve instantly, because it's been initialized in the background.
class MyController
{
private MyServiceFactory _serviceFactory;
public MyController(MyServiceFactory serviceFactory)
{
_serviceFactory = serviceFactory;
}
[HttpGet]
public async Task<ActionResult> Index()
{
var service = await _serviceFactory.CreateAsync("first instance");
// use the service
}
}
I am trying to wire up a background thread that will update the database once an hour from Active Directory. I am not sure how to pass the current
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Connection")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1).AddSessionStateTempDataProvider();
services.AddSession();
services.AddHttpContextAccessor();
services.AddSingleton(Configuration);
services.AddScoped<IAppDbRepository, AppDbRepository>();
services.AddScoped<IActiveDirectoryUtility, ActiveDirectoryUtility>();
services.AddScoped<IActiveDirectoryManager, ActiveDirectoryManager>();
services.AddHostedService<LdapManager>();
services.AddScoped<ILdapManager, LdapManager>();
}
In the LdapManager class I would like to call the UpdateUsers method every hour:
public class LdapManager : ILdapManager, IHostedService
{
private IConfiguration _configuration = null;
private Logging _logger;
private List<string> ldapConnectorForDirectoryEntries = new List<string>();
public LdapManager(IConfiguration configuration)
{
_configuration = configuration;
UpdateUsers();
SyncActiveDirectoryUsers();
}
public void SyncActiveDirectoryUsers()
{
try
{
using (var waitHandle = new AutoResetEvent(false))
{
ThreadPool.RegisterWaitForSingleObject(waitHandle, (state, timeout) => { UpdateUsers(); }, null, TimeSpan.FromHours(1), false);
}
}
catch
{
throw;
}
}
}
The UpdateUsers() method should be able to call the applicationDBContext.SaveChanges() method.
How can I ensure that the LDAP manger class can use the Application DB context?
You probably want class LdapManager : BackgroundService, ILdapManager
BackgroundService is .NET Core 2.1, there is a code sample available for core 2.0
Inject IServiceScopeFactory and override Task ExecuteAsync( ), run a while loop there.
while(!stoppingToken.IsCancellationRequested)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
...; // do your stuff
}
await Task.Delay(myConfig.BackgroundDelay, stoppingToken);
}
And here is a good read about this on MSDN, including the code sample for 2.0
For accessing ApplicationDbContext from HostedService.
DbHostedService
public class DbHostedService : IHostedService
{
private readonly ILogger _logger;
public DbHostedService(IServiceProvider services,
ILogger<DbHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Consume Scoped Service Hosted Service is starting.");
DoWork();
return Task.CompletedTask;
}
private void DoWork()
{
_logger.LogInformation("Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var user = context.Users.LastOrDefault();
_logger.LogInformation(user?.UserName);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Consume Scoped Service Hosted Service is stopping.");
return Task.CompletedTask;
}
}
Register DbHostedService
services.AddHostedService<DbHostedService>();
I have following code:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IWsApiProvider, WsApiProvider>();
services.AddScoped<IApplicationUserRepository, ApplicationUserRepository>();
...
}
WsApiProvider has following:
public Guid SessionId { get; set; }
public IWSocketProvider WsApi { get; set; }
In Invoke method I'm updating these properties:
public Task Invoke(HttpContext httpContext, IOptions<AppSettings> appSettings)
{
...
this._wsApiProvider.SessionId = sessionGuid;
this._wsApiProvider.WsApi = connection;
...
}
And then I'm going to Controller where I injected Repository:
public AccountController(IApplicationUserRepository applicationUserRepository)
{
this._applicationUserRepository = applicationUserRepository;
}
public ApplicationUserRepository(IWsApiProvider wsApi) : base(wsApi)
{
}
And here I have wsApi object with empty properties. Two questions:
Why in repository constructor I have this object with empty properties?
Is there any way to create one instance of IWsApiProvider for all dependencies per request (non-singleton solution)?
Thank you in advance
UPDATED. The whole middleware class:
public class WsApiMiddleware
{
private readonly RequestDelegate _next;
private readonly IWsApiProvider _wsApiProvider;
private const string QisSessionId = "QisSessionId";
public WsApiMiddleware(RequestDelegate next, IWsApiProvider wsApiProvider)
{
_next = next;
this._wsApiProvider = wsApiProvider;
}
public Task Invoke(HttpContext httpContext, IOptions<AppSettings> appSettings)
{
var sessionId = httpContext.Request.Cookies[QisSessionId];
var sessionGuid = Guid.Empty;
if (!string.IsNullOrEmpty(sessionId))
{
Guid.TryParse(sessionId, out sessionGuid);
}
var connection = ConnectionsPool.GetSocket(sessionGuid);
if (connection == null)
{
connection = new WSocketProvider(null);
var connectTask = Task.Run(async () =>
await connection.Connect(appSettings.Value.WsApiServerEndPointUri, CancellationToken.None)
);
Task.WaitAll(connectTask);
var sessionService = new SessionService(connection);
var sessionOpenTask = Task.Run(async () =>
{
SessionDataState sessionData = null;
//TODO [W-8/6/2017] - think about better solution for situation when sessionId doesn't exist on the server
try
{
sessionData = await sessionService.OpenSession(sessionGuid != Guid.Empty ? (Guid?)sessionGuid : null);
}
catch (Exception ex)
{
sessionData = await sessionService.OpenSession();
}
sessionGuid = sessionData.SessionId;
if (!sessionData.ClientType.HasValue)
{
await sessionService.LoginClient();
}
ConnectionsPool.TryAddConnection(sessionGuid, connection);
httpContext.Response.Cookies.Append(QisSessionId, sessionGuid.ToString());
});
Task.WaitAll(sessionOpenTask);
}
this._wsApiProvider.SessionId = sessionGuid;
this._wsApiProvider.WsApi = connection;
return this._next(httpContext);
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class WsApiMiddlewareExtensions
{
public static IApplicationBuilder UseWsApiMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<WsApiMiddleware>();
}
}
From the ASP.Net core middleware doc :
Middleware is constructed once per application lifetime. Because middleware is constructed at app startup, not per-request, scoped lifetime services used by middleware constructors are not shared with other dependency-injected types during each request.
And the most important part in you situation:
If you must share a scoped service between your middleware and other types, add these services to the Invoke method's signature. The Invoke method can accept additional parameters that are populated by dependency injection.
Since IWsApiProvider is a scoped service(i.e. per request), it should be passed as an argument to the Invoke method, as follow:
public class WsApiMiddleware
{
private readonly RequestDelegate _next;
// no longer passed in the constructor
public WsApiMiddleware(RequestDelegate next)
{
_next = next;
}
// passed as an argument to Invoke, via dependency injection
public Task Invoke(HttpContext httpContext, IWsApiProvider wsApiProvider, IOptions<AppSettings> appSettings)
{
wsApiProvider.SessionId = "SessionId";
wsApiProvider.WsApi = "WsApi";
return this._next(httpContext);
}
}
We have a multi-database solution and are passing the connection string to a factory function like so:
container.Register<IDbContextFactory>(
f => new DynamicDbContextFactory(ClientConfig.GetConnectionString()),
new PerScopeLifetime());
ClientConfig contains a static dictionary that gets populated on app start that maps a sub domain to a connection string. It seems that this approach is causing a memory leak (not 100% sure about this causing the leak but there is a leak).
public class ClientConfig
{
private static ConcurrentDictionary<string, string> ConnectionStringManager
{
get;
set;
}
// etc.
}
My question is in MVC what is the best way to hold a list of connection strings that can be easily looked up on each request in order to pass that down the chain.
Edit : The question was initially tagged with Autofac
With Autofac you don't have to use a dictionary and something like that to do what you want. You can use a custom parameter :
public class ConnectionStringParameter : Parameter
{
public override Boolean CanSupplyValue(ParameterInfo pi,
IComponentContext context,
out Func<Object> valueProvider)
{
valueProvider = null;
if (pi.ParameterType == typeof(String)
&& String.Equals(pi.Name, "connectionString",
StringComparison.OrdinalIgnoreCase))
{
valueProvider = () =>
{
// get connectionstring based on HttpContext.Current.Request.Url.Host
return String.Empty;
};
}
return valueProvider != null;
}
}
Then register your Parameter using a Module
public class ConnectionStringModule : Autofac.Module
{
protected override void AttachToComponentRegistration(
IComponentRegistry componentRegistry, IComponentRegistration registration)
{
registration.Preparing += registration_Preparing;
}
private void registration_Preparing(Object sender, PreparingEventArgs e)
{
Parameter[] parameters = new Parameter[] { new ConnectionStringParameter() };
e.Parameters = e.Parameters.Concat(parameters);
}
}
Module you have to register inside your container using
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterModule(new ConnectionStringModule());
Each time Autofac have to resolve a parameter of type String named connectionString it will used the custom parameter and get your connectionstring based on what you want.
By the way this code sample use HttpContext.Current. In case of a multithreaded process it may return null. I don't recommend using HttpContext.Current for such things. You can use an intermediate class instead of accessing it, for example a IConnectionstringProvider interface.
public interface IConnectionstringProvider
{
String ConnectionString { get; }
}
public class ConnectionStringProvider : IConnectionstringProvider
{
public ConnectionStringProvider(Strong host)
{
// get connectionstring based on host
this._connectionString = String.Empty;
}
private readonly String _connectionString;
public String ConnectionString
{
get { return this._connectionString; }
}
}
Inside your Parameter you will have to change the valueProvider by
valueProvider = () =>
{
return context.Resolve<IConnectionstringProvider>().ConnectionString;
};
And finally you will have to register your IConnectionstringProvider at the beginning of the request lifetimescope :
class Program
{
static void Main(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterModule(new ConnectionStringModule());
IContainer container = builder.Build();
container.ChildLifetimeScopeBeginning += container_ChildLifetimeScopeBeginning;
}
private static void container_ChildLifetimeScopeBeginning(
Object sender, LifetimeScopeBeginningEventArgs e)
{
String host = HttpContext.Current.Request.Url.Host;
ContainerBuilder childLifetimeScopeBuilder = new ContainerBuilder();
childLifetimeScopeBuilder.RegisterInstance(new ConnectionStringProvider(host))
.As<IConnectionstringProvider>()
.SingleInstance();
childLifetimeScopeBuilder.Update(e.LifetimeScope.ComponentRegistry);
}
}
Of course there is many way to do it but you have the idea