Injecting objects into a MassTransit Saga using Autofac - c#

I'm trying to get autofac to inject an factory into a saga on creation, and I can't get it working. I've had no problem injecting the factory into a consumer so I know it's registered correctly, so I'm assuming I'm not registering the sagas correctly and autofac isn't building them up.
Here's my registration code:
var mapTypes = assembly.GetTypes()
.Where(type => type.Implements(typeof(SagaClassMapping<>)));
builder.Register(context => GetSessionFactory(mapTypes)).AsSelf().SingleInstance();
// register all sagas and consumers
builder.RegisterAssemblyTypes(assembly)
.Where(type => type.IsAssignableTo<ISaga>() || type.IsAssignableTo<IConsumer>())
.AsSelf();
builder
.RegisterGeneric(typeof(NHibernateSagaRepository<>))
.As(typeof(ISagaRepository<>))
.SingleInstance();
builder
.Register(context => ServiceBusFactory.New(sbc =>
{
sbc.UseLog4Net();
var queueUri = new Uri(ConfigurationManager.AppSettings["ReceiveQueue"]);
var scope = context.Resolve<ILifetimeScope>();
sbc.UseRabbitMq(transportConfig =>
transportConfig.ConfigureHost(queueUri, hostConfig =>
{
hostConfig.SetUsername(ConfigurationManager.AppSettings["busUser"]);
hostConfig.SetPassword(ConfigurationManager.AppSettings["busPassword"]);
}));
sbc.ReceiveFrom(queueUri);
sbc.Subscribe(cfg => cfg.LoadFrom(scope));
}))
.SingleInstance();
The saga itself is pretty standard
public class MySaga : SagaStateMachine<MySaga>, ISaga
{
public Guid CorrelationId { get; private set; }
public Func<MyObject> ObjectBuilder { get; set; }
public MySaga() { }
public MySaga(Guid correlationId)
{
CorrelationId = correlationId;
}
Static MySaga()
{
Define(() =>
{ .... }
}
I've tried adding the Func<MyObject> to a constructor, but it's not hit, it does work in a consumer so I know Autofac can build a Func<MyObject>. I also tried using property injection with no luck:
builder.RegisterAssemblyTypes(assembly)
.Where(type => type.IsAssignableTo<ISaga>() || type.IsAssignableTo<IConsumer>())
.PropertiesAutowired()
.AsSelf();
and
builder.RegisterType<MySaga>()
.OnActivated(arg => arg.Instance.MyBuilder =
arg.Context.Resolve<Func<MyObject>>())
.AsSelf();
Any help on what I'm doing wrong would be appreciated.
I got a reply from Chris Patterson on the masstransit-discuss group that pointed out I was probably doing it wrong.
Automatonymous is a better choice if you have dependencies, since the state machine and the state itself are separate classes.
Injecting dependencies into a class hydrated via NHibernate is never going to end well. There are a couple of helper classes that can be used to perform property-injection into the saga after it is loaded from NHibernate, the decorating saga repository I think has been posted here.
Here is the example of the injecting repository for Magnum:
https://github.com/MassTransit/MassTransit/blob/master/src/MassTransit.Tests/Saga/Injecting_Specs.cs
Given it's NHibernate that's hydrating the object I should be looking there for the hook. I've got a workaround for my current issue, but I'll post an answer here if/when I find one.

old question but it took a while for us to figure out a suitable way to wiring up sagas and autofac and then adding nhibernate on top of that.
First create a wiring class (property injection)
public class MySagaRepository<TSaga> : ISagaRepository<TSaga> where TSaga : class, ISaga
{
private ISagaRepository<TSaga> _inner;
private readonly IComponentContext _context;
public MySagaRepository(ISagaRepository<TSaga> inner, IComponentContext context)
{
_inner = inner;
_context = context;
}
public IEnumerable<Action<IConsumeContext<TMessage>>> GetSaga<TMessage>(IConsumeContext<TMessage> context, Guid sagaId, InstanceHandlerSelector<TSaga, TMessage> selector, ISagaPolicy<TSaga, TMessage> policy) where TMessage : class
{
return _inner.GetSaga(context, sagaId, (saga, message) =>
{
_context.InjectProperties(saga);
return selector(saga, message);
}, policy);
}
public IEnumerable<Guid> Find(ISagaFilter<TSaga> filter)
{
return _inner.Find(filter);
}
public IEnumerable<TSaga> Where(ISagaFilter<TSaga> filter)
{
return _inner.Where(filter).Select(x =>
{
_context.InjectProperties<TSaga>(x);
return x;
});
}
public IEnumerable<TResult> Where<TResult>(ISagaFilter<TSaga> filter, Func<TSaga, TResult> transformer)
{
return _inner.Where(filter, x =>
{
_context.InjectProperties(x);
return transformer(x);
});
}
public IEnumerable<TResult> Select<TResult>(Func<TSaga, TResult> transformer)
{
return _inner.Select(x =>
{
_context.InjectProperties(x);
return transformer(x);
});
}
}
Then wire it
builder.RegisterGeneric(typeof(NHibernateSagaRepository<>)).Named("innerRepo", typeof(ISagaRepository<>));
builder.RegisterGenericDecorator(typeof(MySagaRepository<>), typeof(ISagaRepository<>), fromKey: "innerRepo");
For the persistance it was just a matter of
public class SagaPersistenceHandler
{
public static ISessionFactory CreateSessionFactory()
{
var provider = new SqlServerSessionFactoryProvider(ConfigurationManager.ConnectionStrings["myConnectionString"].ConnectionString, new[]
{
typeof (MySagaMap)
});
return provider.GetSessionFactory();
}
}
Now wire that
builder.Register(c => SagaPersistenceHandler.CreateSessionFactory()).As<ISessionFactory>();
and the saga mapping to the saga (not included)
public MySagaMap()
{
SchemaAction(NHibernate.Mapping.ByCode.SchemaAction.None);
Table("dbo.[tTable]");
Property(x => x.Id);
Property(x => x.CloseDate);
}
}
All thats left is to register your saga
builder.RegisterType<MySaga>().AsSelf();
worked well (credits goes to #erikkallen)

I made an adapter for Autofac to register and configure MT state machine sagas.
The repo is here.
It is available on nuget.org.
You can register your consumers and state machines by calling:
builder.RegisterSagaStateMachines(sagasAssembly); // register all state machines
builder.RegisterConsumers(consumersAssembly); // register consumers (standard MassTransit Autofac integration)
Note that RegisterConsumers is the standard MassTransit.Autofac extension.
In your endpoint configuration you need to call the configuration like this:
x.ReceiveEndpoint(queueName, c =>
{
c.LoadConsumers(container);
c.LoadStateMachineSagas(container);
});
Pre-condition is that you have to register ISagaRepository implementations as well.

Related

Using Autofac Registration middleware to resolve a version of the service

I want to resolve the Named registration where the name is given at runtime.
Background story - I am exposing my endpoints with multiple versions, e.g.:
https://localhost/myservice/api/v1/allcities
https://localhost/myservice/api/v2/allcities
...
The controller needs to invoke a different version of the injected service based on the version of the invoked endpoint.
To illustrate, I would expect when I invoke https://localhost/myservice/api/v1/blahblah it executes using MyServiceV1 and when I invoke https://localhost/myservice/api/v2/blahblah it executes using MyServiceV2
I'm using .NET Core 3.1 API with Microsoft.AspNetCore.Mvc.Versioning, Autofac 6.
I can get what version was invoked via IHttpContextAccessor as contextAccessor.HttpContext.GetRequestedApiVersion().
In this question I want to focus on resolving a specific version of the service at runtime.
My idea was to register the service as Named (or Keyed, doesn't matter) and using the Registration middleware registration intercept the resolution process and inject the proper version of the Named service.
Code:
public interface IMyService
{
string GetImplementationVersion();
}
[MyServiceVersioning("1.0")]
public class MyService1 : IMyService
{
public string GetImplementationVersion() => "Example 1.0";
}
[MyServiceVersioning("2.0")]
public class MyService2 : IMyService
{
public string GetImplementationVersion() => "Example 2.0";
}
public class MyMasterService
{
private IMyService _myService;
public MyMasterService(IMyService myService)
{
_myService = myService;
}
public string GetInjectedVersion() => _myService.GetImplementationVersion();
}
The registrations (Edited for completeness)
// ...
builder.RegisterType<VersionService>().As<IVersionService>();
// next two lines registered using extension that's in the next code block example
builder.RegisterVersioned<MyServiceV1, IMyService>();
builder.RegisterVersioned<MyServiceV2, IMyService>();
builder.Register<MyMasterService>();
And finally the implementation of RegisterVersioned extension where the question lies:
public static class AutofacVersioningExtensions
{
public static IRegistrationBuilder<T, ConcreteReflectionActivatorData, SingleRegistrationStyle>
RegisterVersioned<T, TInterface>(this ContainerBuilder builder) where T: class
{
var versioningAttribute = typeof(T).GetCustomAttribute<MyServiceVersionAttribute>();
if (versioningAttribute == null)
{
// no versioning exists, do it simply
return builder.RegisterType<T>().As<TInterface>();
}
return builder.RegisterType<T>().As<TInterface>().Named<TInterface>(versioningAttribute.Version).ConfigurePipeline(p =>
{
p.Use(PipelinePhase.RegistrationPipelineStart, (context, next) =>
{
var invokedVersion = context.Resolve<IVersionService>().CurrentVersion;
// HERE --> in the next line issue is that we have obvious circular resolution
// + it only resolves the last registration
// (I could do Resolve<IEnumerable<TInterface>> to get all registrations but that's an overhead that I'd like to avoid if possible).
var resolved = context.ResolveNamed<TInterface>(invokedVersion);
if (resolved != null)
{
context.Instance = resolved;
}
else
{
next(context);
}
});
});
}
}
Do you have any ideas? Is my approach even on the right path?
It seems like you could make this easier by using a lambda registration.
builder.RegisterType<MyServiceV1>().Keyed<IMyService>("1.0");
builder.RegisterType<MyServiceV2>().Keyed<IMyService>("2.0");
builder.Register<MyMasterService>();
builder.Register(ctx =>
{
var version = ctx.Resolve<IHttpContextAccessor>().HttpContext.GetRequestedApiVersion();
return ctx.ResolveKeyed<IMyService>(version);
}).As<IMyService>().InstancePerLifetimeScope();
This way, when you resolve an un-keyed IMyService, it'll go through the detection process and return the right keyed version. That keyed instance will be cached for the duration of the request.

How can I pass parameters to an AutoMapper Profile in ABP?

I need to customize the way MyAutoMapper profile maps my objects to DTOs. From one of my ApplicationServices, I use an ObjectMapper for a relatively simple mapping. The catch is that ABP's AutoMapper isn't the normal AutoMapper that everyone knows about.
Below is a snippet of what it would ideally look like; Except opt.MapFrom(m => Localizer[m.Type.ToString()]) and _objectMapper.Map<Preparation, DtoPreparation>(preparation, _localizer) cannot work that way.
public class MyAutoMapperProfile : Profile
{
public MyAutoMapperProfile()
{
CreateMap<Preparation, DtoPreparation>()
.ForMember(m => m.PreparatorType, opt => opt.MapFrom(m => m.Type))
.ForMember(m => m.PreparatorTypeString, opt => opt.MapFrom(m => Localizer[m.Type.ToString()]));
}
}
public class SlipsAppService : TaxesAppService
{
private readonly IObjectMapper<TaxesApplicationModule> _objectMapper;
private readonly ISlipsManager _slipsManager;
private readonly IStringLocalizer<TaxesResource> _localizer;
public SlipsAppService(ISlipsManager iSlipsManager, IObjectMapper<TaxesApplicationModule> objectMapper, IStringLocalizer<TaxesResource> localizer)
{
_objectMapper = objectMapper;
_slipsManager = iSlipsManager;
_localizer = localizer;
}
[Microsoft.AspNetCore.Mvc.HttpPost("/api/slips/get-or-create-preparation")]
public async Task<DtoPreparation> GetOrCreateCurrentPreparation(BaseGetterInput input)
{
var preparation = await _slipsManager.GetOrCreatePreparation(input.Id);
return _objectMapper.Map<Preparation, DtoPreparation>(preparation, _localizer);
}
}
I can't find a way to pass any information from my ApplicationService to the AutoMapper Profile, as IObjectMapper.Map<>() has no parameters for additional options or objects, unlike the normal AutoMapper.
Maybe there is a way to register the Profile in dependency injection, but with my limited knowledge of the framework, I couldn't find a clue...
For now, my problem is only with Localization, but really it can apply to anything. Since my DTOs contain other nested DTOs, managing extra stuff outside of the AutoMapper isn't an option, unless I change the structure of my application just for a workaround.
Since you are using one mapping profile per appservice, here is a good suggestion that works for me:
Create a class the implements the IMappingAction interface.
In the implementation of the Process method, inject your ILocalizer and use it with the source and destination.
In your mapping, instead of passing the localizer, chain call with AfterMap.
Here is an example:
public class MyAutoMapperProfile : Profile
{
public MyAutoMapperProfile()
{
CreateMap<Preparation, DtoPreparation>()
.ForMember(m => m.PreparatorType, opt => opt.MapFrom(m => m.Type))
.AfterMap<PreparationDtoLocalizerAction>;
}
}
public class PreparationDtoLocalizerAction : IMappingAction<Preparation, DtoPreparation>
{
private readonly IStringLocalizer<TaxesResource> _localizer;
public PreparationDtoLocalizerAction(IStringLocalizer<TaxesResource> localizer)
{
_localizer = localizer;
}
public void Process(Preparation source, DtoPreparation destination)
{
destination.PreparatorTypeString = _localizer[source.Type.ToString()]
}
}
public class SlipsAppService : TaxesAppService
{
private readonly IObjectMapper<TaxesApplicationModule> _objectMapper;
private readonly ISlipsManager _slipsManager;
public SlipsAppService(ISlipsManager iSlipsManager, IObjectMapper<TaxesApplicationModule> objectMapper)
{
_objectMapper = objectMapper;
_slipsManager = iSlipsManager;
}
[Microsoft.AspNetCore.Mvc.HttpPost("/api/slips/get-or-create-preparation")]
public async Task<DtoPreparation> GetOrCreateCurrentPreparation(BaseGetterInput input)
{
var preparation = await _slipsManager.GetOrCreatePreparation(input.Id);
return _objectMapper.Map<Preparation, DtoPreparation>(preparation);
}
}

How to use MassTransit test harness to test sagas with Activities (Automatonymous.Binders.EventActivityBinder) with constructor dependency injection?

To keep saga code simple I created activities that are executed when a saga event is triggered. Now I'd like to create tests for the saga using MT's test harness. I also need to mock dependencies from these activities. How can this be done in MT 7.0.2?
This question is similar to the one found here:
How to use MassTransit test harness to test Consumer with constructor dependency injection?
Below is some pseudo code to exemplify my use case:
public class SomeSaga : MassTransitStateMachine<SomeSagaState>
{
public Event<InitializeCommand> Initialize { get; set; }
public State Initialized { get; set; }
public SomeSaga()
{
InstanceState(x => x.CurrentState);
Event(() => Initialize, e => { e.CorrelateById(c => c.Message.CorrelationId); });
Initially(
When(Initialize)
.Activity(x => x.OfType<InitializeActivity>())
.TransitionTo(Initialized));
}
}
public class InitializeActivity : Activity<SomeSagaState, InitializeCommand>
{
private readonly ISomeDependency _dep;
public InitializeActivity(ISomeDependency dep)
{
_dep = dep ?? throw new ArgumentNullException(nameof(dep));
}
public async Task Execute(...)
{
// do something
}
}
You need at least v7.0.4 of MassTransit, though I'd suggest using the latest version. Container support for the InMemoryTestHarness was added in that version.
There are also extensive examples of how to use it in Sample-Library.

DbContext is Disposed When Using Autofac Dependency Injection on WebApi project

I have a WebApi project using Entity Framework 6.0, Autfac for DI and CQRS architecture. The problem I have that DbContext isn't disposing how it supposed to. The action I take:
I run two quick requests, e.g. send request from Postman to one endpoint, runtime stops on breakpoint in controller method, I send second request to another endpoint in different controller.
Resume Runtime
if the second request finished before the first one is done, the first one throws and error that dbcontext was disposed and it cannot run whatever it was supposed to do
Originally problem appeared when I posted and patched from frontend one after another.
It seems like lifetime scope is not really per-request. It seems like all dbcontexts are disposed on one of the request's end. The other one does not have anything to work with.
How is it configured?
Starting from the highest layer - controller:
public class UsersController : BaseController, IUsersApi
{
private readonly IUserService _userService;
public UsersController(IUserService userService, ILogging logging) : base(logging)
{
_userService = userService;
}
[HttpGet]
[Route("api/users")]
public IList<UserDto> GetUsers()
{
try
{
return _userService.GetAllUsers();
}
catch (Exception e)
{
_logger.Error(e);
_logger.Trace(e);
throw;
}
}
[HttpPatch]
[Route("api/users/")]
public IHttpActionResult EditUsers(ICollection<UserEditDto> model)
{
try
{
_userService.EditUsers(model);
return Ok();
}
catch (Exception e)
{
_logger.Error(e);
_logger.Trace(e);
return BadRequest("Error");
}
}
}
Service layer:
public class UserService : IUserService
{
private readonly IServiceTools _serviceTools;
private readonly IUserQuerier _userQuerier;
public UserService(IServiceTools serviceTools, IUserQuerier userQuerier)
{
_serviceTools = serviceTools;
_userQuerier = userQuerier;
}
public void EditUsers(ICollection<UserEditDto> model)
{
var mapper = _serviceTools.AutoMapperConfiguration.Configure().CreateMapper();
var userEditCommands = mapper.Map<ICollection<UserEditDto>, ICollection<EditUserCommand>>(model);
foreach (var command in userSaveCommands)
{
_serviceTools.CommandBus.SendCommand(command);
CacheHelper.Clear(command.Id.ToString());
}
}
public IList<UserDto> GetAllUsers()
{
var allUsers = _userQuerier.GetAllUsers();
var result = allUsers.Select(x => new UserDto()
{
...
}).ToList();
return result;
}
}
Service Tools interface where command bus sits:
public interface IServiceTools
{
ICommandBus CommandBus { get; }
IAutoMapperConfiguration AutoMapperConfiguration { get; }
IIdentityProvider IdentityProvider { get; }
}
public class ServiceTools : IServiceTools
{
public ServiceTools(ICommandBus commandBus, IAutoMapperConfiguration autoMapperConfiguration, IIdentityProvider identityProvider)
{
CommandBus = commandBus;
AutoMapperConfiguration = autoMapperConfiguration;
IdentityProvider = identityProvider;
}
public ICommandBus CommandBus { get; }
public IAutoMapperConfiguration AutoMapperConfiguration { get; }
public IIdentityProvider IdentityProvider { get; }
}
And whatever handler for command:
public class EditUserHandler : IHandleCommand<EditUserCommand>
{
private readonly ICommandsContext _commandsContext;
public SaveUserHandler(ICommandsContext commandsContext)
{
_commandsContext = commandsContext;
}
public void Handle(EditUserCommand command)
{
... using dbcontext here...
}
}
}
For DI I use Autofac, all resources are set to per-request lifetime, split into modules, e.g. module for data access
public class DataModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<AppNameDbContext>().As<ICommandsContext>().InstancePerRequest();
builder.RegisterType<AppNameDbContext>().As<IQueryContext>().InstancePerRequest();
base.Load(builder);
}
}
The difference between both interfaces is that IQueryContext cannot change entity states and use SaveChagnes() method. IQueryContext have all DbSets in it, while ICommandsContext inherits from it and adds SettingState methods (added, modified, deleted) and SaveChanges() method.
IQueryContext is injected into queries and ICommandsContext into commands as seend in example aboove.
Now the Autofac config for command bus looks like that:
public class InfrastractureModule : Module
{
private ICommandsContext _commandsContext;
private ITranslationsCommandsContext _translationsCommandsContext;
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<AutoMapperConfiguration>().
As<IAutoMapperConfiguration>().InstancePerRequest();
builder.RegisterType<ServiceTools>().As<IServiceTools>().InstancePerRequest();
builder.Register(c =>
{
_commandsContext = c.Resolve<ICommandsContext>();
_translationsCommandsContext = c.Resolve<ITranslationsCommandsContext>();
return new CommandBus(CreateHandlersFactory);
})
.As<ICommandBus>().InstancePerRequest();
base.Load(builder);
}
private IHandleCommand CreateHandlersFactory(Type type)
{
if (type == typeof(XXXCommand))
{
return new XXXHandler(_commandsContext);
}
}
While the command bus looks like that
public class CommandBus : ICommandBus
{
private readonly Func<Type, IHandleCommand> _handlersFactory;
public CommandBus(Func<Type, IHandleCommand> handlersFactory)
{
_handlersFactory = handlersFactory;
}
public void SendCommand<T>(T command) where T : ICommand
{
var handler = (IHandleCommand<T>) _handlersFactory(typeof(T));
handler.Handle(command);
}
}
There is completely separate context used for translations for the app, but I do not thing that is important here.
I did not find any posts with similar problem. It only occurs when where two requests processed at the same time. I do not know if the configuration is wrong or Autofac messes things up, because it should not technically dispose dbcontext which was allocated for another request.
Sorry for the wall of text ;) I hope someone can help with that.
Obiously changing dbcontext's lifetime to SingleInstance fixed the problem, but we do not want that :)
SOLUTION EDIT:
As #ZeljkoVujaklija noticed CommandsDbContext declarations in InfrastractureModule seemed strange. I removed whole CommandBus registration from InfrastractureModule. Instead I created CommandsModule in the assembly where all the commands sit. It looks like that:
public class CommandsModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.RegisterAssemblyTypes(ThisAssembly)
.Where(x => x.IsAssignableTo<IHandleCommand>())
.AsImplementedInterfaces();
builder.Register<Func<Type, IHandleCommand>>(c =>
{
var ctx = c.Resolve<IComponentContext>();
return t =>
{
var handlerType = typeof(IHandleCommand<>).MakeGenericType(t);
return (IHandleCommand)ctx.Resolve(handlerType);
};
});
builder.RegisterType<CommandBus>()
.AsImplementedInterfaces();
}
}
Not only it fixes the problem but also gets rid of huge factory.
If you are running within ASP.NET Core you should run InstancePerLifetimeScope instead of InstancePerRequest
Use InstancePerLifetimeScope instead of InstancePerRequest. In previous ASP.NET integration you could register a dependency as InstancePerRequest which would ensure only one instance of the dependency would be created per HTTP request. This worked because Autofac was in charge of setting up the per-request lifetime scope. With the introduction of Microsoft.Extensions.DependencyInjection, the creation of per-request and other child lifetime scopes is now part of the conforming container provided by the framework, so all child lifetime scopes are treated equally - there’s no special “request level scope” anymore. Instead of registering your dependencies InstancePerRequest, use InstancePerLifetimeScope and you should get the same behavior. Note if you are creating your own lifetime scopes during web requests, you will get a new instance in these child scopes.
http://autofaccn.readthedocs.io/en/latest/integration/aspnetcore.html#differences-from-asp-net-classic

