Conditional dependency injection via middleware in Blazor.Server - c#

Is it possible to have 2 different implementations of a service and inject a specific implementation into a component based on the props passed to it?
I have tried to find a solution and all I could find is pulling the desired service out of the services provider in the constructor which I don't want to do. I have done something similar in an API Controller which essentially would intercept the request before the service layer was injected and inject the correct service implementation depending on the request parameters.
Edit:
Essentially during startup I want to be able to inject 2 implementations of the same interface service.
builder.Services.AddScoped<IEmployeeService,EmployeeServiceA>();
builder.Services.AddScoped<IEmployeeService,EmployeeServiceB>();
Then in my component I would do something like:
#inject IEmployeeService _employeeService;
With the actual implementation being injected into the component being decided based off of parameters being sent into the component which would be intercepted by middle wear.

You need to manually get the correct service from the ServiceProvider by injecting the IServiceProvider and calling GetService on the provider.
Note: This is not recommended for normal component injection because you need to be careful about when you use the service: you may try and access it before you have initialized it. In the example below it's not available in OnInitialized.
Here's an example for a simple notification service.
An interface:
public interface INotificationService
{
public event EventHandler? Changed;
public void NotifyChanged(object? sender);
}
Two classes:
public class WeatherForecastNotificationService
: INotificationService
{
public event EventHandler? Changed;
public void NotifyChanged(object? sender)
=> this.Changed?.Invoke(sender, EventArgs.Empty);
}
public class WeatherReportNotificationService
: INotificationService
{
public event EventHandler? Changed;
public void NotifyChanged(object? sender)
=> this.Changed?.Invoke(sender, EventArgs.Empty);
}
Registered as (scoped) services:
builder.Services.AddScoped<WeatherForecastNotificationService>();
builder.Services.AddScoped<WeatherReportNotificationService>();
A component to get the services - ServiceTestComponent.razor:
#inject IServiceProvider serviceProvider
<div class="bg-info p-2 m-2">
<h3>ServiceTestComponent</h3>
<div class="p-3">
Type: #message
</div>
</div>
#code {
[Parameter] public object? Record { get; set; }
private INotificationService notificationService = default!;
private string message = string.Empty;
protected override void OnParametersSet()
{
if (this.Record is WeatherForecast)
{
var service = serviceProvider.GetService<WeatherForecastNotificationService>();
if (service is not null)
notificationService = service as INotificationService;
}
else
{
var service = serviceProvider.GetService<WeatherReportNotificationService>();
if (service is not null)
notificationService = service as INotificationService;
}
// simple bit of code to demo what actual service we have
if (notificationService is not null)
message = notificationService.GetType().Name;
}
}
And a test page:
#page "/"
#page "/Index"
<h1>Hello</h1>
<ServiceTestComponent Record=this.record />
<div>
<button class="btn btn-primary" #onclick=ChangeType>Change Type</button>
</div>
#code
{
private object record = new WeatherForecast();
private void ChangeType()
{
if (record is WeatherForecast)
record = new WeatherReport();
else
record = new WeatherForecast();
}
}

Related

Proper use of OnAfterRender?

