Background
I'm trying to set up a Web API 2 which needs to communicate to a NServicebus Endpoint.
I will need to implement IoC, which will be done using Autofac.
What I have
A controller defined like so:
[RoutePrefix("api")]
public class Controller : ApiController
{
private IEndpointInstance EndpointInstance { get; set; }
public public MyController(IEndpointInstance endpointInstance)
{
this.EndpointInstance = endpointInstance;
}
[HttpGet]
[Route("dostuff")]
public async Task DoStuff()
{
var command = new MyCommand
{
...
};
await this.EndpointInstance.SendLocal(command);
}
}
And in global.asax
Application_Start
protected async void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
await RegisterNServiceBusWithAutofac();
}
RegisterNServiceBusWithAutofac
private async Task RegisterNServiceBusWithAutofac()
{
var builder = new ContainerBuilder();
var endpointConfiguration = await GetEndpointConfiguration("My.Service");
var endpointInstance = await Endpoint.Start(endpointConfiguration);
builder.RegisterInstance(endpointInstance);
var container = builder.Build();
endpointConfiguration.UseContainer<AutofacBuilder>(c => c.ExistingLifetimeScope(container));
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
}
GetEndpointConfiguration
private static async Task<EndpointConfiguration> GetEndpointConfiguration(string name)
{
var endpointConfiguration = new EndpointConfiguration(name);
// Set transport.
var routing = endpointConfiguration.UseTransport<MsmqTransport>().Routing();
// Register publish to self
routing.RegisterPublisher(typeof(EventHasFinished), name);
endpointConfiguration.UseSerialization<JsonSerializer>();
endpointConfiguration.UsePersistence<InMemoryPersistence>();
endpointConfiguration.SendFailedMessagesTo("error");
endpointConfiguration.EnableInstallers();
return endpointConfiguration;
}
The result
I get the following error on the UseContainer line:
Unable to set the value for key:
NServiceBus.AutofacBuilder+LifetimeScopeHolder. The settings has been
locked for modifications. Move any configuration code earlier in the
configuration pipeline
What I think this means
I think I need to do all Autofac registrations for the NServicebus when creating the endpointConfiguration. The above manipulates the builder instance after that.
But
I can't do the above, because I need to register the endpointinstance to the IoC, because I need that in my controller to send messages. And that doesn't exist yet, because I need the endpointConfiguration first, for that.
So I have a chicken and egg situation ...
Question
Do I understand the issue correctly and how can I solve it while
making sure that IoC works correctly for the Controller?
I.e.: this.EndpointInstance has been correctly instantiated through IoC.
Instead of registering the actual instance, you could register it with a lambda expression that is going to be executed the first time the container will be asked to resolve IEndpointInstance.
builder
.Register(x =>
{
var endpointConfiguration = GetEndpointConfiguration("My.Service").GetAwaiter().GetResult();
var endpointInstance = Endpoint.Start(endpointConfiguration).GetAwaiter().GetResult();
return endpointInstance
})
.As<IEndpointInstance>()
.SingleInstance();
Related
I have created a .net standard class library service for accessing a sqlite database in my new Maui app. My question is where to call the initialisation code. I've added the DI registration in MauiProgram.cs which registers my DbService as the implementation of IDbService interface:
builder
.Services
.AddSingleton<IDbService>(serviceProvider =>
ActivatorUtilities.CreateInstance<DbService>(serviceProvider, databasePath))
.AddSingleton<MainViewModel>()
.AddSingleton<MainPage>();
The code to initialise the database (create tables, load test data) I've currently put in the constructor for the main page viewmodel which is registered as a singleton so the initialisation will only occur once. But obviously calling async initialisation code in the constructor is just wrong. Where is the correct location for this?
Task.Run(async () =>
{
await _dbService.Initialise();
if (!(await _dbService.GetExperiences(1, 0)).Any())
await _dbService.LoadTestData();
await GetData();
}).GetAwaiter().GetResult();
For custom startup logic, usually hosted services are the way to go. But MAUI does not currently support hosted services. However, there is an undocumented IMauiInitializeService interface that can be used to implement initialization logic.
internal class DatabaseInitializer : IMauiInitializeService
{
public void Initialize(IServiceProvider services)
{
var dbService = services.GetRequiredService<IDbService>();
Task.Run(async () =>
{
await dbService.Initialise();
if (!(await dbService.GetExperiences(1, 0)).Any())
await dbService.LoadTestData();
await GetData();
}).GetAwaiter().GetResult();
}
}
This class needs to be registered as an implementation of IMauiInitiailizeService:
builder.Services;
.AddSingleton<IDbService>(serviceProvider =>
ActivatorUtilities.CreateInstance<DbService>(serviceProvider, databasePath))
.AddSingleton<MainViewModel>()
.AddSingleton<MainPage>()
.AddTransient<IMauiInitializeService, DatabaseInitializer>();
It will be executed after the application is built, here.
It should work by the looks of things. Currently, I don't have MAUI installed so I can't verify for sure. Please let me know if there is a problem.
I'm a MAUI beginner so apologies if this is not best practice/helpful.
My MAUI app is set up with a local SQLite DB using EF Core code first migrations.
In App.xaml.cs, I'm using the service provider to create a new scope that gets the DB context. The DB context then applies migrations and adds seed/test data etc.
The following seems to work for me, perhaps you could do something similar using your dbService in place of the ctx...
public partial class App : Application
{
private readonly IServiceProvider _serviceProvider;
public App(IServiceProvider serviceProvider)
{
InitializeComponent();
_serviceProvider = serviceProvider;
AddTestData().Wait();
MainPage = new AppShell();
}
public async Task AddTestData()
{
using(var scope = _serviceProvider.CreateScope())
{
await using var ctx = scope.ServiceProvider.GetRequiredService<MyDbContext>();
await ctx.Database.MigrateAsync();
// Use ctx to add test/seed data etc
await ctx.SaveChangesAsync();
}
}
}
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
var configurationBuilder = new ConfigurationBuilder()
.AddJsonFile($"appsettings.json", true, true);
IConfiguration configuration = configurationBuilder.Build();
var sqlServerConnectionString = configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<AppDbContext>(x => x.UseSqlServer(sqlServerConnectionString));
#if DEBUG
builder.Services.AddBlazorWebViewDeveloperTools();
#endif
builder.Services.AddSingleton<WeatherForecastService>();
// Update Databases when app started
using (ServiceProvider serviceProvider = builder.Services.BuildServiceProvider())
{
AppDbContext dbContext = serviceProvider.GetRequiredService<AppDbContext>();
dbContext.Database.Migrate();
}
return builder.Build();
}
}
We all know you can register multiple instances and resolve them all with Autofac
builder.RegisterType<Foo1>().As<IFoo>();
builder.RegisterInstance(new Foo2("something")).As<IFoo>();
builder.RegisterInstance(new Foo2("another")).As<IFoo>();
//and finally
var foos = ctx.Resolve<IEnumerable<IFoo>>();
Now I had to register some foos as follow:
builder.Register(ctx =>{
var factory = ctx.Resolve<IFooFactory>();
var allTheFoos = config.FooConfigs.Select(fooConfig => factory.CreateFoo(fooConfig));
return allTheFoos;
}).As<IEnumerable<IFoo>>();
This still works, but I think it is not the correct way.
In addition to this, I still want to add an additional registration of IFoo
builder.RegisterInstance(new Foo2("this one")).As<IFoo>();
If I now resolve the IEnumerable, I get the collection explicitly registered as IEnumerable, but I need it to also contain the Foo2("this one");
//contains allTheFoos, but not Foo2("this one");
var foos = ctx.Resolve<IEnumerable<IFoo>>();
The registrations itself can not be merged since they are in different modules for a reason. I think I need to register the foos created with the factory individually, but this need to be inside the Register method (or equivalent) since I needs to be able to first resolve the factory itself. I am hoping on something like:
builder.Register(ctx => /* generate foos*/).AsMultiple<IFoo>();
EDIT: Based upon the answer
public class BotRegistrationSource : IRegistrationSource
{
private readonly IConfiguration _config;
public BotRegistrationSource(IConfiguration config)
{
_config = config;
}
public IEnumerable<IComponentRegistration> RegistrationsFor(Service service,
Func<Service, IEnumerable<ServiceRegistration>> registrationAccessor)
{
var swt = service as IServiceWithType;
if (!(swt?.ServiceType.IsAssignableTo<IBot>() ?? false))
{
return Enumerable.Empty<IComponentRegistration>();
}
return _config.GetSection("Bots")
.GetChildren()
.Select(c =>
new ComponentRegistration(
Guid.NewGuid(),
new DelegateActivator(swt.ServiceType, (componentContext, _) =>
{
var botFactory = componentContext.Resolve<IBotFactory>();
var config = botFactory.CreateConfig();
c.Bind(config);
var bot = botFactory.Create(config);
bot.Enable();
return bot;
}),
new CurrentScopeLifetime(),
InstanceSharing.None,
InstanceOwnership.OwnedByLifetimeScope,
new[] { service },
new Dictionary<string, object>()));
}
public bool IsAdapterForIndividualComponents => false;
}
I think what you're going to be looking at is a registration source. Registration sources are a way to do exactly what you want - provide one or more components to the container when a service is requested. The documentation has an example.
I am using MassTransit to publish\subscribe messages using RabbitMQ. I want to inject dependencies in dependency in consumers so that consumers can insert data to the database. However, I found the examples in the documentation confusing.
public class MessageConsumer : IConsumer<Message>
{
private IDao dao;
public MessageConsumer(IDao dao)
{
this.dao = dao;
}
public async Task Consume(ConsumeContext<Message> context)
{
Console.WriteLine("Order Submitted: {0}", context.Message.MessageId);
}
}
The bus is configured as follows
static void Main(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<ConcreteDao>().As<IDao>();
builder.RegisterType<MessageConsumer>().As<IConsumer<Message>>();
builder.AddMassTransit(x => {
// add the bus to the container
x.UsingRabbitMq((context, cfg) => {
cfg.Host("localhost");
cfg.ReceiveEndpoint("MessageQueueName", ec => {
// Configure a single consumer
ec.ConfigureConsumer<MessageConsumer>(context);
});
// or, configure the endpoints by convention
cfg.ConfigureEndpoints(context);
});
});
var container = builder.Build();
var bc = container.Resolve<IBusControl>();
bc.Start();
}
However I am getting an exception when the IBusControl is resolved.
System.ArgumentException: 'The consumer type was not found: StationDashboard.Messaging.Consumer.OperationModeChangedConsumer (Parameter 'T')'
What is wrong with the code above? What is the best way to inject dependency to a consumer? It would help if there was a complete working sample.
You need to register the consumer as explained in the documentation.
Do NOT do this:
builder.RegisterType<MessageConsumer>().As<IConsumer<Message>>();
Instead, as shown in the documentation, do this:
x.AddConsumer<MessageConsumer>();
Your dependency can be registered as you've done it.
I have an API (eg: ItemController.cs) which would obtain the Authorization Token from the Request Header at run time. With the Token, then only I pass into my Service Class (eg: ServiceItem.cs).
Here's how I did.
At the Startup.cs, I register my ServiceItem
var builder = new ContainerBuilder();
builder.RegisterType<ServiceItem>();
container = builder.Build(); //Note that, my container is a static variable
In my API, I resolve it in this way:
[Authorize]
[Route("GetData")]
[HttpGet]
public IHttpActionResult GetData([FromUri] Filter filter)
{
using (var scope = Startup.container.BeginLifetimeScope())
{
var serviceItem = Startup.container.Resolve<ServiceItem>(
new NamedParameter("token", Request.GetHeader("Authorization"))
);
return Ok(serviceItem.getItem(filter)); //filter is a param from webAPI
}
}
Question:
Is this how the Autofac normally work in web API? First, i am using a global static IContainer. Second, the codes look repetitive if i expose a few more functions.
I was thinking to resolve the ServiceItem in the constructor of the API. But the authorization token is not available yet.
Any suggestion is appreciated.
P.S.:
Here's my ServiceItem which, in the constructor, has a param 'token'
public class ServiceItem
{
public string token;
public ServiceItem(string token)
{
this.token = token;
}
public void doSomething()
{
//based on token, do processing
}
}
It is a bad idea to refer to a static container within your startup class. That way, you introduce tight coupling between the controller and the startup. Your controller dependencies should be satisfied by constructor parameters. Take at http://docs.autofac.org/en/v4.0.0/integration/aspnetcore.html
The Startup.ConfigureServices method can optionally return a IServiceProvider instance, which allows you to plug-in Autofac into the ASP.NET Core Dependency Injection framework:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
var builder = new ContainerBuilder();
builder.RegisterType<MyType>().As<IMyType>();
builder.Populate(services);
this.ApplicationContainer = builder.Build();
return new AutofacServiceProvider(this.ApplicationContainer);
}
After initializing your container, constructor parameters will be automatically resolved by Autofac:
public class MyController
{
private readonly IMyType theType;
public MyController(IMyType theType)
{
this.theType = theType;
}
....
}
I have a self hosted Owin application that uses Nancy. In one of the NancyModules I need to get an instance of IOwinContext.
This question touches on the subject, but there's no solution in it: Get current owin context in self host mode
It says that for Nancy, you have to use NancyContext to get to the Items dictionary and look for the value corresponding to the key "OWIN_REQUEST_ENVIRONMENT".
I do have access to the NancyContext and I can see the Items dictionary and that it contains a key called "OWIN_REQUEST_ENVIRONMENT". (I could also call the NancyContext.GetOwinEnvironment() extension, which gives the same result
However, when I get that key it doesn't contain an actual IOwinContext.
It contains a lot of keys with information about Owin (some of the keys are owin.RequestPath, owin.RequestMethod, owin.CallCancelled, and more), but not an actual context object. And it is only really a dictionary with various keys, so I can't cast it to an IOwinContext either.
How can I get from a NancyContext to an IOwinContext object?
public class MyStartup
{
public void Start()
{
var options = new StartOptions()
options.Urls.Add(new Uri("http://*:8084"));
options.AppStartup(this.GetType().AssemblyQualifiedName;
var host = WebApp.Start(options, Configuration);
}
public void Configuration(IAppBuilder app)
{
app.UseNancy();
}
}
public class MyModule : NancyModule
{
Get["/", true] = async(x, ct) =>
{
var owinEnvironment = Context.GetOwinEnvironment();
// Now what?
}
}
var owinContext = new OwinContext(Context.GetOwinEnvironment());
example:
public class SecurityApi : NancyModule
{
public SecurityApi()
{
Post["api/admin/register", true] = async (_, ct) =>
{
var body = this.Bind<RegisterUserBody>();
var owinContext = new OwinContext(Context.GetOwinEnvironment());
var userManager = owinContext.GetUserManager<ApplicationUserManager>();
var user = new User {Id = Guid.NewGuid().ToString(), UserName = body.UserName, Email = body.Email};
var result = await userManager.CreateAsync(user, body.Password);
if (!result.Succeeded)
{
return this.BadRequest(string.Join(Environment.NewLine, result.Errors));
}
return HttpStatusCode.OK;
};
}
}
Actually, question that you mentioned has some tips that you probably missed.
For Nancy, you have to use NancyContext to get to the Items dictionary
and look for the value corresponding to the key
"OWIN_REQUEST_ENVIRONMENT". For SignalR, Environment property of
IRequest gives you access to OWIN environment. Once you have the OWIN
environment, you can create a new OwinContext using the environment.
So, once you called var owinEnvironment = Context.GetOwinEnvironment() and got the dictionary then you can create OwinContext (which is just wrapper for these dictionary values)
It has a constructor OwinContext(IDictionary<String, Object>) which, i guess, is what you need.
Also, you can get OwinContext from HttpContext:
// get owin context
var owinContext = HttpContext.Current.GetOwinContext();
// get user manager
var userManager = owinContext.GetUserManager<YourUserManager>();
I ended up solving this by creating new Owin middleware. In the middleware you have access to the current Owin context, which gives you access to the Owin environment.
When you have access to the Owin environment it's simply a case of adding the Owin context to the environment. When the context is in the environment you can retrieve it in the NancyModule.
After retrieving it like this I also had access to the GetUserManager() method on the context so that I could get my AspNetIdentity manager (as I mentioned in a comment to another answer). Just remember that the middleware must be added before Nancy to the Owin pipeline.
Startup
public class Startup
{
public void Start()
{
var options = new StartOptions()
options.Urls.Add(new Uri("http://*:8084"));
options.AppStartup(this.GetType().AssemblyQualifiedName;
var host = WebApp.Start(options, Configuration);
}
public void Configuration(IAppBuilder app)
{
app.Use(typeof(OwinContextMiddleware));
app.UseNancy();
}
}
Middleware
public class OwinContextMiddleware : OwinMiddleware
{
public OwinContextMiddleware(OwinMiddleware next)
: base(next)
{
}
public async override Task Invoke(IOwinContext context)
{
context.Environment.Add("Context", context);
await Next.Invoke(context);
}
}
NancyModule
public class MyModule : NancyModule
{
public MyModule()
{
Get["/", true] = async(x, ct) =>
{
IDictionary<string, object> environment = Context.GetOwinEnvironment();
IOwinContext context = (IOwinContext)environment["Context"]; // The same "Context" as added in the Middleware
}
}
Caveat
The middleware listed above is untested as the middleware I have is more complex and I haven't had the time to create a working example. I found a simple overview on how to create Owin middleware on this page.