Более простая универсальная фабрика ClientBase c - PullRequest
2 голосов
/ 07 января 2020

После прочтения C# generi c ClientBase с путаницей в интерфейсе Мне удалось создать Soap фабрику веб-сервисов, которая упрощает мой код:

private T ClientMaker<TInterface, T>(string username, string password, string address)
    where TInterface : class 
    where T : ClientBase<TInterface>, TInterface
{
    var binding = new BasicHttpBinding();
    binding.MaxBufferPoolSize = int.MaxValue;
    binding.MaxBufferSize = int.MaxValue;
    binding.MaxReceivedMessageSize = int.MaxValue;

    binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
    binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;

    EndpointAddress ordersEndpoint = new EndpointAddress(address);

    T client = Activator.CreateInstance(typeof(T), new object[] { binding, ordersEndpoint }) as T;

    client.ClientCredentials.UserName.UserName = username;
    client.ClientCredentials.UserName.Password = password;

    return client;
}

и используется как это:

var client = ClientMaker<CreateWebOrder.WEB_Functions_Port, CreateWebOrder.WEB_Functions_PortClient>(user, pass, endpointBase + "Codeunit/WEB_Functions");

CreateWebOrder.WEB_Functions_Port is the interface implemented by CreateWebOrder.WEB_Functions_PortClient

Мне не очень нравится то, что мне нужно предоставить интерфейс И класс / тип веб-службы, так что я Интересно, есть ли способ обойти это, вытащив интерфейс или класс из параметра типа.

Учитывая, что тип возвращаемого значения - ClientBase, а TInterface известен благодаря предоставленному параметру типа, зачем мне тогда нужно указывать "PortClient"?

В идеале я хотел бы вызвать фабрика только с одним параметром типа, но я не знаю, возможно ли это

Ответы [ 2 ]

1 голос
/ 07 января 2020

Краткий ответ

Эта функциональность уже предоставляется "из коробки". С учетом аннотированного интерфейса:

[ServiceContract()]
interface IMath
{
    [OperationContract()]
     double Add(double A, double B);
}

Вы можете создать фабрику для указанной c привязки и конечной точки:

BasicHttpBinding myBinding = new BasicHttpBinding();

//configure the binding ........ then 

EndpointAddress myEndpoint = new EndpointAddress("http://localhost/MathService/Ep1");

ChannelFactory<IMath> myChannelFactory = new ChannelFactory<IMath>(myBinding, myEndpoint);

// Create a channel.
IMath wcfClient1 = myChannelFactory.CreateChannel();
double s = wcfClient1.Add(3, 39);

Длинное объяснение

WCF уже предоставляет фабрики для всех этих вещей, на самом деле у них их слишком много и недостаточно документов - еще в 2008 году люди, хотя каждый создавал клиентский прокси из WSDL, совместимого со стандартом WS- *, и настраивали через * 1047. *.

Тогда все действительно думали, что XML - это решение всего, точно так же, как все думали, что JSON будет решением 5 лет go или YAML 2 года go.

Клиент использует поведение. Поведения объединяют конечные точки и привязки. Все они могут быть созданы в коде и вставлены по мере необходимости. WCF предоставляет инфраструктуру для загрузки настроек из файлов app.config / web.config. К сожалению, эта инфраструктура конфигурации не может быть легко настроена, и документы How To не показывают весь процесс построения канала программно, потому что это считалось случаем advanced.

Фабрики для привязок, конечных точек, конвертов сообщений, тел и т. Д. c. Никто не думал документировать их все, потому что они, хотя каждый будет использовать инструменты для генерации прокси.

Например, Обзор клиента WCF показывает, как создать интерфейс и клиентскую базу, но 4 из 5 конструкторов либо используют конфигурацию по умолчанию, либо ожидают имена конфигурации. Последний, тем не менее, принимает как Binding, так и EndpointAddress. Нет необходимости в жестком кодировании, например:


public partial class SampleServiceClient : System.ServiceModel.ClientBase<ISampleService>, ISampleService
{
    public SampleServiceClient(Binding binding, EndpointAddress remoteAddress)
        :base(binding, remoteAddress)
    {
    }
}

Генерировать этот код каждый раз нецелесообразно, особенно когда нужно изменить поведение. Например, чтобы добавить шифрование или проверить / изменить пользовательские заголовки. Поэтому WCF предлагает ChannelFactory для «продвинутого» сценария ios. Это также используется прокси-сервером для кэширования экземпляров поведения, привязки и конечных точек. Документы находятся в «расширенном» разделе, хотя Как: использовать ChannelFactory .

Из этого примера do c это так же просто, как:

[ServiceContract()]
interface IMath
{
    [OperationContract()]
     double Add(double A, double B);
}


