Как использовать удаленную службу WCF в .net 6 со ссылкой на службу

Хорошо, у меня кружится голова, потому что я провел прошлую неделю в поисках решения или достойного объяснения.

Вот моя не очень сложная цель: мне просто нужно взять удаленно размещенный сервис SOAP (созданный давным-давно в далекой-далекой галактике), подключить его к моей библиотеке классов, которая затем будет использоваться ASP.NET WebApi.

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

В обычных случаях он возвращает null, но когда я комментирую атрибут DebuggerStepThroughAttribute в реализации службы и ставлю точку останова внутри любого метода, возникает исключение: System.ServiceModel.CommunicationObjectFaultedException . Channel и InnerChannel выбрасывают это исключение, и я не могу понять, как от него избавиться. У меня такое ощущение, что весь этот клиент не отправляет нужные запросы в удаленную службу. У меня есть созданный и работающий запрос Postman, который отправляет обычный запрос POST с телом xml и получает ответ, поэтому удаленная служба не неисправна.

Я следовал всем инструкциям, которые смог найти, даже образцам на Github для CoreWCF, но так и не смог разобраться. Весь мой код генерируется svcutil, часть внедрения зависимостей и часть параметров были написаны мной. Прикреплю все файлы:

ФайлconnectServices.json, сгенерированный VS2022:

      {
  "ExtendedData": {
    "inputs": [
      "https://services.rs.ge/WayBillService/WayBillService.asmx"
    ],
    "collectionTypes": [
      "System.Array",
      "System.Collections.Generic.Dictionary`2"
    ],
    "namespaceMappings": [
      "*, RevenueService"
    ],
    "targetFramework": "net6.0",
    "typeReuseMode": "All"
  }
}