Using Autofac in integration tests with web api 2

I have been manually instantiating my services in my integration tests, but when I got to a serve that had Lazy dependencies, I did some research and found that you can actually use Autofac to resolve your services when doing your tests.
So, I wrote this class:
public class Container<TModule> where TModule: IModule, new()
{
private readonly IContainer _container;
protected Container()
{
var builder = new ContainerBuilder();
builder.RegisterModule(new TModule());
_container = builder.Build();
}
protected TEntity Resolve<TEntity>() => _container.Resolve<TEntity>();
protected void Dispose() => _container.Dispose();
}
And then in my context, I changed to this:
public class ProductContext : Container<AutofacModule>
{
public IProductProvider ProductProvider { get; }
public static ProductContext GiventServices() => new ProductContext();
protected ProductContext()
{
ProductProvider = Resolve<IProductProvider>();
}
public List<JObject> WhenListProducts(int categoryId) => ProductProvider.List(categoryId);
}
I have another context that seems to work (the tests pass) and that is using a MatchProvider. If I compare both in my Autofac module, they look like this:
builder.RegisterType<ProductProvider>().As<IProductProvider>().InstancePerRequest();
and
builder.RegisterType<MatchProvider>().As<IMatchProvider>().SingleInstance();
Because the MatchProvider is a singelton, it seems to have no issues being resolved, but the ProductProvider is an instance per request, this is where the issue seems to lie.
I get this error when running any tests that require that service:
No scope with a tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested.
I figured it was because I didn't have the right nuget packages installed. So I installed:
Autofac
Autofac.Integration.Owin
Autofac.Integration.WebApi
Autofac.Integration.WebApi.Owin
These are the same references that are used where my module is defined, but this did not help.
Does anyone know what I need to do to get this to work?
I couldn't find a suitable (easy) solution to this. I saw some people creating lifetime scopes themselves, which to me seemed like overkill and it wasn't "nice" code.
So, taking one of Autofac's principles: Any service that is registered multiple times; the last instance is the instance that is resolved.
So in my Container class, I just re-registered my InstancePerRequest services as InstancePerDependency instead. This solved my issue.
Here is my full code:
public class ContainerContext<TModule> where TModule: IModule, new()
{
private IContainer _container;
protected ContainerContext()
{
var builder = new ContainerBuilder();
builder.RegisterModule(new TModule());
// Because Autofac will resolve services using the last registration, we can change all our web api
// services to by InstancePerDependency instead of InstancePerRequest which is obviously better
// when testing.
builder.RegisterType<AnswerProvider>().As<IAnswerProvider>().InstancePerDependency();
builder.RegisterType<AttributeProvider>().As<IAttributeProvider>().InstancePerDependency();
builder.RegisterType<CategoryProvider>().As<ICategoryProvider>().InstancePerDependency();
builder.RegisterType<ClaimProvider>().As<IClaimProvider>().InstancePerDependency();
builder.RegisterType<ClientProvider>().As<IClientProvider>().InstancePerDependency();
builder.RegisterType<CriteriaProvider>().As<ICriteriaProvider>().InstancePerDependency();
builder.RegisterType<FeedProvider>().As<IFeedProvider>().InstancePerDependency();
builder.RegisterType<FormulaProvider>().As<IFormulaProvider>().InstancePerDependency();
builder.RegisterType<ImageProvider>().As<IImageProvider>().InstancePerDependency();
builder.RegisterType<GroupProvider>().As<IGroupProvider>().InstancePerDependency();
builder.RegisterType<QuestionProvider>().As<IQuestionProvider>().InstancePerDependency();
builder.RegisterType<StripeProvider>().As<IStripeProvider>().InstancePerDependency();
builder.RegisterType<ApiAiProvider>().As<IApiAiProvider>().InstancePerDependency();
builder.RegisterType<PiiikProvider>().As<IPiiikProvider>().InstancePerDependency();
builder.RegisterType<ProductProvider>().As<IProductProvider>().InstancePerDependency();
builder.RegisterType<SettingProvider>().As<ISettingProvider>().InstancePerDependency();
builder.RegisterType<TrackingProvider>().As<ITrackingProvider>().InstancePerDependency();
_container = builder.Build();
}
protected TEntity Resolve<TEntity>() => _container.Resolve<TEntity>();
protected void Dispose() => _container.Dispose();
}
And then, any context I use inherits this class:
public class ProductContext : ContainerContext<AutofacModule>
{
public IProductProvider ProductProvider { get; }
public static ProductContext GiventServices() => new ProductContext();
protected ProductContext()
{
ProductProvider = Resolve<IProductProvider>();
}
public List<JObject> WhenListProducts(int categoryId) => ProductProvider.List(categoryId);
}
Which means, when testing, I can just do this:
[TestFixture]
public class ProductProviderTests
{
[Test]
public void ShouldHaveProducts()
{
var services = ProductContext.GiventServices();
var products = services.WhenListProducts(1);
products.Count.Should().NotBe(0);
}
[Test]
public void ShouldHaveDuplicateVariants()
{
var services = ProductContext.GiventServices();
var products = services.WhenListProducts(1);
var anyDuplicate = products.GroupBy(x => x.SelectToken("variant").ToString()).Any(g => g.Count() > 1);
anyDuplicate.Should().Be(true);
}
[Test]
public void ShouldNotHaveDuplicateGtins()
{
var services = ProductContext.GiventServices();
var products = services.WhenListProducts(1);
var anyDuplicate = products.GroupBy(x => x.SelectToken("gtin").ToString()).Any(g => g.Count() > 1);
anyDuplicate.Should().Be(false);
}
}
This should help anyone else having the same issue.

Categories