private void Run()
{
        BasicHttpBinding myBinding = new BasicHttpBinding();

        EndpointAddress myEndpoint = new EndpointAddress("http://localhost/MathService/Ep1");

        ChannelFactory<IMath> myChannelFactory = new ChannelFactory<IMath>(myBinding, myEndpoint);

        // Create a channel.
        IMath wcfClient1 = myChannelFactory.CreateChannel();
        double s = wcfClient1.Add(3, 39);
}

Почему я написал примеры в обратном порядке?

Именно потому, что ChannelFactory «скрыт» в расширенных функциях. Сначала я нашел клиентский пример программы c, затем Настройка поведения клиента , который указывал на ClientFactory и, наконец, Как: использовать ChannelFactory .

0 голосов
/ 07 января 2020

Прежде всего вам нужно очистить свою подпись. Уменьшите количество параметров. Используя книгу «Чистый код» в качестве ссылки, один параметр - это уже один параметр слишком много. Это имеет смысл, если вы можете реализовать Inversion Of Control, но на самом деле в этом случае это просто означает создание POCO / DTO / Class для передачи ваших данных. Это удовлетворяет принципу Open / Closed

public class EndpointDetails{
    public string UserName {get; set;}  
    public string Password {get; set;}
    public string Address {get; set;}
}

, а затем

private T ClientMaker<TInterface, T>(EndpointDetails endpointDetails)

«ClienMaker» на самом деле ничего не значит для меня и вводит в заблуждение. В C# мы должны свободно владеть смыслом кода, поэтому осмысленно назовите вещи (также из Чистого кода) - посмотрев на код, я бы назвал его MakeClientWithSettings () - потому что это то, что он будет делать в коде ниже, что я предлагаю.

Кроме того, это НЕ фабрика ... не путайте вещи, называя это так. Если бы это была фабрика, вы бы просто позвонили Create() БЕЗ ПАРАМЕТРОВ! и было бы теперь, как создать все, используя DI ( ссылка на то, чем может быть фабрика )

Теперь очистить все дженерики, как вы правильно заметили, немного все зависит от зависимостей, которые требуются.

По моему мнению, в этом одном методе есть просто слишком много всего, что происходит в этом одном методе

  • Это означает, что в классе существует более одной проблемы (нарушает Ответственность)
  • Кто-то действительно цепляется за str aws, чтобы сделать вещи "generi c"

Так что я могу попытаться помочь вам в рефакторинге этого, но я не знаю контекста вокруг кода, так что это то, что я придумал - возможно, это не скомпилировать, поэтому вам нужно настроить, я просто делаю это вручную в блокноте.

По сути, здесь jist

  • что Generi c, который вы передаете, также может быть выдан.
  • Единственное, что напоминает здесь фабрику, это то, что мы вручную создаем
  • тип возвращаемого значения с помощью generi c проверяется типом по логическому выводу

    private ClientBase<TInterface> MakeClientWithSettings<TInterface>(EndpointDetails endpointDetails)
        where TInterface : class 
    {
        var binding = new BasicHttpBinding();
        binding.MaxBufferPoolSize = int.MaxValue;
        binding.MaxBufferSize = int.MaxValue;
        binding.MaxReceivedMessageSize = int.MaxValue;
        binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
        binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
        EndpointAddress ordersEndpoint = new EndpointAddress(endpointDetails.Address);
    
        //T client = Activator.CreateInstance(typeof(T), new object[]{binding, ordersEndpoint}) as T;
        var client = new ClientBase<T>(binding, ordersEndpoint);
    
        client.ClientCredentials.UserName.UserName = endpointDetails.Username;
        client.ClientCredentials.UserName.Password = endpointDetails.Password;
    
            return client;
    }
    

Таким образом, согласно комментариям от Panagiotis Kanavos действительно кажется, что это должно быть сделано по-другому, и источник боли, пытаясь заставить Это наоборот.

Похоже, вам нужно использовать ваш контейнер Dependency Injection для обработки зависимостей в требуемой области, PerRequest или PerSession (Transient), и что в WCF уже есть фабрика, которая может сделайте все привязки за вас.

Это псевдокод (я не знаю ничего из этого клиентского материала, с которым вы работаете? это, вероятно, еще один класс, который вам нужно использовать или и Interface)

services.AddTransient<BasicHttpBinding>(() => new BasicHttpBinding(*))
services.AddTransient<EndpointAddress>(() = EndpointAddress(*))
services.AddTransient<Client?>(ClientFactory) <-- the dependencies here are the ones you need to inject above

И затем вы просто используете клиент там, где вам нужно, вставляя его в конструктор класса

public OrderSystem(Client? client){

   client.DoReqeust()

}
...