我們使用 Praxedo,需要將其與我們的其他解決方案集成。他們的 API 需要使用 SOAP,而且還需要 MTOM 和基本身份驗證。
我們已經成功地與多種服務集成,例如他們的客戶經理。在客戶經理的情況下,我可以像這樣創建客戶經理客戶端,它可以作業:
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 客戶端,類似的解決方案會導致:
附件串列來源:UnitTest1.cs line 75 持續時間:1 秒
訊息:System.ServiceModel.FaultException:無法滿足這些策略替代方案:{http://schemas.xmlsoap.org/ws/2004/09/policy/optimizedmimeserialization}OptimizedMimeSerialization
Stack Trace: ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc) ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result) <>c__DisplayClass1_0.b__0(IAsyncResult asyncResult) --- End of stack trace from previous location --- AttachmentControllerV6.GetAttachments(String businessEventId) line 38 AttachmentControllerV6.HasAttachments(String businessEventId) line 22 Tests.AttachmentList() line 78 GenericAdapter
1.BlockUntilCompleted() NoMessagePumpStrategy.WaitForCompletion(AwaitAdapter awaiter) AsyncToSyncAdapter.Await(Func1 invoke) TestMethodCommand.Execute(TestExecutionContext context) <>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](Func1 func) SimpleWorkItem.PerformWork()
We were able to determine that we can solve this policy problem by adding the ContentType to the HttpRequestMessageProperty, like this:
_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;
But this results in:
AttachmentList Source: UnitTest1.cs line 75 Duration: 486 ms
Message: System.ServiceModel.FaultException : Couldn't determine the boundary from the message!
Stack Trace: ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc) ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result) <>c__DisplayClass1_0.b__0(IAsyncResult asyncResult) --- End of stack trace from previous location --- AttachmentControllerV6.GetAttachments(String businessEventId) line 38 AttachmentControllerV6.HasAttachments(String businessEventId) line 22 Tests.AttachmentList() line 78 GenericAdapter
1.BlockUntilCompleted() NoMessagePumpStrategy.WaitForCompletion(AwaitAdapter awaiter) AsyncToSyncAdapter.Await(Func1 invoke) TestMethodCommand.Execute(TestExecutionContext context) <>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](Func1 func) SimpleWorkItem.PerformWork()
By hacking around in Postman, we've discovered we can create a successful request by adding a boundary both the content type and the content, like so:
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>'
But this seems very hacky and it is far from clear how we could add the boundary value to the body of the request before the XML from within the C# context, without manually recreating all the logic which we should be getting from importing the WSDL.
Is there a way we can communicate in the ContentType there shouldn't be a boundary value? Or is there a "normal" way to insert this boundary into the request, even though it seems wrong (to me) have something non-Xml in the body?
(I also can't help feeling that the we way we are doing authentication may be inherently wrong. Why do we need to instantiate OperationContextScope even though we don't use or otherwise capture its value? Why do we need to get the username and password out of the settings multiple times and present it multiple ways?)
P.S. Further experimenting in Postman has demonstrated we don't need the boundary if we simply use the content type type=\"application/xop xml\", BUT back in C#, if we use this value for the content type, we are back to:
Message: System.ServiceModel.FaultException : These policy alternatives can not be satisfied: {http://schemas.xmlsoap.org/ws/2004/09/policy/optimizedmimeserialization}OptimizedMimeSerialization
uj5u.com熱心網友回復:
我們終于得到了這個作業!
我們創建了一個值物件來捕獲有關檔案的資訊:
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 成功發送的請求。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/318709.html
標籤:c# web-services soap mtom praxedo
上一篇:Weblogic14c-webservice-description-name在weblogic-webservices中不是唯一的
下一篇:接受動態網址時,基本布局無法加載
