I have implemented a service locator, but I am unsure if the services (instances) obtained from it can guarantee the security of the accessed or manipulated data.
In some classes where services cannot be injected (such as attribute classes or static extension classes and my team's coding style), I need to use a static property "Current" to resolve services that have already been added in Program.cs.
usage:
NasStorageContext.Current.Resolve<IWorkService>().GetWorkFlowById(id);
The actual code is shown below, which is designed to facilitate code review and identify any potential issues.
void AddNasStorageContext(this IServiceCollection services, Microsoft.Extensions.Configuration.IConfiguration configuration)
{
var engine = NasStorageContext.Create();
engine.ConfigureServices(services, configuration);
}
To meet this requirement, I need to implement a service locator.
Here is the core code.
This code has passed unit tests and has also been tested for high concurrency in real-world scenarios.
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace NasDIService
{
public interface INasStorageApp
{
void ConfigureRequestPipeline(IServiceProvider serviceProvider);
void ConfigureServices(IServiceCollection services, IConfiguration configuration);
T Resolve<T>(IServiceScope scope = null) where T : class;
}
public class BaseSingleton
{
static BaseSingleton()
{
AllSingletons = new Dictionary<Type, object>();
}
public static IDictionary<Type, object> AllSingletons { get; }
}
public class NasStorageApp : INasStorageApp
{
public virtual IServiceProvider ServiceProvider { get; protected set; }
public void ConfigureRequestPipeline(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton<INasStorageApp>(this);
}
public T Resolve<T>(IServiceScope scope = null) where T : class
{
var fac = ServiceProvider.GetService<IScopedServiceFactory<T>>();
if (fac == null)
{
return (T)Resolve(typeof(T), scope);
}
return fac.GetInstance().ScopedServiceInstance;
}
public object Resolve(Type type, IServiceScope scope = null)
{
return GetServiceProvider(scope)?.GetService(type);
}
protected IServiceProvider GetServiceProvider(IServiceScope scope = null)
{
if (scope != null)
return scope.ServiceProvider;
var accessor = ServiceProvider?.GetService<IHttpContextAccessor>();
var context = accessor?.HttpContext;
return context?.RequestServices ?? ServiceProvider;
}
private bool UseHttpScope()
{
var accessor = ServiceProvider?.GetService<IHttpContextAccessor>();
var context = accessor?.HttpContext;
return context != null;
}
}
public class NasStorageContext
{
public static INasStorageApp Current
{
get
{
if (Singleton<INasStorageApp>.Instance == null)
{
Create();
}
return Singleton<INasStorageApp>.Instance;
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
public static INasStorageApp Create()
{
return Singleton<INasStorageApp>.Instance ?? (Singleton<INasStorageApp>.Instance = new NasStorageApp());
}
}
public class Singleton<T> : BaseSingleton
{
private static T instance;
public static T Instance
{
get => instance;
set
{
instance = value;
AllSingletons[typeof(T)] = value;
}
}
}
#region scope
public interface IScopedService<T> : IDisposable
{
public T ScopedServiceInstance { get; }
}
public interface IScopedServiceFactory<T>
{
IScopedService<T> GetInstance();
}
public class ScopedServiceFactory<T> : IScopedServiceFactory<T>
{
public ScopedServiceFactory(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
private IServiceProvider ServiceProvider { get; init; }
public IScopedService<T> GetInstance()
{
var scope = ServiceProvider.CreateScope();
var instance = scope.ServiceProvider.GetService<T>();
var result = new ScopedService<T>(instance, scope);
return result;
}
}
internal class ScopedService<T> : IScopedService<T>
{
internal ScopedService(T t, IServiceScope serviceScope)
{
ScopedServiceInstance = t;
ServiceScope = serviceScope;
}
public T ScopedServiceInstance { get; private init; }
private IServiceScope ServiceScope { get; init; }
public void Dispose()
{
ServiceScope.Dispose();
}
}
#endregion scope
}
The process of how the code operates is as follows: in Program.cs
- ConfigureServices -> services.AddNasStorageContext() which use Create() method to new NasStorageApp() and set to static property Instance of Singleton.cs
- ConfigureServices -> services.AddNasBackgroundHostingContext()
void AddNasBackgroundHostingContext(this IServiceCollection services)
{
services.AddSingleton(typeof(IScopedServiceFactory<>), typeof(ScopedServiceFactory<>));
}
then i can use NasStorageContext.Current.Resolve to get the instance from container as previously mentioned.
Scenario:
The IWorkService can handle requests coming from the website's HTTP requests to manipulate the database.
The other usage is to use it in a background service to manipulate the database.
To my knowledge, the ServiceProvider can be obtained from HttpContext or through IHost.Services.BuildServiceProvider(). Therefore, I had added an if statement inside the Resolve method to handle this.
I have read relevant articles, including those from Stack Overflow and Code Review, as well as Microsoft's MSDN, all of which mention the potential risks of using the singleton pattern.
Using IServiceScopeFactory without implementing the Service Locator Pattern
As you see the code, I am trying to ensure that accessing services and performing function operations is both safe and normal.I'm not sure if this anti-pattern implementation is really safe??. I'm sorry for my limited Microsoft DI understanding, even though this code has passed unit tests.
If I have misused the service provider or misunderstood any lifetime mechanisms in the code, please advise me on how to correct it. I would appreciate any suggestions from the code reviewer.