在 C# 中,如何为 Praxedo 业务事件附件管理器创建 SOAP 集成?
Posted
技术标签:
【中文标题】在 C# 中,如何为 Praxedo 业务事件附件管理器创建 SOAP 集成?【英文标题】:In C#, how can I create a SOAP integration for the Praxedo Business Event Attachment Manager? 【发布时间】:2021-11-14 20:10:36 【问题描述】:我们使用 Praxedo,需要将其与我们的其他解决方案集成。 他们的 API 需要使用 SOAP,而且需要 MTOM 和 Basic 身份验证。
我们已成功集成了多项服务,例如他们的客户经理。对于客户经理,我可以像这样创建客户经理客户端,它可以工作:
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 第 75 行持续时间:1 秒
消息:System.ServiceModel.FaultException:这些策略 不能满足的替代方案: http://schemas.xmlsoap.org/ws/2004/09/policy/optimizedmimeserializationOptimizedMimeSerialization
堆栈跟踪:ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc) ServiceChannel.EndCall(String action, Object[] 出局,IAsyncResult 结果) 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.Execute(TestExecutionContext 上下文) 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 第 75 行持续时间:486 毫秒
消息:System.ServiceModel.FaultException:无法确定 消息的边界!
堆栈跟踪:ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc) ServiceChannel.EndCall(String action, Object[] 出局,IAsyncResult 结果) 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.Execute(TestExecutionContext 上下文) 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 中进行通信,不应该有边界值?或者是否有一种“正常”的方式可以将此边界插入到请求中,即使(在我看来)正文中包含非 Xml 的内容似乎是错误的?
(我也忍不住觉得我们进行身份验证的方式可能天生就有问题。为什么我们需要实例化OperationContextScope
,即使我们不使用或以其他方式捕获它的值?为什么我们需要多次从设置中获取用户名和密码并以多种方式呈现?)
附:在 Postman 中的进一步实验表明,如果我们简单地使用内容类型 type=\"application/xop+xml\"
,我们不需要边界,但是在 C# 中,如果我们使用这个值作为内容类型,我们会回到:
消息:System.ServiceModel.FaultException:这些策略 不能满足的替代方案: http://schemas.xmlsoap.org/ws/2004/09/policy/optimizedmimeserializationOptimizedMimeSerialization
【问题讨论】:
【参考方案1】:我们终于搞定了!
我们创建了一个值对象来捕获有关文件的信息:
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成功发送的请求。
【讨论】:
以上是关于在 C# 中,如何为 Praxedo 业务事件附件管理器创建 SOAP 集成?的主要内容,如果未能解决你的问题,请参考以下文章