I have service factory in .NET Core 3.1. Each service implements IService and each service has arguments in the constructor, (there is no parameterless ctors)
What is the best way to inject service factory?
--Update with current solution--
public interface IServiceFactory
{
IService Create(string serviceType);
}
public class ServiceFactory : IServiceFactory
{
public IService[] services;
public ServiceFactory(IService[] services)
{
this.services = services;
}
public IService Create(string serviceType)
{
return services.First(s => s.ServiceName == serviceType);
}
}
public interface IService
{
string ServiceName { get; }
bool Send();
}
public class ServiceA : IService
{
private ISrv1 srv1:
private ISrv2 srv2;
public ServiceA(ISrv1 srv1, ISrv2 srv2)
{
this.srv1 = srv1;
this.srv2 = srv2;
}
public ServiceName => return "serviceA";
public bool Send()
{
var data = srv1.GetData();
var msg = new MessageData
{
Id = data.Id,
Email = data.MailAddress
};
return srv2.Send(msg);
}
}
//not full class just the idea for sending message when getting message data from
//different services
public class ServiceB : IService
{
public ServiceB()
{
}
public ServiceName => return "serviceB";
public bool Send() => false;
}
How the factory should create the correct service with all dependencies?
And how to register correctly the services and the factory in configue services?
For now the solution is
services.AddTransient<IServiceFactory>(c =>
new ServiceFactory(new INotificationService[]
{
new ServiceA(c.GetService<ISrv1>(),c.GetService<ISrv2>()),
new ServiceB();
}));
As a solution I used IServiceProvider in the FactoryService for getting the correct service by the givven type in runtime.
I registered the services as following
services.AddScoped<IServiceFactory, ServiceFactory>();
services.AddScoped<ServiceA>()
.AddScoped<IService, ServiceA>(c => c.GetService<ServiceA>());
services.AddScoped<ServiceB>()
.AddScoped<IService, ServiceB>(c => c.GetService<ServiceB>());
Related
I am building an ASP.NET Core WebAPI application, it is working perfectly fine with the below setup
public void ConfigureServices(IServiceCollection services)
{
var settings = Configuration.Get<Settings>();
CosmosClient client = new CosmosClient(settings.CosmosDB.EndpointUrl, settings.CosmosDB.PrimaryKey);
CosmosDbContainerFactory cosmosDbClientFactory = new CosmosDbContainerFactory(client, settings.CosmosDB.DatabaseName, settings.CosmosDB.Containers);
services.AddSingleton<ICosmosDbContainerFactory>(cosmosDbClientFactory);
services.AddTransient<IFamilyService, FamilyService>();
services.AddTransient<IFamilyRepository, FamilyRepository>();
services.AddControllers();
}
But while trying to replace the manual service registration with Scrutor Scan, like mentioned below
public void ConfigureServices(IServiceCollection services)
{
var settings = Configuration.Get<Settings>();
CosmosClient client = new CosmosClient(settings.CosmosDB.EndpointUrl, settings.CosmosDB.PrimaryKey);
CosmosDbContainerFactory cosmosDbClientFactory = new CosmosDbContainerFactory(client, settings.CosmosDB.DatabaseName, settings.CosmosDB.Containers);
services.AddSingleton<ICosmosDbContainerFactory>(cosmosDbClientFactory);
services.Scan(s => s
.FromAssembliesOf(typeof(IApiAssemblyMarker))
.AddClasses(false)
.UsingRegistrationStrategy(RegistrationStrategy.Append)
.AsImplementedInterfaces()
.WithTransientLifetime()
);
services.AddControllers();
}
I am getting the following error
Error while validating the service descriptor 'ServiceType: FBAuthDemoAPI.Services.Contract.IFamilyService Lifetime: Transient ImplementationType: FBAuthDemoAPI.Services.Implementation.FamilyService': Unable to resolve service for type 'Microsoft.Azure.Cosmos.CosmosClient' while attempting to activate 'FBAuthDemoAPI.CosmosDBFactory.CosmosDbContainerFactory'.
public interface IFamilyService
{
}
public class FamilyService : IFamilyService
{
private readonly IFamilyRepository _familyRepository;
public FamilyService(IFamilyRepository familyRepository)
{
this._familyRepository = familyRepository;
}
}
public interface IGenericRepository<T> where T : class
{
}
public abstract class GenericRepository<T> : IGenericRepository<T> where T : Entity
{
private readonly Container _container;
private readonly ICosmosDbContainerFactory _cosmosDbContainerFactory;
public abstract string DatabaseName { get; }
public abstract string ContainerName { get; }
protected GenericRepository(ICosmosDbContainerFactory cosmosDbContainerFactory)
{
this._cosmosDbContainerFactory = cosmosDbContainerFactory ?? throw new ArgumentNullException(nameof(ICosmosDbContainerFactory));
this._container = this._cosmosDbContainerFactory.GetContainer(ContainerName)._container;
}
}
public interface IFamilyRepository : IGenericRepository<Family>
{
}
public class FamilyRepository : GenericRepository<Family>, IFamilyRepository
{
public override string DatabaseName => "FamilyDB";
public override string ContainerName => "Family";
public FamilyRepository(ICosmosDbContainerFactory factory) :
base(factory)
{
}
}
What is the issue and How do I fix this?
Update:: Having registered the classes shown below, it started working, but I'm not understanding why it failed when registering with the Single instance like mentioned above, Is it due to the limitation of Scrutor library?
var cosmosDBSettings = new CosmosDBSettings();
configration.Bind(CosmosDBSettings.SectionName, cosmosDBSettings);
services.AddSingleton(Microsoft.Extensions.Options.Options.Create(cosmosDBSettings));
services.Scan(scan => scan
.FromAssembliesOf(typeof(IApiAssemblyMarker))
.AddClasses(classes => classes.AssignableTo<ICosmosDbContainerFactory>()) // Filter classes
.AsSelfWithInterfaces()
.WithSingletonLifetime()
.AddClasses(x => x.AssignableTo(typeof(IGenericRepository<>))) // Can close generic types
.AsMatchingInterface()
.WithScopedLifetime()
.AddClasses(x => x.AssignableTo(typeof(IFamilyService))) // Can close generic types
.AsMatchingInterface()
.WithScopedLifetime());
I'm trying to implement UNITY on my WebApi2 application.
The problem is that I'm using an existing SqlConnection, depending on an identifier found in the URL of the resource.
So I need the identifier provided in the request uri, to create my context with.
Is it possible to get the {dbIdentifier} from the request URI, and parse it into the constructor of MyRepo?
The Request usi will look like: /api/{dbIdentifier}/{controller}/{id}
The structure looks like...
Request POST /api/myDbIdentifier/my/ + PAYLOAD data
Controller:
public class MyController : ApiController
{
private readonly IRepo _repo;
public MyController(IRepo repo)
{
_repo = repo;
}
}
Repo:
public class MyRepo : IRepo
{
private readonly MyContext _context;
public MyRepo(string dbIdentifier)
{
_context = new MyContext(GetConnection(dbIdentifier));
}
public void Insert(string s)
{
//Inserting string in context and save changes
}
private DbConnection(string id)
{
//psudo find connecion from pool, and return instance of DbConnection...
}
}
public interface IRepo
{
void Insert(string s);
}
Context:
public class MyContext : DbContext
{
public MyContext(DbConnection exitingConnection) : base(existingConnection, true)
{ }
}
Btw, it's my first time playing around with WebApi and Unity, so please bear with my ignorance.
UPDATED Unity part of my code...
UnityResolver (taken from https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/dependency-injection):
public class UnityResolver : IDependencyResolver
{
protected IUnityContainer Container;
public UnityResolver(IUnityContainer container)
{
if (container == null)
{
throw new ArgumentNullException(nameof(container), "Please provider an IUnityContainer.");
}
Container = container;
}
public void Dispose()
{
Container.Dispose();
}
public object GetService(Type serviceType)
{
try
{
return Container.Resolve(serviceType);
}
catch (ResolutionFailedException)
{
return null;
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
try
{
return Container.ResolveAll(serviceType);
}
catch (Exception)
{
return new List<object>();
}
}
public IDependencyScope BeginScope()
{
return new UnityResolver(Container.CreateChildContainer());
}
}
Unity Register part in my startup:
public static void Register(HttpConfiguration config)
{
// Configuring DI Container fo IoC (Invert of Control)
var container = new UnityContainer();
container.RegisterType<IRepo, MyRepo>(new HierarchicalLifetimeManager());
config.DependencyResolver = new UnityResolver(container);
}
You can try the following:
1)Create a DelegatingHandler where you can access HtppRequestMessage.RequestUri
2)Extract dbIdentifier from Uri
3)Wrap dbIdentifier with a class (e.g. DbIdentifier) and register it in unity using HierarchicalLifetimeManager
4)Remember to register handler in owin:
httpConfiguration.MessageHandlers.Add(new DbIdentifierHandler());
EDIT.
You can look into this post to find some inspiration :
How to pass Owin context to a Repo being injected into Api controller
I'm using ASP.NET Core
My IService is registered with the DI container
There are two implementations: FooService and BarService
I must choose a service based on current request's MVC Area
So I need something like:
services.AddScoped<IService>(
c => IsThisTheFooArea
? c.GetRequiredService<FooService>() as IService
: c.GetRequiredService<BarService>() as IService
);
I don't know how to implement the IsThisTheFooArea check.
How do I access the HttpContext or something similar, so I can inspect the current route?
Here is a way:
ConfigureServices.cs:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<IService>(provider =>
{
var actionContextAccessor = provider.GetService<IActionContextAccessor>();
var descriptor = actionContextAccessor.ActionContext.ActionDescriptor as ControllerActionDescriptor;
var areaName = descriptor.ControllerTypeInfo.GetCustomAttribute<AreaAttribute>().RouteValue;
if(areaName == "FooArea")
{
return new FooService();
}
else
{
return new BarService();
}
});
Services:
public interface IService { string DoThisThing(); }
public class FooService : IService
{
public string DoThisThing()
{
return "Foo";
}
}
public class BarService : IService
{
public string DoThisThing()
{
return "Bar";
}
}
And controllers:
[Area("FooArea")]
public class FooController : Controller
{
private readonly IService _service;
public FooController(IService service)
{
_service = service;
}
public IActionResult Index()
{
return Content(_service.DoThisThing());
}
}
[Area("BarArea")]
public class BarController : Controller
{
private readonly IService _service;
public BarController(IService service)
{
_service = service;
}
public IActionResult Index()
{
return Content(_service.DoThisThing());
}
}
You need to either implement (or find an implementation based on) IControllerFactory or IDependencyResolver and set it at application startup in order to inject the controller dependencies.
ControllerBuilder.Current.SetControllerFactory(new MyControllerFactory(container));
// Or...
DependencyResolver.SetResolver(new MyDependencyResolver(container));
More info
https://www.asp.net/mvc/overview/older-versions/hands-on-labs/aspnet-mvc-4-dependency-injection
I'm trying to create a simple Hangfire test but it's not working. Here's all the important code, and how I've configured it with the Hangire.Autofac . Not sure what I'm missing here. The exception I'm getting in the /hangfire dashbaord is below also.
public class AmazonSqsService : IAmazonSqsService
{
private readonly IBackgroundJobClient _backgroundJobClient;
private readonly ILogService _logService;
public AmazonSqsService(IBackgroundJobClient backgroundJobClient, ILogService logService)
{
_backgroundJobClient. = backgroundJobClient;
_logService= logService;
}
public async Task<string> Test()
{
return _backgroundJobClient.Enqueue(() => Looper());
}
public void Looper() {
while (true) { _logService.Info("In Looper Loop"); Thread.Sleep(5000); }
}
}
public partial class Startup
{
public static IContainer ConfigureContainer()
{
var builder = new ContainerBuilder();
RegisterApplicationComponents(builder);
AppGlobal.Container = builder.Build();
}
public static void RegisterApplicationComponents(ContainerBuilder builder)
{
builder.RegisterType<LogService>().As<ILogService>().InstancePerLifetimeScope();
builder.RegisterType<AmazonSqsService>().As<IAmazonSqsService>().InstancePerLifetimeScope();
builder.RegisterType<BackgroundJobClient>().As<IBackgroundJobClient>().InstancePerLifetimeScope();
builder.Register(c => JobStorage.Current).As<JobStorage>().InstancePerLifetimeScope();
builder.Register(c => new StateMachineFactory(JobStorage.Current)).As<IStateMachineFactory>().InstancePerLifetimeScope();
}
public static void ConfigureHangfire(IAppBuilder app)
{
app.UseHangfire(config =>
{
config.UseAutofacActivator(AppGlobal.Container);
config.UseSqlServerStorage("DefaultDatabase");
config.UseServer();
});
}
}
However in the dashboard I keep getting this error for the task:
Failed An exception occurred during job activation.
Autofac.Core.Registration.ComponentNotRegisteredException
The requested service 'App.Services.AmazonSqsService' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.
Figured this out eventually.
Correct Usage:
public class Service : IService {
public void MethodToQueue() { ... }
}
public class AnyOtherClass {
public void StartTasks() {
BackgroundJob.Enqueue<IService>(x => x.MethodToQueue()); //Good
}
}
Incorrect usage (what I was doing wrong)
public class Service : IService {
public void StartTasks() {
BackgroundJob.Enqueue(() => this.MethodToQueue()); //Bad
}
public void MethodToQueue() { ... }
}
public class AnyOtherClass {
public AnyOtherClass(IService service) {
service.StartTasks();
}
}
The ServiceStack AppHost provides a Funq.Container with which to register types that can be injected into Services as they are constructed. Can this container be used to register an ILog factory that returns an ILog appropriate for the type in which it will reside?
Put another way, given the following AppHost:
public class AppHost : AppHostBase
{
public AppHost() : base("Example Web Services", Assembly.GetExecutingAssembly())
{
}
public override void Configure(Funq.Container container)
{
var baseLogFactory = new ServiceStack.Logging.NLogger.NLogFactory();
LogManager.LogFactory = new ServiceStack.Logging.Elmah.ElmahLogFactory(baseLogFactory);
// Would prefer to register a Func<Type, ILog> one time here
}
}
And a Service:
public class FooService : IService<FooRequest>
{
static ILog Log { get { return LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); } }
// Would prefer:
// public ILog { get; set; }
public object Execute(FooRequest request)
{
Log.Info("Received request: " + request.Dump());
return new FooResponse();
}
}
Is there anything I can add to AppHost.Configure to avoid the static ILog boilerplate in all of my Services (and instead just use a plain old ILog property)?
Put a third way, most succinctly, Can I use Funq.Container for ILog injection instead of LogManager?
container.Register<ILog>(
ctx => LogManager.LogFactory.GetLogger(typeof(IService))
);
Now your service could look like this:
public class FooService : IService<FooRequest>
{
public ILog { get; set; }
public object Execute(FooRequest request)
{
Log.Info("Received request: " + request.Dump());
return new FooResponse();
}
}