我们使用Praxedo,需要将其与我们的其他解决方案集成。它们的API需要使用SOAP,而且还需要MTOM和基本身份验证。
我们已经成功地集成了多个服务,比如他们的客户经理。在Customer Manager的例子中,我可以像这样创建Customer Manager客户端,它可以工作:
EndpointAddress endpoint = new(_praxedoSettings.CustomerManagerEndpoint);
MtomMessageEncoderBindingElement encoding = new(new TextMessageEncodingBindingElement
{
MessageVersion = MessageVersion.CreateVersion(EnvelopeVersion.Soap12, AddressingVersion.None)
});
CustomBinding customBinding = new(encoding, new HttpsTransportBindingElement());
_CustomerManagerClient = new CustomerManagerClient(customBinding, endpoint);
_praxedoSettings.AddAuthorizationTo(_CustomerManagerClient);
_ = new OperationContextScope(_CustomerManagerClient.InnerChannel);
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name]
= _praxedoSettings.ToHttpRequestMessageProperty();
其中PraxedoSettings如下:
using System;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Text;
namespace Common.Configurations
{
public class PraxedoSettings
{
public string Username { get; init; }
public string Password { get; init; }
public Uri BusinessEventAttachmentManagerEndpoint { get; init; }
public Uri BusinessEventManagerEndpoint { get; init; }
public Uri CustomerManagerEndpoint { get; init; }
public Uri FieldResourceManagerEndpoint { get; init; }
public Uri LocationManagerEndpoint { get; init; }
public ClientBase<TChannel> AddAuthorizationTo<TChannel>(ClientBase<TChannel> client)
where TChannel : class
{
client.ClientCredentials.UserName.UserName = Username;
client.ClientCredentials.UserName.Password = Password;
return client;
}
public string ToBasicAuthorizationHeader() =>
$" Basic {ToBase64()}";
private string ToBase64() =>
Convert.ToBase64String(ToAsciiEncoding());
private byte[] ToAsciiEncoding() =>
Encoding.ASCII.GetBytes($"{Username}:{Password}");
public T ToCredentials<T>()
{
T credentials = (T)Activator.CreateInstance(typeof(T));
Set(credentials, "login", Username);
Set(credentials, "password", Password);
return credentials;
}
private static T Set<T>(T credentials, string propertyName, string propertyValue)
{
typeof(T)
.GetProperty(propertyName)
.SetValue(credentials, propertyValue);
return credentials;
}
public string ToCredentialString() =>
$"{Username}|{Password}";
public HttpRequestMessageProperty ToHttpRequestMessageProperty()
{
HttpRequestMessageProperty httpRequestMessageProperty = new();
httpRequestMessageProperty.Headers[HttpRequestHeader.Authorization] = ToBasicAuthorizationHeader();
return httpRequestMessageProperty;
}
}
}
但是,在Business Event Attachment Manager客户机的情况下,类似的解决方案会导致:
AttachmentList来源:UnitTest1.cs line 75持续时间:1秒
消息:System.ServiceModel.FaultException:这些策略替代方案不能满足:{http://schemas.xmlsoap.org/ws/2004/09/policy/optimizedmimeserialization} OptimizedMimeSerialization
Stack Trace: ServiceChannel。HandleReply (ProxyOperationRuntime操作,ProxyRpc&ServiceChannel rpc)。EndCall (String行动,对象[]out, IAsyncResult result)& lt;在c__DisplayClass1_0。b__0 (IAsyncResult asyncResult)——前一个位置的堆栈跟踪结束——AttachmentControllerV6。GetAttachments(String businessEventId)第38行AttachmentControllerV6。HasAttachments(String businessEventId)第22行Tests.AttachmentList()第78行GenericAdapter
1.BlockUntilCompleted() NoMessagePumpStrategy.WaitForCompletion(AwaitAdapter awaiter) AsyncToSyncAdapter.Await(Func
1调用)TestMethodCommand。执行(TestExecutionContext上下文)& lt;祝辞c__DisplayClass4_0.b__0 ()<>c__DisplayClass1_01.<DoIsolated>b__0(Object _) ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) --- End of stack trace from previous location --- ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) ContextUtils.DoIsolated(ContextCallback callback, Object state) ContextUtils.DoIsolated[T](Func
1 func) SimpleWorkItem.PerformWork()
我们能够确定我们可以通过将ContentType
添加到HttpRequestMessageProperty
来解决这个策略问题,如下所示:
_praxedoSettings.AddAuthorizationTo(ManagerClient);
_ = new OperationContextScope(ManagerClient.InnerChannel);
HttpRequestMessageProperty httpRequestMessageProperty = _praxedoSettings.ToHttpRequestMessageProperty();
httpRequestMessageProperty.Headers[HttpRequestHeader.ContentType] = "multipart/related; type="application/xop+xml"";
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name]
= httpRequestMessageProperty;
但是结果是:
AttachmentList来源:UnitTest1.cs line 75持续时间:486 ms
消息:System.ServiceModel.FaultException: could 't decide从消息的边界!
Stack Trace: ServiceChannel。HandleReply (ProxyOperationRuntime操作,ProxyRpc&ServiceChannel rpc)。EndCall (String行动,对象[]out, IAsyncResult result)& lt;在c__DisplayClass1_0。b__0 (IAsyncResult asyncResult)——前一个位置的堆栈跟踪结束——AttachmentControllerV6。GetAttachments(String businessEventId)第38行AttachmentControllerV6。HasAttachments(String businessEventId)第22行Tests.AttachmentList()第78行GenericAdapter
1.BlockUntilCompleted() NoMessagePumpStrategy.WaitForCompletion(AwaitAdapter awaiter) AsyncToSyncAdapter.Await(Func
1调用)TestMethodCommand。执行(TestExecutionContext上下文)& lt;祝辞c__DisplayClass4_0.b__0 ()<>c__DisplayClass1_01.<DoIsolated>b__0(Object _) ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) --- End of stack trace from previous location --- ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) ContextUtils.DoIsolated(ContextCallback callback, Object state) ContextUtils.DoIsolated[T](Func
1 func) SimpleWorkItem.PerformWork()
通过对Postman的修改,我们发现可以通过添加内容类型和内容的边界来创建一个成功的请求,如下所示:
curl --location --request POST 'https://eu1.praxedo.com/eTech/services/cxf/v6/BusinessEventAttachmentManager'
--header 'Accept-Encoding: gzip,deflate'
--header 'Content-Type: Content-Type: multipart/related; type="application/xop+xml"; boundary="whatever"'
--header 'Authorization: Basic XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=='
--header 'Host: eu1.praxedo.com'
--data-raw '--whatever
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:bus="http://ws.praxedo.com/v6/businessEvent">
<soap:Header/>
<soap:Body>
<bus:listAttachments>
<businessEventId>00044</businessEventId>
</bus:listAttachments>
</soap:Body>
</soap:Envelope>'
但是这看起来非常粗糙,而且我们还远不清楚如何在c#上下文中将边界值添加到请求主体之前的XML中,而不需要手动重新创建我们应该从导入WSDL中获得的所有逻辑。
是否有一种方法,我们可以在ContentType中进行通信,不应该有边界值?还是有一个"正常"?如何将这个边界插入到请求中,即使(对我来说)在body中有一些非xml的东西似乎是错误的?
(我也忍不住觉得我们做认证的方式可能本质上是错误的。为什么我们需要实例化OperationContextScope
,即使我们不使用或以其他方式捕获它的值?为什么我们需要多次从设置中获取用户名和密码并以多种方式呈现它?
注:在Postman中进一步的实验表明,如果我们简单地使用内容类型type="application/xop+xml"
,我们不需要边界,但是在c#中,如果我们使用这个值作为内容类型,我们就回到了:
Message: System.ServiceModel.FaultException:这些策略替代方案不能满足:{http://schemas.xmlsoap.org/ws/2004/09/policy/optimizedmimeserialization} OptimizedMimeSerialization
我们终于让它工作了!
我们创建了一个值对象来捕获关于文件的信息:
public class BusinessEventAttachmentFile
{
public string BusinessEventId { get; init; }
public string FileName { get; init; }
public string ContentType { get; init; } = "application/pdf";
public byte[] FileBytes { get; init; }
public BusinessEventAttachmentFile ToDeleteFile() =>
new()
{
BusinessEventId = BusinessEventId,
FileName = FileName
};
}
我们修改了请求信封的实例,使其看起来像这样:
public partial class Envelope : IRequestEnvelope
{
private const string ContentType = "multipart/related; type="application/xop+xml"";
public object Header { get; init; }
public EnvelopeBody Body { get; init; }
[XmlIgnore]
private string StreamId { get; init; }
[XmlIgnore]
private BusinessEventAttachmentFile AttachmentFile;
internal static Envelope From(BusinessEventAttachmentFile attachmentFile)
{
string streamId = Guid.NewGuid()
.ToString();
return new()
{
AttachmentFile = attachmentFile,
Body = new()
{
createAttachment = new()
{
attachment = new()
{
entityId = attachmentFile.BusinessEventId,
name = attachmentFile.FileName
},
stream = attachmentFile.FileBytes
}
},
StreamId = streamId
};
}
public IRestRequest ToRestRequest(PraxedoSettings praxedoSettings) =>
new RestRequest(Method.POST)
.AddHeader("Content-Type", ContentType)
.AddHeader("Authorization", praxedoSettings.ToBasicAuthorizationHeader())
.AddParameter(ContentType, PraxedoSerializationHelper.CreateRequestBody(this), ParameterType.RequestBody)
.AddFile(
name: StreamId,
bytes: AttachmentFile.FileBytes,
fileName: AttachmentFile.FileName,
contentType: AttachmentFile.ContentType
);
}
我们可以使用ToRestRequest()
创建一个请求,它可以成功地从RestClient发送。