I have a project in which I frequently use OnAfterRender() to call methods on child components. As a simple example, suppose I have two Blazor components, Counter.razor and CounterParent.razor:
Counter:
#page "/counter"
<p role="status">Current count: #currentCount</p>
<button class="btn btn-primary" #onclick="IncrementCount">Click me</button>
#code {
private int currentCount = 0;
// This method is private out of the box,
// but let's change it to public for this example
public void IncrementCount()
{
currentCount++;
StateHasChanged();
}
}
CounterParent:
#page "/counterParent"
<h3>Counter Parent</h3>
<Counter #ref="counterChild"></Counter>
#code {
private Counter counterChild;
private bool loaded;
protected override void OnAfterRender(bool firstRender)
{
if (!loaded && counterChild != null)
{
counterChild.IncrementCount(); // Do some operation on the child component
loaded = true;
}
}
}
The parent component in this example causes the child component (the counter) to be incremented by 1.
However, I have recently been advised that this is not a good practice, and that
On OnAfterRender{async} usage, it should (in general) only be used to do JS interop stuff.
So if this is poor practice what is the best practice for calling methods on child components when a page loads? Why might someone choose to
actually either disable it on ComponentBase based components, , or don't implement it on [their] own components?
What I tried:
Code like the above works. It is definitely slow and the user often sees an ugly mess as things load. I probably won't fix this current project (works well enough for what I need it to), but if there's a better way I'd like to be better informed.
The core problem here is #ref="counterChild" , that reference will not yet be set in OnInitialized or (the first) OnParametersSet of the parent.
"it should only be used to do JS interop stuff" is too strict, it should be used for logic that needs the render to be completed. Making use of a component reference qualifies too.
I would use one of:
Put this action in OnInitialized() of the child component. (Assumes you always run the same action on a new child).
trigger it by a parameter on the Child component. That could a simple IsLoaded boolean. Take action in OnParametersSet() of the child.
Keep it in OnAfterRender() but use firstRender instead of loading.
That last one is exactly what you already have and it's not totally wrong. But it automatically means you get 2 renders, not the best U/X.
It's better to use OnParametersSet instead of OnAfterRender.
OnAfterRender is called after each rendering of the component. At this stage, the task of loading the component, receiving information and displaying them is finished. One of its uses is the initialization of JavaScript components that require the DOM to work; Like displaying a Bootstrap modal.
Note: any changes made to the field values in these events are not applied to the UI; Because they are in the final stage of UI rendering.
OnParameterSet called once when the component is initially loaded and again whenever the child component receives a new parameter from the parent component.
Separate out the data from the UI code and you get a state object and a component.
public class CounterState
{
public event EventHandler? CounterUpdated;
public int Counter { get; private set; }
// Contrived as Async as in real coding there may well be async calls to DbContexts or HttpClient
public async ValueTask IncrementCounterAsync(object sender)
{
await Task.Delay(150);
Counter = Counter + this._appConfiguration.IncrementRate;
CounterUpdated?.Invoke(sender, EventArgs.Empty);
}
}
#page "/countercomponent"
#implements IDisposable
<p role="status">Current count: #this.State.Counter</p>
<button class="btn btn-primary" #onclick="IncrementCount">Click me</button>
#code {
// Need to define how we get this and it's nullability
private CounterState State { get; set; }
protected override void OnInitialized()
=> this.State.CounterUpdated += this.OnCounterUpdated;
private void OnCounterUpdated(object? sender, EventArgs e)
{
if (sender != this)
StateHasChanged();
}
public async Task IncrementCount(object sender)
=> await this.State.IncrementCounterAsync(sender);
public void Dispose()
=> this.State.CounterUpdated -= this.OnCounterUpdated;
}
The parent:
#page "/counter"
<h3>Counter Parent</h3>
<SimpleCounter />
#code {
// Need to define how we get this and it's nullability
private CounterState State { get; set; }
protected async override Task OnInitializedAsync()
=> await this.State.IncrementCounterAsync(this);
}
The Scoped Service
The simplest implementation is to register CounterState as a scoped Service:
builder.Services.AddScoped<CounterState>();
And then inject it into our two components:
[Inject] private CounterState State { get; set; } = default!;
This maintains state within the SPA session. Navigate away and return and the old count is retained. However, this is often too wide a scope.
The Cascade
Cascading restricts the scope of the state object to component and sub-component scope.
Capture the cascaded value in the children:
[CascadingParameter] private CounterState State { get; set; } = default!;
How and what you cascade depends on the dependancies and disposal requirements of the state object.
With no commitments, simply create an instance and cascade it.
<CascadingValue Value="State" IsFixed>
//...
<SimpleCounter />
</CascadingValue>
//....
private CounterState State { get; set; } = new();
If you have DI dependancies then you need to create the state instance in the context of the DI container. With no disposal requirements you can set the scope to Transient and get a new instance by injection.
However, if the object requires disposal, you need to use ActivatorUtilities to create an instance outside the Service Container, but in the context of the container to populate dependancies.
#page "/counter"
#inject IServiceProvider ServiceProvider
#implements IDisposable
#implements IAsyncDisposable
<h3>Counter Parent</h3>
<CascadingValue Value="State" IsFixed>
<CounterComponent />
</CascadingValue>
#code {
private CounterState State { get; set; } = default!;
// Run before any render takes place, so the cascaded value is the correct instance
protected override void OnInitialized()
=> this.State = ActivatorUtilities.CreateInstance<CounterState>(ServiceProvider);
protected async override Task OnInitializedAsync()
=> await this.State.IncrementCounterAsync(this);
// Demonstrates how to implement Dispose when you don't know if your object implements IDisposable
public void Dispose()
{
if (this.State is IDisposable disposable)
disposable.Dispose();
}
// Demonstrates how to implement Dispose when you don't know if your object implements IAsyncDisposable
public async ValueTask DisposeAsync()
{
if (this.State is IAsyncDisposable disposable)
await disposable.DisposeAsync();
}
}
Note the cascade is set as IsFixed to ensure it doesn't cause RenderTree Cascades.
Saving a few CPU Cycles
If you don't use OnAfterRender you can short circuit it (and save running a few lines of code) like this.
#implements IHandleAfterRender
//...
Task IHandleAfterRender.OnAfterRenderAsync()
=> Task.CompletedTask;

