Начал изучать принципы DI и работу с IoC контейнерами. Вопрос следующего характера: Пусть у меня есть некая библиотека классов MyClassLibrary, там представлены следующие классы:
public class DefaultDownloader : IDownloader
{
public string DownloadSomething(string param)
{
return $"{nameof(DefaultDownloader)} {param}";
}
}
public static class MyContainer
{
public static IContainer Container { get; set; }
static MyContainer()
{
var builder = new ContainerBuilder();
builder.RegisterType<DefaultDownloader>().As<IDownloader>();
builder.RegisterType<FirstSource>().AsSelf();
builder.RegisterType<SecondSource>().AsSelf();
Container = builder.Build();
}
}
public class SourceControl
{
public string GetAllInfo(string param)
{
string toReturn = "";
using (var scope = MyContainer.Container.BeginLifetimeScope())
{
var source1 = scope.Resolve<FirstSource>();
var source2 = scope.Resolve<SecondSource>();
toReturn += source1.DownloadInformation("FirstSource");
toReturn += " "+ source2.DownloadInformation("SecondSource");
}
return toReturn;
}
}
public class FirstSource
{
private readonly IDownloader downloader;
public FirstSource(IDownloader downloader)
{
this.downloader = downloader;
}
public string DownloadInformation(string param)
{
return downloader.DownloadSomething(param);
}
}
public class SecondSource
{
private readonly IDownloader downloader;
public SecondSource(IDownloader downloader)
{
this.downloader = downloader;
}
public string DownloadInformation(string param)
{
return downloader.DownloadSomething(param);
}
}
public interface IDownloader
{
string DownloadSomething(string param);
}
Этими классами можно пользоваться и по отдельности, но есть еще один класс (SourceControl), который позволяет забирать информацию со всех источников. В классе необходимо создать экземпляры источников (FirstSource, SecondSource) и вызвать некоторые методы оттуда. Соответственно, в этом же классе придется внедрять зависимости в FirstSource и SecondSource.
Использование извне:
class Program
{
private static IContainer Container { get; set; }
public static void ConfigureContainer()
{
var builder = new ContainerBuilder();
builder.RegisterType<MySecondDownloader>().As<IDownloader>();
builder.RegisterType<FirstSource>().AsSelf();
builder.RegisterType<SecondSource>().AsSelf();
Container = builder.Build();
}
static void Main(string[] args)
{
// Следующие две строчки можно закомментировать, чтобы использовать
// "стандартный" контейнер
ConfigureContainer();
MyContainer.Container = Container;
SourceControl sourceControl = new SourceControl();
Console.WriteLine(sourceControl.GetAllInfo("Main"));
Console.ReadLine();
}
}
public class MyFirstDownloader : IDownloader
{
public string DownloadSomething(string param)
{
return $"{nameof(MyFirstDownloader)} {param} - MODIFIED! (1)";
}
}
public class MySecondDownloader : IDownloader
{
public string DownloadSomething(string param)
{
return $"{nameof(MySecondDownloader)} {param} - MODIFIED! (2)";
}
}
Вопрос: Как это более правильно сделать с точки зрения логики и DI. В качестве IoC контейнера планирую использовать autofac. И правильно ли я сейчас это делаю?
Ваш подход вызывает вопросы, например, зачем нужны разные классы для Downloader'ов, зачем разные источники, почему это нельзя просто сконфигурировать как надо и прочее.
Но давайте отойдем от этих вопросов и попробуем поработать с вашим кодом как есть.
Что мы имеем: 3 имплементации одного и того же интерфкейа, а значит, чтобы их различать, можно их регистрировать с конкретными именами.
Далее, источники должны быть ассоциированы только с соответствующими реализациями Downloader'ов.
И наконец, SourceControl
должен быть также зарегистрирован и создан с помощью контейнера.
Ну и последнее, все эти именованные регистрации и сопоставления интерфейсов с реализацией должны находиться только на этапе конфигурации контейнера, то есть это должно быть абсолютно прозрачно для классов.
Со всеми этими требованиями, давайте сначала перепишем класс SourceControl
public class SourceControl
{
private ILifetimeScope _scopeFactory;
public SourceControl(ILifetimeScope scopeFactory)
{
_scopeFactory = scopeFactory;
}
public string GetAllInfo(string param)
{
string toReturn = "";
using (var scope = _scopeFactory.BeginLifetimeScope())
{
var source1 = scope.Resolve<FirstSource>();
var source2 = scope.Resolve<SecondSource>();
toReturn += source1.DownloadInformation("FirstSource");
toReturn += "\n" + source2.DownloadInformation("SecondSource");
}
return toReturn;
}
}
Создание контейнера становится таким
private static IContainer ConfigureContainer()
{
var builder = new ContainerBuilder();
builder.RegisterType<MyFirstDownloader>().Named<IDownloader>("MyFirstDownloader");
builder.RegisterType<MySecondDownloader>().Named<IDownloader>("MySecondDownloader");
builder.RegisterType<DefaultDownloader>().Named<IDownloader>("DefaultDownloader");
builder
.RegisterType<FirstSource>()
.WithParameter(
new ResolvedParameter(
(p, c) => p.ParameterType == typeof(IDownloader),
(p, c) => c.ResolveNamed<IDownloader>("MyFirstDownloader")));
builder
.RegisterType<SecondSource>()
.WithParameter(
new ResolvedParameter(
(p, c) => p.ParameterType == typeof(IDownloader),
(p, c) => c.ResolveNamed<IDownloader>("MySecondDownloader")));
builder
.RegisterType<SourceControl>();
var container = builder.Build();
return container;
}
Использование элементарное
var container = ConfigureContainer();
var control = container.Resolve<SourceControl>();
Console.WriteLine(control.GetAllInfo("Main"));
Вывод
MyFirstDownloader FirstSource - MODIFIED! (1)
MySecondDownloader SecondSource - MODIFIED! (2)
Очень рекомендую к прочтению книгу Марка Симана - Внедрение зависимостей в .NET, многие вещи после этого станут на свои места.
UPD
Что тут ещё можно сделать. Если заметить, то источники по сути одинаковые, так что можно выделить интерфейс
public interface ISource
{
string DownloadInformation(string param);
}
Зарегистрировать источники как интерфейсы
private static IContainer ConfigureContainer()
{
var builder = new ContainerBuilder();
builder.RegisterType<MyFirstDownloader>().Named<IDownloader>("MyFirstDownloader");
builder.RegisterType<MySecondDownloader>().Named<IDownloader>("MySecondDownloader");
builder.RegisterType<DefaultDownloader>().Named<IDownloader>("DefaultDownloader");
builder
.RegisterType<FirstSource>()
.As<ISource>()
.WithParameter(
new ResolvedParameter(
(p, c) => p.ParameterType == typeof(IDownloader),
(p, c) => c.ResolveNamed<IDownloader>("MyFirstDownloader")));
builder
.RegisterType<SecondSource>()
.As<ISource>()
.WithParameter(
new ResolvedParameter(
(p, c) => p.ParameterType == typeof(IDownloader),
(p, c) => c.ResolveNamed<IDownloader>("MySecondDownloader")));
builder
.RegisterType<SourceControl>();
var container = builder.Build();
return container;
}
И получать после просто их список
public class SourceControl
{
private ILifetimeScope _scopeFactory;
public SourceControl(ILifetimeScope scopeFactory)
{
_scopeFactory = scopeFactory;
}
public string GetAllInfo(string param)
{
var sb = new StringBuilder();
using (var scope = _scopeFactory.BeginLifetimeScope())
{
var sources = scope.Resolve<IEnumerable<ISource>>();
foreach(var source in sources)
sb.AppendLine(source.DownloadInformation(source.GetType().Name));
}
return sb.ToString();
}
}
Вывод будет аналогичный, но SourceControl
теперь не знает ни о каких реализациях вообще.
UPD 2
Переписал SourceControl
public class SourceControl
{
private IEnumerable<ISource> _sources;
public SourceControl(IEnumerable<ISource> sources)
{
_sources = sources;
}
public string GetAllInfo(string param)
{
var sb = new StringBuilder();
foreach (var source in _sources)
sb.AppendLine(source.DownloadInformation(source.GetType().Name));
return sb.ToString();
}
}
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Написал сервер для чата, который должен работать постоянноНо он почему то выключается при запуске, исключений не бросает
Мне необходимо создать программно новый сайт в IISДля этого использую команду:
Можно ли как-то получить данные из класса, используя только имя экземпляра классаТо есть
Как создать вебсокет сервер, чтобы к нему могли подключиться не только локально, но и пользователи по всему миру?