Также сгенерированный интерфейс канала (я понятия не имею, что это такое и как его использовать):

      [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.3")]
public interface WayBillsSoapChannel : RevenueService.WayBillsSoap, System.ServiceModel.IClientChannel
{
}

ServiceContract одним из методов:

        [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.3")]
    [System.ServiceModel.ServiceContractAttribute(ConfigurationName="RevenueService.WayBillsSoap")]
    public interface WayBillsSoap
    {
        [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/get_error_codes", ReplyAction="*")]
        System.Threading.Tasks.Task<RevenueService.get_error_codesResponse> get_error_codesAsync(RevenueService.get_error_codesRequest request);
    }

Реализация сервисного контракта:

      [System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.3")]
public partial class WayBillsSoapClient : System.ServiceModel.ClientBase<RevenueService.WayBillsSoap>, RevenueService.WayBillsSoap
{
    
    /// <summary>
    /// Implement this partial method to configure the service endpoint.
    /// </summary>
    /// <param name="serviceEndpoint">The endpoint to configure</param>
    /// <param name="clientCredentials">The client credentials</param>
    static partial void ConfigureEndpoint(System.ServiceModel.Description.ServiceEndpoint serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials);
    
    public WayBillsSoapClient(EndpointConfiguration endpointConfiguration) : 
            base(WayBillsSoapClient.GetBindingForEndpoint(endpointConfiguration), WayBillsSoapClient.GetEndpointAddress(endpointConfiguration))
    {
        this.Endpoint.Name = endpointConfiguration.ToString();
        ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
    }
    
    public WayBillsSoapClient(EndpointConfiguration endpointConfiguration, string remoteAddress) : 
            base(WayBillsSoapClient.GetBindingForEndpoint(endpointConfiguration), new System.ServiceModel.EndpointAddress(remoteAddress))
    {
        this.Endpoint.Name = endpointConfiguration.ToString();
        ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
    }
    
    public WayBillsSoapClient(EndpointConfiguration endpointConfiguration, System.ServiceModel.EndpointAddress remoteAddress) : 
            base(WayBillsSoapClient.GetBindingForEndpoint(endpointConfiguration), remoteAddress)
    {
        this.Endpoint.Name = endpointConfiguration.ToString();
        ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
    }
    
    public WayBillsSoapClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : 
            base(binding, remoteAddress)
    {
    }
            
    public virtual System.Threading.Tasks.Task OpenAsync()
    {
        return System.Threading.Tasks.Task.Factory.FromAsync(((System.ServiceModel.ICommunicationObject)(this)).BeginOpen(null, null), new System.Action<System.IAsyncResult>(((System.ServiceModel.ICommunicationObject)(this)).EndOpen));
    }
    
    public virtual System.Threading.Tasks.Task CloseAsync()
    {
        return System.Threading.Tasks.Task.Factory.FromAsync(((System.ServiceModel.ICommunicationObject)(this)).BeginClose(null, null), new System.Action<System.IAsyncResult>(((System.ServiceModel.ICommunicationObject)(this)).EndClose));
    }
    
    private static System.ServiceModel.Channels.Binding GetBindingForEndpoint(EndpointConfiguration endpointConfiguration)
    {
        if ((endpointConfiguration == EndpointConfiguration.WayBillsSoap))
        {
            System.ServiceModel.BasicHttpBinding result = new System.ServiceModel.BasicHttpBinding();
            result.MaxBufferSize = int.MaxValue;
            result.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max;
            result.MaxReceivedMessageSize = int.MaxValue;
            result.AllowCookies = true;
            result.Security.Mode = System.ServiceModel.BasicHttpSecurityMode.Transport;
            return result;
        }
        if ((endpointConfiguration == EndpointConfiguration.WayBillsSoap12))
        {
            System.ServiceModel.Channels.CustomBinding result = new System.ServiceModel.Channels.CustomBinding();
            System.ServiceModel.Channels.TextMessageEncodingBindingElement textBindingElement = new System.ServiceModel.Channels.TextMessageEncodingBindingElement();
            textBindingElement.MessageVersion = System.ServiceModel.Channels.MessageVersion.CreateVersion(System.ServiceModel.EnvelopeVersion.Soap12, System.ServiceModel.Channels.AddressingVersion.None);
            result.Elements.Add(textBindingElement);
            System.ServiceModel.Channels.HttpsTransportBindingElement httpsBindingElement = new System.ServiceModel.Channels.HttpsTransportBindingElement();
            httpsBindingElement.AllowCookies = true;
            httpsBindingElement.MaxBufferSize = int.MaxValue;
            httpsBindingElement.MaxReceivedMessageSize = int.MaxValue;
            result.Elements.Add(httpsBindingElement);
            return result;
        }
        throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
    }
    
    private static System.ServiceModel.EndpointAddress GetEndpointAddress(EndpointConfiguration endpointConfiguration)
    {
        if ((endpointConfiguration == EndpointConfiguration.WayBillsSoap))
        {
            return new System.ServiceModel.EndpointAddress("https://services.rs.ge/WayBillService/WayBillService.asmx");
        }
        if ((endpointConfiguration == EndpointConfiguration.WayBillsSoap12))
        {
            return new System.ServiceModel.EndpointAddress("https://services.rs.ge/WayBillService/WayBillService.asmx");
        }
        throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
    }
    
    public enum EndpointConfiguration
    {
        
        WayBillsSoap,
        
        WayBillsSoap12,
    }
}

Класс опций:

      public class RevenueServiceOptions
{
    public string? ServiceUrl { get; set; }

    public string? ServiceUser { get; init; }

    public string? ServicePassword { get; init; }

    internal void Validate()
    {
        if (string.IsNullOrWhiteSpace(ServiceUrl))
            throw new Exception($"{nameof(RevenueServiceOptions)}.{nameof(ServiceUrl)} should not be empty");

        if (string.IsNullOrWhiteSpace(ServiceUser))
            throw new Exception($"{nameof(RevenueServiceOptions)}.{nameof(ServiceUser)} should not be empty");

        if (string.IsNullOrWhiteSpace(ServicePassword))
            throw new Exception($"{nameof(RevenueServiceOptions)}.{nameof(ServicePassword)} should not be empty");
    }
}

И логика DependencyInjection:

      public static class DependencyInjection
{
    public static void AddExternalServices(this IServiceCollection services, RevenueServiceOptions revenueServiceOptions)
    {
        services.AddRevenueServiceOptions(revenueServiceOptions);

        services.AddServices(revenueServiceOptions);

        services.ConfigureAutoMapper();
    }

    private static void AddRevenueServiceOptions(this IServiceCollection services, RevenueServiceOptions revenueServiceOptions)
    {
        if (revenueServiceOptions == null)
            throw new ArgumentNullException(nameof(revenueServiceOptions));

        revenueServiceOptions.Validate();

        services.AddSingleton(revenueServiceOptions);
    }

    private static void AddServices(this IServiceCollection services, RevenueServiceOptions revenueServiceOptions)
    {
        services.AddScoped<WayBillsSoap>(_ =>
            new WayBillsSoapClient(WayBillsSoapClient.EndpointConfiguration.WayBillsSoap, revenueServiceOptions.ServiceUrl));
    }

    private static void ConfigureAutoMapper(this IServiceCollection services)
    {
        var mappingConfig = new MapperConfiguration(mc =>
        {
            mc.AddProfile(new AutoMapper());
        });

        var mapper = mappingConfig.CreateMapper();
        services.AddSingleton(mapper);
    }
}

serviceUrl, serviceUser и servicePassword берутся из файла appsettings.json

Не могли бы вы сказать мне, я делаю что-то не так? требуется ли этому клиенту дополнительная настройка для отправки запросов POST? Или, может быть, вы мне посоветуете хороший ресурс, который я не смог найти и который реально поможет?

Спасибо.

PS Пока здесь, не могли бы вы также сказать мне, как или где использовать частичный метод ConfigureEndpoint ?

0 ответов

Другие вопросы по тегам