Blazor WASM: getting sibling components to communicate in order to update data

I have a page component and within it a breadcrumbs component and a business data component that displays all of the businesses for that page based on a category alias it is passed through the route.
Some of the breadcrumb links direct the user to another routable/page component and these work fine. However, some of them require a refresh of the business data component and it's these I'm having an issue with.
I've tried creating a service with a event that the breadcrumbs component can invoke when a link is clicked and the data component can subscribe to (following this post), but I can't get that to work as the return type is wrong when I try and call GetBusinesses as a result.
Here is the front end of my page component:
#if (breadcrumbs != null)
{
<Breadcrumbs Items="breadcrumbs"></Breadcrumbs>
}
<BrowseByBusiness Alias="#Alias"></BrowseByBusiness>
I set up my breadcrumbs like so (the Alias on the API call is a route parameter):
protected async override Task OnInitializedAsync()
{
categoryDto = await _publicClient.Client.GetFromJsonAsync<CategoryDto>($"api/Categories/GetCategoryByAlias/{Alias}");
breadcrumbs = new List<BreadcrumbItem>
{
new BreadcrumbItem("Browse By Category", "/browse-by-category"),
new BreadcrumbItem(categoryDto.Name, $"/browse-by-category/{categoryDto.Alias}"),
};
if (categoryDto.ParentCategory.Name != null)
{
breadcrumbs.Insert(1, new BreadcrumbItem(categoryDto.ParentCategory.Name, $"/browse-by-category/{categoryDto.ParentCategory.Alias}"));
}
}
My BrowseByBusiness component:
#foreach (var businessDto in businessesDto)
{
<Animate Animation="Animations.Fade" Easing="Easings.Ease" DurationMilliseconds="2000">
<BusinessCard businessDto="businessDto"></BusinessCard>
</Animate>
}
#code {
[Inject] IBusinessHttpRepository _businessRepo { get; set; }
public MetaData metaData = new MetaData();
IEnumerable<BusinessDto> businessesDto;
protected async override Task OnInitializedAsync()
{
await GetBusinesses();
}
private async Task GetBusinesses()
{
var pagingResponse = await _businessRepo.GetBusinessesByCategory(Alias, businessParameters);
businessesDto = pagingResponse.Items;
metaData = pagingResponse.MetaData;
}
}
My Breadcrumbs component:
<div class="flex-row border-bottom pb-2">
#foreach (var item in Items)
{
if (item.Equals(Last))
{
<span class="me-1">#item.Name</span>
}
else
{
<a class="me-1" href="#item.Url">#item.Name </a>
<span class="me-1">/</span>
}
}
</div>
#code {
[Parameter] public List<BreadcrumbItem> Items { get; set; }
private BreadcrumbItem Last;
protected async override Task OnInitializedAsync()
{
Last = Items.Last();
}
}
I'm using Blazor WASM and .net 6.
I recently had a similar need and ended up rolling my own (extremely simple) message broker. You can see full details in this blog post, but the short story is that you add a message broker class (which in my blog post has a silly name, but should really be called something like MessageBroker)...
public class SoupDragon {
public Action<Widget> NewWidget;
public void RaiseNewWidget(Widget w) =>
NewWidget?.Invoke(w);
}
You then register this as a service, inject it into the component that is to send the message, and use it follows...
_soupDragon.RaiseNewWidget(newWidget);
The component that is to pick up the message would inject a soup dragon, and hook up a method to catch the message. Whilst you can do this with a lambda, for reasons explained in more detail over there, you are best off hooking up to a method...
_soupDragon.NewWidget += HandleNewWidget;
One nice thing about this approach is that if you're doing server-side Blazor, you get communication between different users for free by making the message broker static.

