0

I have a class library which I am converting for use with DI in asp.net core. I wish to use this library in an asp.net core web application which mixes both MVC and Blazor server components. I need one particular interface used within this library (say IServiceA) to have two different implementations (ServiceAForMVC and ServiceAForBlazor).

i.e. I need the classes in this library which use constructor injection to get IServiceA to receive an instance of ServiceForMVC when created during a normal mvc page request, and to receive an instance of ServiceAForBlazor when created inside of a Blazor component.

Can this be done?

Initially I had one implementation of this interface, and this implementation used DI to get an IHttpContextAccessor instance.

I was hoping that the IHttpContextAccessor instance would have a null value for HttpContext when requested inside of a Blazor server component, and most of the time this is the case. But I have run into situations where the HttpContext value is not null, but it is disposed, and raises an ObjectDisposedException when I reference the HttpContext property.

So alternatively I'd like to create a 2nd implementation of IServiceA which does not require IHttpContextAccessor, and which would be returned when requested from inside a Blazor component.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459

1 Answers1

0

You can declare a shared delegate:

public delegate IServices ServiceResolver(string key);

Then setup the multiple concrete registrations and a manual mapping of those types:

builder.Services.AddTransient<ServiceAForMVC>();
builder.Services.AddTransient<ServiceAForBlazor>();

builder.Services.AddTransient<ServiceResolver>(serviceProvider => key =>
{
    switch (key)
    {
        case "MVC":
            return serviceProvider.GetService<ServiceAForMVC>();
        case "Blazor":
            return serviceProvider.GetService<ServiceAForBlazor>();
        default:
            throw new KeyNotFoundException(); // or maybe return null, up to you
    }
});

My Test Example:

Interface and Implementation:

public interface IServices
{
    string ServiceA();
}

public class ServiceAForMVC : IServices
{
    public string ServiceA()
    {    
        return "MVC";
    }
}

public class ServiceAForBlazor : IServices
{
    public string ServiceA()
    {
        return "Blazor";
    }
}

In MVC HomeController:

public class HomeController : Controller
{
    private readonly IServices _services;
    public HomeController(ServiceResolver serviceAccessor)
    {
        _services = serviceAccessor("MVC");
    }
    public string Test()
    { 
        return _services.ServiceA();
    }
}

In Blazor Component:

@inject ServiceResolver serviceAccessor

<h3>Blazor Component in MVC</h3>
<button @onclick="Test" class="btn btn-dark">Click to get answer</button>
<br />
<div>@Data </div>
@code {
    [Parameter]
    public string Data { get; set; } = string.Empty;
    private void Test()
    {
        Data = serviceAccessor("Blazor").ServiceA();
    }
 }

Result:

enter image description here

enter image description here

For more details, you can refer to this link.

Edit:

After you declare the shared delegate, your interface can still be used normally through dependency injection:

//The last registered implementation method is applied by default
builder.Services.AddTransient<IServices, ServiceAForMVC>();
builder.Services.AddTransient<IServices,ServiceAForBlazor>();


private readonly IServices _services;

public HomeController(IServices services)
{
    _services = services;
}
public string SomeAction()
{ 
    return _services.ServiceA();
}
Chen
  • 4,499
  • 1
  • 2
  • 9
  • Thank you for taking the time to respond. My problem is that I have other classes in the class library which also depend on IServiceA. They would not be able to supply a key to a service resolver. – David Hanlon Dec 02 '22 at 11:38
  • Hi @David Hanlon, do your other classes that depend on IServices also need to use a different implementation? Or just use one implementation method? I updated my answer, you can check it. – Chen Dec 05 '22 at 08:52
  • All the services are scoped and I'm relying on the classes getting the same instance created for the scope, whether the MVC request or a Blazor circuit. I'll try this approach when I can get back to this project, unfortunately I've been pulled away to work on something else. Thanks again. – David Hanlon Dec 06 '22 at 11:10