Fabryka zwracająca serwis po wpisaniu nazwy typu

0

Mam do napisania mechanizm, który eksportuje dane z listy do pliku CSV. Rozwiązanie mam zrobić generyczne. Widzę to tak: każdy serwis ma implementować interfejs IExportableCsv, gdzie będzie metoda Write() zwracająca dane. Może ją implementować wiele serwisów. Następnie do kontrolera ma przyjść zapytanie typu: "api/export/{type}", gdzie "type" ma zawierać nazwę typu/serwisu, gdzie danego stringa trzeba powiązać z danym interfejsem serwisu. Jeśli serwis udałoby się pobrać, następowałoby sprawdzenie, czy da się go ograniczyć do IExportableCsv. Jeśli tak, wykonywana jest metoda Write() i rozpoczyna się pobieranie pliku.
Czy ktoś mógłby powiedzieć, jak stworzyć fabrykę, która pozwoliłaby pobrać serwis zaraz po jego nazwie? Czy koniecznie byłoby ręczne tworzenie słownika, czy być może dałoby się stworzyć to też generycznie?

1

Możesz na podstawie requestu wstrzykiwać implementacje tego interfejsu ExportableCsv do kontrolera. Samo tworzenie implementacji w fabryce możesz zrealizować bazując na jakimś enumie lub (moim zdaniem mniej jawne i gorsze rowziązanie) tworzyć typ refleksją na podstawie nazwy. Nazwa exportera mogła by być wyciągana z requestu i mapowana na tego enuma a fabryka zwracałaby już konkretną implementację do wstrzykniecia. W .net core możesz to zrobić mniej więcej tak:

 public void ConfigureServices(IServiceCollection services)
 {
        services.AddHttpContextAccessor();
        services.AddScoped<IExportableCsv>(IExportableCsvFactory);
 }

 private static IExportableCsv IExportableCsvFactory(IServiceProvider serviceProvider)
 {
        var http = serviceProvider.GetService<IHttpContextAccessor>(); // dostęp do requestu.
   
        return new ExportableCsv(); // tutaj możesz także zresolwować tą instancje powyższym serviceProviderem, stworzyć ją ręcznie lub refleksją.
 }

Kod oczywiście poglądowy.

0

Dzięki za pomoc. Korzystam z narzędzia DI Windsor Castle, gdzie wykorzystałem (być może finalnie) takie rozwiązanie

Inicjalizacja:

public override void PostInitialize()
{
    try
    {
        InitializationManager.InitalizeIocRegistrations(IocManager);
        InitializationManager.InitializeApp(IocManager);
        IocManager.Register<CsvWriter, CsvWriter>();
        IocManager.Register<IUserService, UserService>();

        IocManager.IocContainer.Kernel.Register(
            Component.For<ICsvExportable>()
                .ImplementedBy<UserService>()
                .Named(nameof(UserDto))
                .LifeStyle.Transient);
        IocManager.IocContainer.Kernel.Register(
            Component.For<ICsvExportable>()
                .ImplementedBy<TenantAppService>()
                .Named(nameof(TenantDto))
                .LifeStyle.Transient);
    }
}

Konkretna instancja wywoływana jest w kontrolerze przez *nameof * danej klasy, której dotyczy serwis. Metoda kontrolera wygląda tak:

[HttpGet]
[Route("/api/documents/export/csv/{type}")]
public IActionResult ExportExternalUsersToCsv(string type)
{
    var handlers = _iocManager.IocContainer.Kernel.GetHandlers(typeof(ICsvExportable));
    var names = handlers.Select(h => h.ComponentModel.Name);

    if (names.FirstOrDefault(e => e == type) == null)
    {
        throw new UserFriendlyException("Service related with specified type does not exist.");
    }

    var files = _iocManager.Resolve<ICsvExportable>(type).Write();
    return File(files, "text/csv");
}

Pytanie, czy da się to jeszcze jakoś usprawnić i czy jest sens wydzielać logikę do jakiejś osobnej klasy, która faktycznie będzie zarejestrowana jako fabryka?

1

A nie można tego LINQ napisać krócej?

if (!_iocManager.IocContainer.Kernel.GetHandlers(typeof(ICsvExportable)).Any(x => x.ComponentModel.Name == type))
    throw new UserFriendlyException("Service related with specified type does not exist.");

Albo najlepiej wcale - Windsor i tak pewnie rzuca wyjątkiem jeśli nie może znaleźć komponentu.

0

Finalnie fabryka wygląda tak:

 public class ExportFactory : VZeroAppServiceBase
    {
        private readonly IIocManager _iocManager;

        public ExportFactory(IIocManager iocManager)
        {
            _iocManager = iocManager;
        }

        public virtual ICsvExportable GetCsvWriter(string entityType)
        {
            if (_iocManager.IocContainer.Kernel.GetHandlers(typeof(ICsvExportable)).All(e => e.ComponentModel.Name != entityType))
            {
                throw new VZeroUserFriendlyException(L("ServiceRelatedWithTypeNotExist"));
            }

            return _iocManager.Resolve<ICsvExportable>(entityType);
        }
    }

Rejestracja

 IocManager.Register<ExportFactory, ExportFactory>(DependencyLifeStyle.Transient);
                IocManager.IocContainer.Kernel.Register(
                    Component.For<ICsvExportable>()
                        .ImplementedBy<UserService>()
                        .Named(nameof(UserDto))
                        .LifeStyle.Transient);

Wywołanie

        [HttpGet]
        [Route("/api/documents/export/csv")]
        [AbpAuthorize(PermissionNames.Pages_Users_Administrators)]
        [ApiExplorerSettings(IgnoreApi = true)]
        public IActionResult ExportExternalUsersToCsv(string entityType)
        {
            return File(_exportFactory.GetCsvWriter(entityType).Write(), "text/csv");
        }

Pytanie, czy jeszcze coś wypadałoby tutaj usprawnić? Wiem, że przypomina to "standardowej" fabryki dla Windsor - taką znalazłem tutaj klik. Windsor wymaga: poszerzania metod dla każdej nowej implementacji, rozszerzania enuma, rozszerzania switcha, i oczywiście dodawania kolejnej rejestracji. Nie przekonało mnie to specjalnie.

0

czesc, podam ci prosty przyklad który bym zastosował, ale nie uzywajac winsdora,

public interface IExportable()
{
  void Export();
  string SupportType{get;set;}
}
pubic class ExportCSV : IExportable()
{
 object Export() { ...}
 public string SupportType() => "csv"
}
pubic class ExportXLS : IExportable
{
 object Export() { ...}
 public string SupportType() => "xls"
}

i w zasadzie te dwie klasy mógłbyś umieć w jakimś tam kontenerze jako implementujace intefrejs IExportable, ja to przykladowo umieszcze w tablicy (somewhere in code...)

IExportable[] ExportableClasses = new IExportable[] {  new ExpotsXls(), new ExportCSV() };

public void Export(string mode)
{
var result =  ExportableClasses .Where(x=> x.supportType == mode).FirstOrDefault()?.Export();
}
 

mam nadzieję że źle nie zrozumiałem tematu i coś pomogę :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1