Blazor component needs to modify value of parent component

I have these two components in my Blazor app:
Component1.razor:
<CascadingValue Value=this>
<Component2/>
</CascadingValue>
<p>#DisplayedText</p>
#code {
public string DisplayedText = string.Empty;
}
Component2.razor:
<button #onclick=#(e => { C1.DisplayedText = "testing"; })>Set DisplayedText</button>
#code {
[CascadingParameter]
public Component1 C1 { get; set; }
}
When I click the "Set DisplayedText" button, the text in the p element in Component1 should change to testing, but it does not. How can I fix this?
Merlin04, the following code snippet demonstrate how you can do it. Note that this is really a very simple sample, but it shows how you should code when communication between distant components is required.
Here's the code, copy and run it, and if you have more questions don't hesitate to ask.
MessageService.cs
public class MessageService
{
private string message;
public string Message
{
get => message;
set
{
if (message != value)
{
message = value;
if (Notify != null)
{
Notify?.Invoke();
}
}
}
}
public event Action Notify;
}
Note: The service is a normal class... It provides services to other objects, and it should be added to the DI container in Startup.ConfigureServices method to make it available to requesting clients. Add this: to the ConfigureServices method:
services.AddScoped<MessageService>();
Note: As you can see I define an event delegate of the Action type, which is invoked from the property's set accessor, when the user type text into a text box in Component3. Triggering this delegate causes the text entered by Components3 to be displayed in the Index component which is the parent of Component2 (see code below).
Index.razor
#page "/"
#inject MessageService MessageService
#implements IDisposable
<p>I'm the parent of Component2. I've got a message from my grand child:
#MessageService.Message</p>
<Component2 />
#code {
protected override void OnInitialized()
{
MessageService.Notify += OnNotify;
}
public void OnNotify()
{
InvokeAsync(() =>
{
StateHasChanged();
});
}
public void Dispose()
{
MessageService.Notify -= OnNotify;
}
}
Note that we directly bind to the MessageService.Message property, but the StateHasChanged method must be called to refresh the display of the text.
Component2.razor
<h3>Component2: I'm the parent of component three</h3>
<Component3/>
#code {
}
Component3.razor
#inject MessageService MessageService
<p>This is component3. Please type a message to my grand parent</p>
<input placeholder="Type a message to grandpa..." type="text"
#bind="#MessageService.Message" #bind:event="oninput" />
Note that in Component3 we bind the MessageService.Message to a text box, and the binding occurs each time you press a key board( input event versus change
event).
That is all, hope this helps, and don't hesitate to ask any question.
#Merlin04, this is an abuse and misuse of the cascading value feature. Ordinarily, a parent component communicates with its child via component parameters.
You can't update a cascaded value from a descendant.
Wrong...
The following code snippet demonstrate a better solution, based on what you do, though it is not optimal because your code and mine as well update a property from a method, when in matter of fact, we should changed the property's value directly, and not through a mediator code (that is a method)
Component2.razor
<button #onclick="#(() => SetDisplayedText.InvokeAsync("testing"))">Set
DisplayedText</button>
#code {
[Parameter]
public EventCallback<string> SetDisplayedText { get; set; }
}
Component1.razor
<Component2 SetDisplayedText="#SetDisplayedText"/>
<p>#DisplayedText</p>
#code {
private string DisplayedText = string.Empty;
public void SetDisplayedText(string newText)
{
DisplayedText = newText;
}
}
Note that calling the StateHasChanged method is not necessary, as this is the bonus you get when using the EventCallback 'delegate'
Hope this helps...
See enet's answer instead of this
You can't update a cascaded value from a descendant.
Instead, make a method in the parent (Component1) to set the value:
public void SetDisplayedText(string newText) {
DisplayedText = newText;
StateHasChanged();
}
Then, you can call that in the descendant:
<button #onclick=#(e => { C1.SetDisplayedText("testing"); })>Set DisplayedText</button>

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

Dependency Injection Architectural Design - Service classes circular references

