在c#控制台应用程序中使用WCF皇家邮政API
本文关键字:皇家 WCF API 控制台 应用程序 | 更新日期: 2023-09-27 18:28:25
我试图在我的C#控制台应用程序中使用皇家邮政运输的API,但我被卡住了。当我调用API时,它会显示"无效请求"。。
这就是我到目前为止所做的
RoyalMailMessage.cs
class RoyalMailMessage : Message
{
private readonly Message message;
public RoyalMailMessage(Message message)
{
this.message = message;
}
public override MessageHeaders Headers
{
get
{
return this.message.Headers;
}
}
public override MessageProperties Properties
{
get
{
return this.message.Properties;
}
}
public override MessageVersion Version
{
get
{
return this.message.Version;
}
}
protected override void OnWriteStartBody(XmlDictionaryWriter writer)
{
writer.WriteStartElement("Body", "http://schemas.xmlsoap.org/soap/envelope/");
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
this.message.WriteBodyContents(writer);
}
protected override void OnWriteStartEnvelope(XmlDictionaryWriter writer)
{
writer.WriteStartElement("s", "Envelope", "http://schemas.xmlsoap.org/soap/envelope/");
writer.WriteAttributeString("xmlns", "v2", null, "http://www.royalmailgroup.com/api/ship/V2");
writer.WriteAttributeString("xmlns", "v1", null, "http://www.royalmailgroup.com/integration/core/V1");
writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance");
writer.WriteAttributeString("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema");
writer.WriteAttributeString("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema");
}
}
RoyalMailMessageFormatter.cs
public class RoyalMailMessageFormatter : IClientMessageFormatter
{
private readonly IClientMessageFormatter formatter;
public RoyalMailMessageFormatter(IClientMessageFormatter formatter)
{
this.formatter = formatter;
}
public object DeserializeReply(Message message, object[] parameters)
{
return this.formatter.DeserializeReply(message, parameters);
}
public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
{
var message = this.formatter.SerializeRequest(messageVersion, parameters);
return new RoyalMailMessage(message);
}
}
RoyalMailIEndpointBehavior.cs
class RoyalMailIEndpointBehavior : IOperationBehavior
{
public RoyalMailIEndpointBehavior() { }
public void ApplyClientBehavior(OperationDescription description, ClientOperation proxy)
{
IClientMessageFormatter currentFormatter = proxy.Formatter;
proxy.Formatter = new RoyalMailMessageFormatter(currentFormatter);
}
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
}
public void Validate(OperationDescription operationDescription)
{
}
}
程序.cs
class Program
{
static void Main(string[] args)
{
try
{
using (var shippingService = new shippingAPIPortTypeClient())
{
shippingService.ClientCredentials.UserName.UserName = "xxxx";
shippingService.ClientCredentials.UserName.Password = "xxxxx";
foreach (OperationDescription od in shippingService.Endpoint.Contract.Operations)
{
od.Behaviors.Add(new RoyalMailIEndpointBehavior());
}
var createShipment = new createShipmentRequest()
{
integrationHeader = new integrationHeader()
{
dateTime = DateTime.Now,
dateTimeSpecified = true,
debugFlag = false,
debugFlagSpecified = false,
identification = new identificationStructure()
{
applicationId = "xxxx",
endUserId = "Sandra",
intermediaryId = "null",
transactionId = "123456789"
},
performanceFlag = false,
performanceFlagSpecified = false,
testFlag = false,
testFlagSpecified = false,
version = 1,
versionSpecified = false
},
requestedShipment = new requestedShipment()
{
bfpoFormat = new bFPOFormatType()
{
bFPOFormatCode = null,
},
customerReference = "",
departmentReference = "",
}
};
shippingService.createShipment(null, createShipment);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
应用程序配置
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="shippingAPISoapBinding">
<security mode="Transport">
<transport clientCredentialType="Certificate"></transport>
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://api.royalmail.com/shipping/onboarding" binding="basicHttpBinding"
bindingConfiguration="shippingAPISoapBinding" contract="ShippingService.shippingAPIPortType"
name="shippingAPIPort" behaviorConfiguration="CustomBehavior" />
</client>
<behaviors>
<endpointBehaviors>
<behavior name="CustomBehavior">
<clientCredentials>
<clientCertificate findValue="RM10001815" x509FindType="FindBySubjectName"
storeLocation="CurrentUser" storeName="My" />
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
现在,当我调用API时,它会显示"无效请求"。。我不确定我是否遗漏了什么,可能是在Soap Envelop标头中添加凭据,如下所示?
<soapenv:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>xxxx</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">xxxx</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">xWstjXG0iUxbv3NH/fX+kw==</wsse:Nonce>
<wsu:Created>2014-08-16T15:29:42</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
首先,您丢失了已经识别的安全标头,以及createShipment请求中的一大堆其他字段,如地址、服务代码等。我强烈建议使用fiddler来捕获您的SOAP请求和响应,它们会让您更深入地了解正在发生的事情。您还可以将您生成的请求与皇家邮件入职提供的示例请求进行比较。
查看您的代码,您没有附加安全令牌(wsse),它需要对您发出的每个请求都是唯一的(即nonce令牌)。您还缺少createShipemt请求所需的各种其他字段,如地址、服务代码和类型等。
我必须将证书和密钥附加到请求中才能使其正常工作。以下是我为实现这一目标而创建的一些代码片段,它不是一个复制粘贴解决方案,但比您在皇家邮政和C#方面发现的任何其他解决方案都要好,应该会为您指明正确的方向。
请注意,我有一个配置类,它从sqlite数据库加载了很多设置(未发布)。createShipment请求的值来自一个表单(未发布),该表单预先填充了数据,但允许我们仓库中的用户进行相应的更改和调整。您已经使用了我在post中的自定义消息格式化程序示例(C#WCF(Royal Mail SOAP API)Declare Namespace In Header)来处理命名空间问题。皇家邮政API在C#中不容易实现,我花了将近2天的时间才得到有效的请求和响应,正如我所说,你真的需要捕获请求和响应来了解发生了什么。
private X509Certificate2 certificate;
private Config config;
public RoyalMail() {
// Load The Config
config = new Config();
config.loadConfig();
// Load The SSL Certificate (Check The File Exists)
String certificatePath = (Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + @"'" + config.GetCertificateName());
if (!System.IO.File.Exists(certificatePath))
{
throw new Exception(@"The Royal Mail Certificate Is Missing From The Plugins Directory. Please Place The File " + config.GetCertificateName() + " In The Same Directory As The Plugin DLL File & Relaunch FileMaker.'n'n" + certificatePath);
}
certificate = new X509Certificate2(certificatePath, config.GetCertificatePassword());
// Check It's In The Certificate
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);
if (!store.Certificates.Contains(certificate))
{
store.Add(certificate);
MessageBox.Show("Certificate Was Installed Into Computer Trust Store");
}
store.Close();
}
/*
*
* SOAP Service & Methods
*
*/
private shippingAPIPortTypeClient GetProxy()
{
BasicHttpBinding myBinding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
myBinding.MaxReceivedMessageSize = 2147483647;
myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
shippingClient = new shippingAPIPortTypeClient(myBinding, new EndpointAddress(new Uri(config.GetEndpointURL()), EndpointIdentity.CreateDnsIdentity("api.royalmail.com"), new AddressHeaderCollection()));
shippingClient.ClientCredentials.ClientCertificate.Certificate = certificate;
foreach (OperationDescription od in shippingClient.Endpoint.Contract.Operations)
{
od.Behaviors.Add(new RoyalMailIEndpointBehavior());
}
return shippingClient;
}
private SecurityHeaderType GetSecurityHeaderType()
{
SecurityHeaderType securityHeader = new SecurityHeaderType();
DateTime created = DateTime.Now;
string creationDate;
creationDate = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");
string nonce = nonce = (new Random().Next(0, int.MaxValue)).ToString();
byte[] hashedPassword;
hashedPassword = GetSHA1(config.GetPassword());
string concatednatedDigestInput = string.Concat(nonce, creationDate, Encoding.Default.GetString(hashedPassword));
byte[] digest;
digest = GetSHA1(concatednatedDigestInput);
string passwordDigest;
passwordDigest = Convert.ToBase64String(digest);
string encodedNonce;
encodedNonce = Convert.ToBase64String(Encoding.Default.GetBytes(nonce));
XmlDocument doc = new XmlDocument();
using (XmlWriter writer = doc.CreateNavigator().AppendChild())
{
writer.WriteStartDocument();
writer.WriteStartElement("Security");
writer.WriteStartElement("UsernameToken", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
writer.WriteElementString("Username", config.GetUsername());
writer.WriteElementString("Password", passwordDigest);
writer.WriteElementString("Nonce", encodedNonce);
writer.WriteElementString("Created", creationDate);
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndDocument();
writer.Flush();
}
doc.DocumentElement.RemoveAllAttributes();
System.Xml.XmlElement[] headers = doc.DocumentElement.ChildNodes.Cast<XmlElement>().ToArray<XmlElement>();
securityHeader.Any = headers;
return securityHeader;
}
private integrationHeader GetIntegrationHeader()
{
integrationHeader header = new integrationHeader();
DateTime created = DateTime.Now;
String createdAt = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");
header.dateTime = created;
header.version = Int32.Parse(config.GetVersion());
header.dateTimeSpecified = true;
header.versionSpecified = true;
identificationStructure idStructure = new identificationStructure();
idStructure.applicationId = config.GetApplicationID();
string nonce = nonce = (new Random().Next(0, int.MaxValue)).ToString();
idStructure.transactionId = CalculateMD5Hash(nonce + createdAt);
header.identification = idStructure;
return header;
}
private static byte[] GetSHA1(string input)
{
return SHA1Managed.Create().ComputeHash(Encoding.Default.GetBytes(input));
}
public string CalculateMD5Hash(string input)
{
// step 1, calculate MD5 hash from input
MD5 md5 = System.Security.Cryptography.MD5.Create();
byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
byte[] hash = md5.ComputeHash(inputBytes);
// step 2, convert byte array to hex string
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
sb.Append(hash[i].ToString("X2"));
}
return sb.ToString();
}
/*
* Check Response Footer For Errors & Warnings From Service
* If Error Return True So We Can Inform Filemaker Of Error
* Ignore Warnings For Now
*
*/
private bool checkErrorsAndWarnings(integrationFooter integrationFooter)
{
if (integrationFooter != null)
{
if (integrationFooter.errors != null && integrationFooter.errors.Length > 0)
{
errorDetail[] errors = integrationFooter.errors;
for (int i = 0; i < errors.Length; i++)
{
errorDetail error = errors[i];
MessageBox.Show("Royal Mail Request Error: " + error.errorDescription + ". " + error.errorResolution, "Royal Mail Request Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
}
if (errors.Length > 0)
{
return true;
}
}
if (integrationFooter.warnings != null && integrationFooter.warnings.Length > 0)
{
warningDetail[] warnings = integrationFooter.warnings;
for (int i = 0; i < warnings.Length; i++)
{
warningDetail warning = warnings[i];
//MessageBox.Show("Royal Mail Request Warning: " + warning.warningDescription + ". " + warning.warningResolution, "Royal Mail Request Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1);
}
}
}
return false;
}
/*
* Show Message Box With SOAP Error If We Receive A Fault Code Back From Service
*
*/
private void showSoapException(FaultException e)
{
MessageFault message = e.CreateMessageFault();
XmlElement errorDetail = message.GetDetail<XmlElement>();
XmlNodeList errorDetails = errorDetail.ChildNodes;
String fullErrorDetails = "";
for (int i = 0; i < errorDetails.Count; i++)
{
fullErrorDetails += errorDetails.Item(i).Name + ": " + errorDetails.Item(i).InnerText + "'n";
}
MessageBox.Show("An Error Occured With Royal Mail Service: " + message.Reason.ToString() + "'n'n" + fullErrorDetails, "Royal Mail SOAP Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
}
public createShipmentResponse SendCreateShipmentRequest(CreateShipmentForm shippingForm)
{
shippingAPIPortTypeClient client = GetProxy();
try
{
createShipmentRequest request = new createShipmentRequest();
request.integrationHeader = GetIntegrationHeader();
requestedShipment shipment = new requestedShipment();
// Shipment Type Code (Delivery or Return)
referenceDataType shipmentType = new referenceDataType();
shipmentType.code = shippingForm.GetShippingType();
shipment.shipmentType = shipmentType;
// Service Occurence (Identifies Agreement on Customers Account) Default to 1. Not Required If There Is There Is Only 1 On Account
shipment.serviceOccurrence = config.GetServiceOccurance();
// Service Type Code (1:24H 1st Class, 2: 48H 2nd Class, D: Special Delivery Guaranteed, H: HM Forces (BFPO), I: International, R: Tracked Returns, T: Tracked Domestic)
referenceDataType serviceType = new referenceDataType();
serviceType.code = shippingForm.GetServiceType().GetServiceTypeCode();
shipment.serviceType = serviceType;
// Service Offering (See Royal Mail Service Offering Type Codes. Too Many To List)
serviceOfferingType serviceOfferingTypeContainer = new serviceOfferingType();
referenceDataType serviceOffering = new referenceDataType();
serviceOffering.code = shippingForm.GetServiceOffering().GetCode();
serviceOfferingTypeContainer.serviceOfferingCode = serviceOffering;
shipment.serviceOffering = serviceOfferingTypeContainer;
// Service Format Code
serviceFormatType serviceFormatTypeContainer = new serviceFormatType();
referenceDataType serviceFormat = new referenceDataType();
serviceFormat.code = shippingForm.GetServiceFormat().GetFormat();
serviceFormatTypeContainer.serviceFormatCode = serviceFormat;
shipment.serviceFormat = serviceFormatTypeContainer;
// Shipping Date
shipment.shippingDate = shippingForm.GetShippingDate();
shipment.shippingDateSpecified = true;
// Signature Required (Only Available On Tracked Services)
if (shippingForm.IsSignatureRequired())
{
shipment.signature = true;
}
else
{
shipment.signature = false;
// Leave In Safe Place (Available On Tracked Non Signature Service Offerings)
shipment.safePlace = shippingForm.GetSafePlaceText();
}
shipment.signatureSpecified = true;
// Sender Reference Number (e.g. Invoice Number or RA Number)
shipment.senderReference = shippingForm.GetInvoiceNumber();
/*
* Service Enhancements
*/
List<serviceEnhancementType> serviceEnhancements = new List<serviceEnhancementType>();
List<dataObjects.ServiceEnhancement> selectedEnhancements = shippingForm.GetServiceEnhancements();
for (int i = 0; i < selectedEnhancements.Count; i++)
{
serviceEnhancementType enhancement = new serviceEnhancementType();
referenceDataType enhancementCode = new referenceDataType();
enhancementCode.code = selectedEnhancements.ElementAt(i).GetEnhancementType().ToString();
enhancement.serviceEnhancementCode = enhancementCode;
serviceEnhancements.Add(enhancement);
}
shipment.serviceEnhancements = serviceEnhancements.ToArray();
/*
* Recipient Contact Details
*/
contact recipientContact = new contact();
recipientContact.complementaryName = shippingForm.GetCompany();
recipientContact.name = shippingForm.GetName();
if(!shippingForm.GetEmailAddress().Equals("")) {
digitalAddress email = new digitalAddress();
email.electronicAddress = shippingForm.GetEmailAddress();
recipientContact.electronicAddress = email;
}
if(!shippingForm.GetMobileNumber().Equals("")) {
telephoneNumber tel = new telephoneNumber();
Regex phoneRegex = new Regex(@"[^'d]");
tel.telephoneNumber1 = phoneRegex.Replace(shippingForm.GetMobileNumber(), "");
tel.countryCode = "00" + shippingForm.GetCountry().GetDialingCode();
recipientContact.telephoneNumber = tel;
}
shipment.recipientContact = recipientContact;
/*
* Recipient Address
*
*/
address recipientAddress = new address();
recipientAddress.addressLine1 = shippingForm.GetAddressLine1();
recipientAddress.addressLine2 = shippingForm.GetAddressLine2();
recipientAddress.addressLine3 = shippingForm.GetAddressLine3();
recipientAddress.addressLine4 = shippingForm.GetCounty();
recipientAddress.postTown = shippingForm.GetTown();
countryType country = new countryType();
referenceDataType countryCode = new referenceDataType();
countryCode.code = shippingForm.GetCountry().getCountryCode();
country.countryCode = countryCode;
recipientAddress.country = country;
recipientAddress.postcode = shippingForm.GetPostCode();
recipientAddress.stateOrProvince = new stateOrProvinceType();
recipientAddress.stateOrProvince.stateOrProvinceCode = new referenceDataType();
shipment.recipientAddress = recipientAddress;
// Shipment Items
List<RoyalMailAPI.RoyalMailShippingAPI.item> items = new List<RoyalMailAPI.RoyalMailShippingAPI.item> ();
foreach(dataObjects.Item i in shippingForm.GetItems()) {
RoyalMailAPI.RoyalMailShippingAPI.item item = new RoyalMailAPI.RoyalMailShippingAPI.item();
item.numberOfItems = i.GetQty().ToString();
item.weight = new dimension();
item.weight.value = (float) (i.GetWeight() * 1000);
item.weight.unitOfMeasure = new unitOfMeasureType();
item.weight.unitOfMeasure.unitOfMeasureCode = new referenceDataType();
item.weight.unitOfMeasure.unitOfMeasureCode.code = "g";
items.Add(item);
}
if (shippingForm.GetServiceType().GetDescription().ToLower().Contains("international"))
{
internationalInfo InternationalInfo = new internationalInfo();
InternationalInfo.shipperExporterVatNo = "GB945777273";
InternationalInfo.documentsOnly = false;
InternationalInfo.shipmentDescription = "Invoice Number: " + shippingForm.GetInvoiceNumber();
InternationalInfo.invoiceDate = DateTime.Now;
InternationalInfo.termsOfDelivery = "EXW";
InternationalInfo.invoiceDateSpecified = true;
InternationalInfo.purchaseOrderRef = shippingForm.GetInvoiceNumber();
List<RoyalMailShippingAPI.parcel> parcels = new List<parcel>();
foreach (dataObjects.Item i in shippingForm.GetItems())
{
parcel Parcel = new parcel();
Parcel.weight = new dimension();
Parcel.weight.value = (float)(i.GetWeight() * 1000);
Parcel.weight.unitOfMeasure = new unitOfMeasureType();
Parcel.weight.unitOfMeasure.unitOfMeasureCode = new referenceDataType();
Parcel.weight.unitOfMeasure.unitOfMeasureCode.code = "g";
Parcel.invoiceNumber = shippingForm.GetInvoiceNumber();
Parcel.purposeOfShipment = new referenceDataType();
Parcel.purposeOfShipment.code = "31";
List<contentDetail> Contents = new List<contentDetail>();
foreach (RoyalMailAPI.dataObjects.ProductDetail product in i.GetProducts())
{
contentDetail ContentDetail = new contentDetail();
ContentDetail.articleReference = product.Sku;
ContentDetail.countryOfManufacture = new countryType();
ContentDetail.countryOfManufacture.countryCode = new referenceDataType();
ContentDetail.countryOfManufacture.countryCode.code = product.CountryOfManufacture;
ContentDetail.currencyCode = new referenceDataType();
ContentDetail.currencyCode.code = product.CurrencyCode;
ContentDetail.description = product.Name;
ContentDetail.unitQuantity = product.Qty.ToString();
ContentDetail.unitValue = Convert.ToDecimal(product.Price);
ContentDetail.unitWeight = new dimension();
ContentDetail.unitWeight.value = Convert.ToSingle(product.Weight * 1000);
ContentDetail.unitWeight.unitOfMeasure = new unitOfMeasureType();
ContentDetail.unitWeight.unitOfMeasure.unitOfMeasureCode = new referenceDataType();
ContentDetail.unitWeight.unitOfMeasure.unitOfMeasureCode.code = "g";
Contents.Add(ContentDetail);
}
//Parcel.contentDetails = Contents.ToArray();
parcels.Add(Parcel);
}
InternationalInfo.parcels = parcels.ToArray();
shipment.internationalInfo = InternationalInfo;
}
else
{
shipment.items = items.ToArray();
}
request.requestedShipment = shipment;
createShipmentResponse response = client.createShipment(GetSecurityHeaderType(), request);
// Show Errors And Warnings
checkErrorsAndWarnings(response.integrationFooter);
return response;
}
catch (TimeoutException e)
{
client.Abort();
MessageBox.Show("Request Timed Out: " + e.Message, "Request Timeout", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
}
catch (FaultException e)
{
client.Abort();
showSoapException(e);
}
catch (CommunicationException e)
{
client.Abort();
MessageBox.Show("A communication error has occured: " + e.Message + " - " + e.StackTrace, "Communication Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
}
catch (Exception e)
{
client.Abort();
MessageBox.Show(e.Message, "Royal Mail Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
}
return null;
}