Как зарегистрировать несколько реализаций одного и того же интерфейса в Asp.Net ядро?
у меня есть службы, которые являются производными от того же интерфейса
public interface IService
{
}
public class ServiceA : IService
{
}
public class ServiceB : IService
{
}
public class ServiceC : IService
{
}
обычно другие контейнеры IOC, такие как Unity
позволяет регистрировать конкретные реализации некоторыми Key
что отличает их.
In Asp.Net ядро Как зарегистрировать эти службы и разрешить их во время выполнения на основе некоторого ключа?
Я не вижу Add
способ служба key
или name
параметр, который обычно используется для различения бетона реализация.
public void ConfigureServices(IServiceCollection services)
{
// How do I register services here of the same interface
}
public MyController:Controller
{
public void DoSomeThing(string key)
{
// How do get service based on key
}
}
является ли Заводской шаблон единственным вариантом здесь?
обновление 1
Я пошел, хотя статья здесь это показывает, как использовать фабричный шаблон для получения экземпляров службы, когда у нас есть несколько конкретных реализаций. Однако это все еще не полное решение. когда я звоню _serviceProvider.GetService()
метод я не могу ввести данные в конструктор. Например рассмотрим этот пример
public class ServiceA : IService
{
private string _efConnectionString;
ServiceA(string efconnectionString)
{
_efConnecttionString = efConnectionString;
}
}
public class ServiceB : IService
{
private string _mongoConnectionString;
public ServiceB(string mongoConnectionString)
{
_mongoConnectionString = mongoConnectionString;
}
}
public class ServiceC : IService
{
private string _someOtherConnectionString
public ServiceC(string someOtherConnectionString)
{
_someOtherConnectionString = someOtherConnectionString;
}
}
как _serviceProvider.GetService()
ввести соответствующую строку подключения?
В Unity или любом другом МОК мы можем сделать это во время регистрации типа. Я могу использовать IOption однако это потребует от меня ввести все настройки, я не могу ввести определенную строку подключения в службу.
также обратите внимание, что я пытаюсь избежать использования других контейнеров (включая Unity), потому что тогда я должен зарегистрировать все остальное ( например, контроллеры) с новым контейнером.
также используя заводские шаблон для создания экземпляра службы против DIP, поскольку фабрика увеличивает количество зависимостей, от которых клиент вынужден зависеть подробности здесь
поэтому я думаю, что по умолчанию DI in ASP.NET ядро отсутствует 2 вещи
1 > Регистрация экземпляров с помощью ключа
2 > Ввести статические данные в конструктор во время регистрации
14 ответов:
Я сделал простой обходной путь с помощью
Func
когда я оказался в этой ситуации.services.AddTransient<ServiceA>(); services.AddTransient<ServiceB>(); services.AddTransient<ServiceC>(); services.AddTransient<Func<string, IService>>(serviceProvider => key => { switch(key) { case "A": return serviceProvider.GetService<ServiceA>(); case "B": return serviceProvider.GetService<ServiceB>(); case "C": return serviceProvider.GetService<ServiceC>(); default: throw new KeyNotFoundException(); // or maybe return null, up to you } });
и использовать его из любого класса, зарегистрированного в DI, как:
public class Consumer { private readonly Func<string, IService> _serviceAccessor; public Consumer(Func<string, IService> serviceAccessor) { _serviceAccessor = serviceAccesor; } public void UseServiceA() { //use _serviceAccessor field to resolve desired type _serviceAccessor("A").DoIServiceOperation(); } }
обновление
имейте в виду, что в этом примере ключом для разрешения является строка, для простоты и потому, что OP просил для этого случая в частности.
но вы можете использовать любой пользовательский тип разрешения в качестве ключа, так как вы обычно не хотите огромного N-case переключатель гниет ваш код. Зависит от того, как масштабируется ваше приложение.
другой вариант-использовать метод расширения
GetServices
СMicrosoft.Extensions.DependencyInjection
.зарегистрируйте свои услуги как:
services.AddSingleton<IService, ServiceA>(); services.AddSingleton<IService, ServiceB>(); services.AddSingleton<IService, ServiceC>();
затем решить с небольшим количеством Linq:
var services = serviceProvider.GetServices<IService>(); var serviceB = services.First(o => o.GetType() == typeof(ServiceB));
или
var serviceZ = services.First(o => o.Name.Equals("Z"));
(предполагая, что
IService
имеет строковое свойство с именем "Name")обязательно
using Microsoft.Extensions.DependencyInjection;
обновление
AspNet 2.1 источник:
GetServices
он не поддерживается
Microsoft.Extensions.DependencyInjection
.но вы можете подключить другой механизм инъекции зависимостей, например
StructureMap
смотрите, это домашняя страница и Проект GitHub.это совсем не сложно:
добавить зависимость к StructureMap в вашем
project.json
:"Structuremap.Microsoft.DependencyInjection" : "1.0.1",
впрысните его в ASP.NET трубопровод внутри
ConfigureServices
и зарегистрировать свои классы (см. документы)public IServiceProvider ConfigureServices(IServiceCollection services) // returns IServiceProvider ! { // Add framework services. services.AddMvc(); services.AddWhatever(); //using StructureMap; var container = new Container(); container.Configure(config => { // Register stuff in container, using the StructureMap APIs... config.For<IPet>().Add(new Cat("CatA")).Named("A"); config.For<IPet>().Add(new Cat("CatB")).Named("B"); config.For<IPet>().Use("A"); // Optionally set a default config.Populate(services); }); return container.GetInstance<IServiceProvider>(); }
затем, чтобы получить именованный экземпляр, вам придется просить
IContainer
public class HomeController : Controller { public HomeController(IContainer injectedContainer) { var myPet = injectedContainer.GetInstance<IPet>("B"); string name = myPet.Name; // Returns "CatB"
вот и все.
для примера, чтобы построить, нужно
public interface IPet { string Name { get; set; } } public class Cat : IPet { public Cat(string name) { Name = name; } public string Name {get; set; } }
я столкнулся с той же проблемой и хочу поделиться, как я ее решил и почему.
как вы упомянули, есть две проблемы. первый:
In Asp.Net Core как зарегистрировать эти службы и разрешить их по адресу время выполнения на основе какого-то ключа?
Итак, какие у нас есть варианты? Люди предлагают два:
используйте пользовательские фабрики (например
_myFactory.GetServiceByKey(key)
)используйте другой двигатель DI (например
_unityContainer.Resolve<IService>(key)
)является ли Заводской шаблон единственным вариантом здесь?
на самом деле оба варианта являются фабриками, потому что каждый контейнер IoC также является фабрикой (очень настраиваемый и сложный, хотя). И мне кажется, что другие варианты также являются вариациями Заводского шаблона.
так какой вариант лучше? Здесь я согласен с @Sock, который предложил использовать custom factory, и именно поэтому.
Во-Первых, Я всегда старайтесь избегать добавления новых зависимостей, когда они действительно не нужны. Поэтому я согласен с вами в этом вопросе. Кроме того, использование двух фреймворков DI хуже, чем создание пользовательской абстракции фабрики. Во втором случае вам нужно добавить новую зависимость от пакета (например, Unity), но в зависимости от нового заводского интерфейса здесь меньше зла. Основная идея ASP.NET Core DI, я считаю, это простота. Он поддерживает минимальный набор функций, следующих принцип KISS. Если вам нужны некоторые дополнительные функция затем DIY или использовать соответствующий Plungin который реализует желаемую функцию (открытый закрытый принцип).
во-вторых, часто нам нужно вводить много именованных зависимостей для одной службы. В случае Unity вам может потребоваться указать имена для параметров конструктора (используя
InjectionConstructor
). Эта регистрация использует отражение и какая-то умная логика угадать аргументы для конструктора. Это также может привести к ошибкам во время выполнения, если регистрация не совпадает с аргументы конструктора. С другой стороны, при использовании собственной фабрики у вас есть полный контроль над тем, как предоставить параметры конструктора. Он более удобочитаем и решается во время компиляции. принцип KISS снова.второй вопрос:
как может _serviceProvider.GetService () вводит соответствующее соединение струна?
во-первых, я согласен с вами, что в зависимости от новой вещи, как
IOptions
(и поэтому на упаковкеMicrosoft.Extensions.Options.ConfigurationExtensions
) это не очень хорошая идея. Я видел некоторые обсуждения оIOptions
где были разные мнения о его пользе. Опять же, я стараюсь избегать добавления новых зависимостей, когда они действительно не нужны. Это действительно необходимо? Думаю, нет. В противном случае каждая реализация должна была бы зависеть от нее без какой-либо явной необходимости, исходящей из этой реализации (для меня это выглядит как нарушение ISP, где я тоже согласен с вами). Это также верно в зависимости от завода, но в в данном случае это можете избежать.ASP.NET Core DI обеспечивает очень хорошую перегрузку для этой цели:
var mongoConnection = //... var efConnection = //... var otherConnection = //... services.AddTransient<IMyFactory>( s => new MyFactoryImpl( mongoConnection, efConnection, otherConnection, s.GetService<ISomeDependency1>(), s.GetService<ISomeDependency2>())));
вы правы, встроенный ASP.NET основной контейнер не имеет концепции регистрации нескольких служб, а затем извлечения конкретного, как вы предлагаете, фабрика является единственным реальным решением в этом случае.
кроме того, вы можете переключиться на сторонний контейнер, такой как Unity или StructureMap, который предоставляет вам необходимое решение (документально здесь: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing-the-default-services-container).
по-видимому, вы можете просто ввести IEnumerable вашего интерфейса службы! А затем найти экземпляр, который вы хотите с помощью LINQ.
мой пример для сервиса AWS SNS, но вы можете сделать то же самое для любого введенного сервиса на самом деле.
Startup
foreach (string snsRegion in Configuration["SNSRegions"].Split(',', StringSplitOptions.RemoveEmptyEntries)) { services.AddAWSService<IAmazonSimpleNotificationService>( string.IsNullOrEmpty(snsRegion) ? null : new AWSOptions() { Region = RegionEndpoint.GetBySystemName(snsRegion) } ); } services.AddSingleton<ISNSFactory, SNSFactory>(); services.Configure<SNSConfig>(Configuration);
SNSConfig
public class SNSConfig { public string SNSDefaultRegion { get; set; } public string SNSSMSRegion { get; set; } }
appsettings.json
"SNSRegions": "ap-south-1,us-west-2", "SNSDefaultRegion": "ap-south-1", "SNSSMSRegion": "us-west-2",
SNS Factory
public class SNSFactory : ISNSFactory { private readonly SNSConfig _snsConfig; private readonly IEnumerable<IAmazonSimpleNotificationService> _snsServices; public SNSFactory( IOptions<SNSConfig> snsConfig, IEnumerable<IAmazonSimpleNotificationService> snsServices ) { _snsConfig = snsConfig.Value; _snsServices = snsServices; } public IAmazonSimpleNotificationService ForDefault() { return GetSNS(_snsConfig.SNSDefaultRegion); } public IAmazonSimpleNotificationService ForSMS() { return GetSNS(_snsConfig.SNSSMSRegion); } private IAmazonSimpleNotificationService GetSNS(string region) { return GetSNS(RegionEndpoint.GetBySystemName(region)); } private IAmazonSimpleNotificationService GetSNS(RegionEndpoint region) { IAmazonSimpleNotificationService service = _snsServices.FirstOrDefault(sns => sns.Config.RegionEndpoint == region); if (service == null) { throw new Exception($"No SNS service registered for region: {region}"); } return service; } } public interface ISNSFactory { IAmazonSimpleNotificationService ForDefault(); IAmazonSimpleNotificationService ForSMS(); }
теперь вы можете получить услугу SNS для региона, который вы хотите в своем обычае сервис или контроллер
public class SmsSender : ISmsSender { private readonly IAmazonSimpleNotificationService _sns; public SmsSender(ISNSFactory snsFactory) { _sns = snsFactory.ForSMS(); } ....... } public class DeviceController : Controller { private readonly IAmazonSimpleNotificationService _sns; public DeviceController(ISNSFactory snsFactory) { _sns = snsFactory.ForDefault(); } ......... }
хотя кажется, что @ Miguel A. Arilla ясно указал на это, и я проголосовал за него, я создал поверх его полезного решения другое решение, которое выглядит аккуратно, но требует гораздо больше работы.
это определенно зависит от вышеуказанного решения. Так что в основном я создал что-то похожее на
Func<string, IService>>
и я назвал егоIServiceAccessor
в качестве интерфейса, а затем мне пришлось добавить еще несколько пристроек кIServiceCollection
такие как:public static IServiceCollection AddSingleton<TService, TImplementation, TServiceAccessor>( this IServiceCollection services, string instanceName ) where TService : class where TImplementation : class, TService where TServiceAccessor : class, IServiceAccessor<TService> { services.AddSingleton<TService, TImplementation>(); services.AddSingleton<TServiceAccessor>(); var provider = services.BuildServiceProvider(); var implementationInstance = provider.GetServices<TService>().Last(); var accessor = provider.GetServices<TServiceAccessor>().First(); var serviceDescriptors = services.Where(d => d.ServiceType == typeof(TServiceAccessor)); while (serviceDescriptors.Any()) { services.Remove(serviceDescriptors.First()); } accessor.SetService(implementationInstance, instanceName); services.AddSingleton<TServiceAccessor>(prvd => accessor); return services; }
служба доступа выглядит например:
public interface IServiceAccessor<TService> { void Register(TService service,string name); TService Resolve(string name); }
конечный результат, вы сможете зарегистрировать службы с именами или именованными экземплярами, как мы привыкли делать с другими контейнерами..например:
services.AddSingleton<IEncryptionService, SymmetricEncryptionService, EncyptionServiceAccessor>("Symmetric"); services.AddSingleton<IEncryptionService, AsymmetricEncryptionService, EncyptionServiceAccessor>("Asymmetric");
этого достаточно на данный момент, но чтобы сделать вашу работу полной, лучше добавить больше методов расширения, поскольку вы можете охватить все типы регистраций, следуя тому же подходу.
был еще один пост на stackoverflow, но я не могу найти его, где плакат объяснил в деталях почему эта функция не поддерживается и как ее обойти, в основном похоже на то, что заявил @Miguel. Это был хороший пост, хотя я не согласен с каждым пунктом, потому что я думаю, что есть ситуации, когда вам действительно нужны именованные экземпляры. Я опубликую эту ссылку здесь, как только найду ее снова.
на самом деле, вам не нужно передавать этот селектор или аксессор:
Я использую следующий код в моем проекте, и он работал хорошо до сих пор.
/// <summary> /// Adds the singleton. /// </summary> /// <typeparam name="TService">The type of the t service.</typeparam> /// <typeparam name="TImplementation">The type of the t implementation.</typeparam> /// <param name="services">The services.</param> /// <param name="instanceName">Name of the instance.</param> /// <returns>IServiceCollection.</returns> public static IServiceCollection AddSingleton<TService, TImplementation>( this IServiceCollection services, string instanceName ) where TService : class where TImplementation : class, TService { var provider = services.BuildServiceProvider(); var implementationInstance = provider.GetServices<TService>().LastOrDefault(); if (implementationInstance.IsNull()) { services.AddSingleton<TService, TImplementation>(); provider = services.BuildServiceProvider(); implementationInstance = provider.GetServices<TService>().Single(); } return services.RegisterInternal(instanceName, provider, implementationInstance); } private static IServiceCollection RegisterInternal<TService>(this IServiceCollection services, string instanceName, ServiceProvider provider, TService implementationInstance) where TService : class { var accessor = provider.GetServices<IServiceAccessor<TService>>().LastOrDefault(); if (accessor.IsNull()) { services.AddSingleton<ServiceAccessor<TService>>(); provider = services.BuildServiceProvider(); accessor = provider.GetServices<ServiceAccessor<TService>>().Single(); } else { var serviceDescriptors = services.Where(d => d.ServiceType == typeof(IServiceAccessor<TService>)); while (serviceDescriptors.Any()) { services.Remove(serviceDescriptors.First()); } } accessor.Register(implementationInstance, instanceName); services.AddSingleton<TService>(prvd => implementationInstance); services.AddSingleton<IServiceAccessor<TService>>(prvd => accessor); return services; } // // Summary: // Adds a singleton service of the type specified in TService with an instance specified // in implementationInstance to the specified Microsoft.Extensions.DependencyInjection.IServiceCollection. // // Parameters: // services: // The Microsoft.Extensions.DependencyInjection.IServiceCollection to add the service // to. // implementationInstance: // The instance of the service. // instanceName: // The name of the instance. // // Returns: // A reference to this instance after the operation has completed. public static IServiceCollection AddSingleton<TService>( this IServiceCollection services, TService implementationInstance, string instanceName) where TService : class { var provider = services.BuildServiceProvider(); return RegisterInternal(services, instanceName, provider, implementationInstance); } /// <summary> /// Registers an interface for a class /// </summary> /// <typeparam name="TInterface">The type of the t interface.</typeparam> /// <param name="services">The services.</param> /// <returns>IServiceCollection.</returns> public static IServiceCollection As<TInterface>(this IServiceCollection services) where TInterface : class { var descriptor = services.Where(d => d.ServiceType.GetInterface(typeof(TInterface).Name) != null).FirstOrDefault(); if (descriptor.IsNotNull()) { var provider = services.BuildServiceProvider(); var implementationInstance = (TInterface)provider?.GetServices(descriptor?.ServiceType)?.Last(); services?.AddSingleton(implementationInstance); } return services; }
мое решение для того, что это стоит... рассматривал возможность перехода в замок Виндзор, так как не могу сказать, что мне понравилось любое из решений выше. Прости!!
public interface IStage<out T> : IStage { } public interface IStage { void DoSomething(); }
создайте свои различные реализации
public class YourClassA : IStage<YouClassA> { public void DoSomething() { ...TODO } } public class YourClassB : IStage<YourClassB> { .....etc. }
Регистрация
services.AddTransient<IStage<YourClassA>, YourClassA>() services.AddTransient<IStage<YourClassB>, YourClassB>()
использование конструктора и экземпляра...
public class Whatever { private IStage ClassA { get; } public Whatever(IStage<YourClassA> yourClassA) { ClassA = yourClassA; } public void SomeWhateverMethod() { ClassA.DoSomething(); ..... }
заводской подход, безусловно, жизнеспособен. Другой подход заключается в использовании наследования для создания отдельных интерфейсов, которые наследуются от IService, реализации унаследованных интерфейсов в реализациях IService и регистрации унаследованных интерфейсов, а не базы. Является ли добавление иерархии наследования или фабрик" правильным " шаблоном, все зависит от того, с кем вы говорите. Мне часто приходится использовать этот шаблон при работе с несколькими поставщиками баз данных в одном приложении, что использует общие, такие как
IRepository<T>
, как основа для доступа к данным.примеры интерфейсов и реализаций:
public interface IService { } public interface IServiceA: IService {} public interface IServiceB: IService {} public IServiceC: IService {} public class ServiceA: IServiceA {} public class ServiceB: IServiceB {} public class ServiceC: IServiceC {}
контейнер:
container.Register<IServiceA, ServiceA>(); container.Register<IServiceB, ServiceB>(); container.Register<IServiceC, ServiceC>();
хотя реализация out of the box не предлагает ее, вот пример проекта, который позволяет вам регистрировать именованные экземпляры, а затем вводить INamedServiceFactory в ваш код и вытаскивать экземпляры по имени. В отличие от других решений facory здесь, это позволит вам зарегистрировать несколько экземпляров та же реализация но настроен по-другому
Как насчет услуг?
Если бы у нас был интерфейс INamedService (с свойством .Name), мы могли бы написать расширение IServiceCollection для .GetService (строковое имя), где расширение будет принимать этот строковый параметр и делать a .GetServices() на себя, и в каждом возвращенном экземпляре, найти экземпляр, чей INamedService.Name соответствует данному имени.
такой:
public interface INamedService { string Name { get; } } public static T GetService<T>(this IServiceProvider provider, string serviceName) where T : INamedService { var candidates = provider.GetServices<T>(); return candidates.FirstOrDefault(s => s.Name == serviceName); }
поэтому ваш IMyService должен реализовать INamedService, но вы получите разрешение на основе ключа, которое вы хотите, не так ли?
честно говоря, необходимость даже иметь этот интерфейс INamedService кажется уродливым, но если вы хотите пойти дальше и сделать вещи более элегантными, то [NamedServiceAttribute("A")] на реализации/классе может быть найден кодом в этом расширении, и он будет работать так же хорошо. Чтобы быть еще более справедливым, отражение медленное, поэтому оптимизация может быть в порядке, но, честно говоря, это то, с чем должен был помочь DI engine. Скорость и простота-это каждый великий вклад в TCO.
В общем, нет необходимости в явной фабрике, потому что "поиск именованной службы" - это такая многоразовая концепция, и классы фабрики не масштабируются как решение. И Func кажется прекрасным, но блок переключения так bleh, и опять же, вы будете писать Funcs так же часто, как вы будете писать фабрики. Начните с простого, многоразового, с меньшим количеством кода, и если это окажется не так, чтобы сделать это для вас, то идите сложным.
Я просто ввести IEnumerable
ConfigureServices при запуске.cs
Assembly.GetEntryAssembly().GetTypesAssignableFrom<IService>().ForEach((t)=> { services.AddScoped(typeof(IService), t); });
Папку Услуги
public interface IService { string Name { get; set; } } public class ServiceA : IService { public string Name { get { return "A"; } } } public class ServiceB : IService { public string Name { get { return "B"; } } } public class ServiceC : IService { public string Name { get { return "C"; } } }
MyController.cs
public class MyController { private readonly IEnumerable<IService> _services; public MyController(IEnumerable<IService> services) { _services = services; } public void DoSomething() { var service = _services.Where(s => s.Name == "A").Single(); } ... }
расширения.cs
public static List<Type> GetTypesAssignableFrom<T>(this Assembly assembly) { return assembly.GetTypesAssignableFrom(typeof(T)); } public static List<Type> GetTypesAssignableFrom(this Assembly assembly, Type compareType) { List<Type> ret = new List<Type>(); foreach (var type in assembly.DefinedTypes) { if (compareType.IsAssignableFrom(type) && compareType != type) { ret.Add(type); } } return ret; }
опоздали на эту вечеринку, но вот мое решение:...
Автозагрузка.cs или программа.cs если универсальный обработчик...
services.AddTransient<IMyInterface<CustomerSavedConsumer>, CustomerSavedConsumer>(); services.AddTransient<IMyInterface<ManagerSavedConsumer>, ManagerSavedConsumer>();
IMyInterface настройки интерфейса T
public interface IMyInterface<T> where T : class, IMyInterface<T> { Task Consume(); }
конкретные реализации IMyInterface T
public class CustomerSavedConsumer: IMyInterface<CustomerSavedConsumer> { public async Task Consume(); } public class ManagerSavedConsumer: IMyInterface<ManagerSavedConsumer> { public async Task Consume(); }
надеюсь, если есть какие-либо проблемы с этим способом, кто-то любезно укажет, почему это неправильный способ сделать это.
Я сделал что-то подобное некоторое время назад, и я думаю, что реализация была хорошей.
предположим, что вы должны обрабатывать входящие сообщения, каждое сообщение имеет тип, поэтому общий подход здесь заключается в использовании коммутатора и делать такие вещи:
@Override public void messageArrived(String type, Message message) throws Exception { switch(type) { case "A": do Something with message; break; case "B": do Something else with message; break; ... } }
мы можем избежать этого кода переключателя, а также мы можем избежать необходимости изменять этот код каждый раз, когда добавляется новый тип, следуя этой схеме:
@Override public void messageArrived(String type, Message message) throws Exception { messageHandler.getMessageHandler(type).handle(message); }
это хорошо, верно? Так как же мы можем достичь чего-то подобного?
сначала давайте определим аннотацию, а также интерфейс
@Retention(RUNTIME) @Target({TYPE}) public @interface MessageHandler { String value() } public interface Handler { void handle(MqttMessage message); }
а теперь давайте использовать эту аннотацию в классе:
@MessageHandler("myTopic") public class MyTopicHandler implements Handler { @override public void handle(MqttMessage message) { // do something with the message } }
последняя часть это написать класс MessageHandler, в этом классе вам просто нужно сохранить в карте все ваши экземпляры обработчиков, ключ карты будет имя темы и значение и экземпляр класса. В основном вы getMessageHandler метод будет выглядеть что-то например:
public Handler getMessageHandler(String topic) { if (handlerMap == null) { loadClassesFromClassPath(); // This method should load from classpath all the classes with the annotation MessageHandler and build the handlerMap } return handlerMap.get(topic); }
хорошая вещь с этим решением заключается в том, что если вам нужно добавить новый обработчик для нового сообщения, вам просто нужно написать новый класс, добавить аннотацию к классу и все. Остальная часть кода остается неизменной, потому что она универсальна и использует отражение для загрузки и создания экземпляров.
надеюсь, это поможет вам немного.