var container = new UnityContainer(); // Unity... container.RegisterType<IService, LocalService>("local"); container.RegisterType<IService, CloudService>("cloud"); IService service; if (context.IsLocal) { service = container.Resolve<IService>("local"); } else { service = container.Resolve<IService>("cloud"); }
public class LocalController { public LocalController([Dependency("local")] IService service) { this.service = service; } } public class CloudController { public CloudController([Dependency("cloud")] IService service) { this.service = service; } }
ServiceDescriptor
class with the ServiceName
property and use it to get a service. But after studying the source codes, it became clear that access to the ServiceProvider
implementation is closed (the access modifier class is internal), and we will not be able to change the logic of the GetService
method.ServiceProvider
, we decided to store the structure of matching the name and type of service directly in the container to use it when getting the service. How to influence the logic of the service, described in the first part of the article. Dictionary<Type, Dictionary<string, Type>>
private readonly Dictionary<Type, Dictionary<string, Type>> serviceNameMap = new Dictionary<Type, Dictionary<string, Type>>(); public void RegisterType(Type service, Type implementation, string name) { if (this.serviceNameMap.ContainsKey(service)) { var serviceNames = ServiceNameMap[service]; if (serviceNames.ContainsKey(name)) { /* overwrite existing name implementation */ serviceNames[name] = implementation; } else { serviceNames.Add(name, implementation); } } else { this.serviceNameMap.Add(service, new Dictionary<string, Type> { [name] = implementation }); } }
IServiceProvider
interface): public object Resolve(IServiceProvider serviceProvider, Type serviceType, string name) { var service = serviceType; if (service.GetTypeInfo().IsGenericType) { return this.ResolveGeneric(serviceProvider, serviceType, name); } var serviceExists = this.serviceNameMap.ContainsKey(service); var nameExists = serviceExists && this.serviceNameMap[service].ContainsKey(name); /* Return `null` if there is no mapping for either service type or requested name */ if (!(serviceExists && nameExists)) { return null; } return serviceProvider.GetService(this.serviceNameMap[service][name]); }
public static IServiceCollection AddScoped<TService, TImplementation>(this IServiceCollection services, string name) where TService : class where TImplementation : class, TService { return services.Add(typeof(TService), typeof(TImplementation), ServiceLifetime.Scoped, name); }
private static IServiceCollection Add(this IServiceCollection services, Type serviceType, Type implementationType, ServiceLifetime lifetime, string name) { var namedServiceProvider = services.GetOrCreateNamedServiceProvider(); namedServiceProvider.RegisterType(serviceType, implementationType, name); services.TryAddSingleton(namedServiceProvider); services.Add(new ServiceDescriptor(implementationType, implementationType, lifetime)); return services; } private static NamedServiceProvider GetOrCreateNamedServiceProvider(this IServiceCollection services) { return services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(NamedServiceProvider))?.ImplementationInstance as NamedServiceProvider ?? new NamedServiceProvider(); }
public static TService GetService<TService>(this IServiceProvider serviceProvider, string name) where TService : class { return serviceProvider .GetService<NamedServiceProvider>() .Resolve<TService>(serviceProvider, name); }
services.AddScoped<IService, LocalhostService>("local"); services.AddScoped<IService, CloudService>("cloud"); var service1 = this.serviceProvider.GetService<IService>("local"); // resolves LocalhostService var service2 = this.serviceProvider.GetService<IService>("cloud"); // resolves CloudService
[FromServices]
, with this syntax: public IActionResult Local([FromNamedServices("local")] IService service) { ... }
IModelBinder
interface) will create the parameter object. For example, the [FromServices]
attribute included in ASP.NET Core MVC indicates that the IoC container will be used to bind the model, therefore the ServicesModelBinder
class will be used for this parameter, which will try to get the parameter type from the IoC container.ModelBinder
, which will receive the service from the IoC container by identifier, and the second is its own FromNamedServices
attribute, which will accept the service identifier in the constructor, and which will indicate that you should use the specific ModelBinder
to bind . [AttributeUsage(AttributeTargets.Parameter)] public class FromNamedServicesAttribute : ModelBinderAttribute { public FromNamedServicesAttribute(string serviceName) { this.ServiceName = serviceName; this.BinderType = typeof(NamedServicesModelBinder); } public string ServiceName { get; set; } public override BindingSource BindingSource => BindingSource.Services; }
public class NamedServicesModelBinder : IModelBinder { private readonly IServiceProvider serviceProvider; public NamedServicesModelBinder(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; } public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext)); var serviceName = GetServiceName(bindingContext); if (serviceName == null) return Task.FromResult(ModelBindingResult.Failed()); var model = this.serviceProvider.GetService(bindingContext.ModelType, serviceName); bindingContext.Model = model; bindingContext.ValidationState[model] = new ValidationStateEntry { SuppressValidation = true }; bindingContext.Result = ModelBindingResult.Success(model); return Task.CompletedTask; } private static string GetServiceName(ModelBindingContext bindingContext) { var parameter = (ControllerParameterDescriptor)bindingContext .ActionContext .ActionDescriptor .Parameters .FirstOrDefault(p => p.Name == bindingContext.FieldName); var fromServicesAttribute = parameter ?.ParameterInfo .GetCustomAttributes(typeof(FromServicesAttribute), false) .FirstOrDefault() as FromServicesAttribute; return fromServicesAttribute?.ServiceName; } }
Source: https://habr.com/ru/post/307034/
All Articles