I have the following service classes:
public class JobService {
private UserService us;
public JobService (UserService us) {
this.us = us;
}
public void addJob(Job job) {
// needs to make a call to user service to update some user info
// similar dependency to the deleteUser method
}
}
public class UserService {
private JobService js;
public UserService(JobService js) {
this.js = js;
}
public void deleteUser(User u) {
using (TransactionScope scope = new TransactionScope()) {
List<IJob> jobs = jobService.findAllByUser(u.Id);
foreach (IJob job in jobs) {
js.deleteJob(job);
}
userDao.delete(user);
scope.Complete();
}
}
}
Each of these service classes is getting instantiated by IoC container, and there is not a functional problem, but this to me feels like there is a potential design flaw in this approach and I'm wondering if there's an alternative approach that makes more sense.
As someone already pointed out, the problem is not with limitations to the DI container but with your design.
I see the reason that you have a separate UserService and a JobService which contain a reference to each other. This is because both UserService and JobService contain some logic that needs the other service as a reference (adding a job requires adding a user, etc.). However, I think that you should NOT reference one service from the other. Rather, you should have another layer of abstraction behind the services which the services will use for the common logic. So, the services will contain the logic which can't(shouldn't) be reused and the helpers will contain the shared logic.
For example:
public class UserHelper{
//add all your common methods here
}
public class JobService {
private UserHelper us;
public JobService (UserHelper us) {
this.us = us;
}
public void addJob(Job job) {
// calls helper class
}
}
public class UserService {
public UserService(UserHelper js) {
this.js = js;
}
public void deleteUser(User u) {
// calls helper class
}
}
In this way, you won't have any issues with circular references and you will have one place which contains the logic which needs to be reused by different services.
Also, I prefer having services which are completely isolated from one another.
The problem you are having has in fact nothing to do with the limitations of your DI container, but it is a general problem. Even without any container, it will be impossible to create those types:
var job = new JobService([what goes here???]);
var user = new UserService(job);
The general answer is therefore to promote one of the dependencies to a property. This will break the dependency cycle:
var job = new JobService();
var user = new UserService(job);
// Use property injection
job.User = user;
Prevent however from using more properties than strictly needed. These dependency cycles should be pretty rare and makes it much harder to either wire your types together, or to validate the DI configuration for correctness. Constructor injection makes this much more easy.
You can decouple the services by using events. Instead of calling a dependent method of another service when an action has been performed, an event is raised. An integrator can then wire up the services through the events. A service does not even know the existence of the other service.
public class JobService
{
public event Action<User, Job> JobAdded;
public void AddJob(User user, Job job)
{
//TODO: Add job.
// Fire event
if (JobAdded != null) JobAdded(user, job);
}
internal void DeleteJobs(int userID)
{
//TODO: Delete jobs
}
}
public class UserService
{
public event Action<User> UserDeleted;
public void DeleteUser(User u)
{
//TODO: Delete User.
// Fire event
if (UserDeleted != null) UserDeleted(u);
}
public void UpdateUser(User user, Job job)
{
//TODO: Update user
}
}
The integrator wires up the services
public static class Services
{
public static JobService JobService { get; private set; }
public static UserService UserService { get; private set; }
static Services( )
{
JobService = new JobService();
UserService = new UserService();
JobService.JobAdded += JobService_JobAdded;
UserService.UserDeleted += UserService_UserDeleted;
}
private static void UserService_UserDeleted(User user)
{
JobService.DeleteJobs(user.ID);
}
private static void JobService_JobAdded(User user, Job job)
{
UserService.UpdateUser(user, job);
}
}
(Note: I simplified event raising a bit. It's not thread safe like this. But you can assume that the events are subscribed in advance and will not be changed later.)
This wont work in Autofac. See circular dependencies section of the documentation.
Constructor/Constructor Dependencies Two types with circular
constructor dependencies are not supported. You will get an exception
when you try to resolve types registered in this manner.
You could potentially use relationship types (Func<>, Lazy<>) to break the cycle.
Your code is a bit too generic to come up with a proper solution but you should consider changing the direction of dependencies regardless of what IoC container you use.
public class JobService {
private UserService us;
public JobService (UserService us) {
this.us = us;
}
public void addJob(Job job) {
// needs to make a call to user service to update some user info
}
}
public class UserService {
private JobService js;
public UserService(Func<JobService> jsFactory) {
this.js = jsFactory(this);
}
public void deleteUser(User u) {
// needs to call the job service to delete all the user's jobs
}
}
Alternatively, In the case of your example you could move deleteUser and create a method, delete all jobs on the job service and instead of refering to the user use an id. this breaks the dependency by using the id.
Another alternative is to pass the job service as a parameter to deleteUser.

Categories