Как использовать удаленную службу 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 ?