WCF Webinvoke POST giving (400) Bad Request for specific server - c#

Good morning/evening,
I am new to WCF and have created a sample application. The problem is I am passing a json string as a request but getting 400:Bad request error. The details of my sample is given below:
ISampleService.cs:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
namespace SampleWCF
{
[ServiceContract]
public interface ISampleService
{
[OperationContract]
[WebInvoke(UriTemplate = "/folder_entries/{mFileID_param}/shares?notify=true", Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
string AddShareToFileNotify(string mFileID_param, string rqst_param);
}
}
#region TestSample
[DataContract]
public class TestSample
{
public TestSample() { }
[DataMember(Name = "recipient")]
public Recipient Recipient { get; set; }
[DataMember(Name = "role")]
public String Role { get; set; }
[DataMember(Name = "access")]
public TestAccess access{ get; set; }
[DataMember(Name = "can_share")]
public bool CanShare { get; set; }
[DataMember(Name = "days_to_expire")]
public int DaysToExpire { get; set; }
}
#region TestAccess
[DataContract]
public class TestAccess
{
#region Attributes
[DataMember(Name = "role")]
public String Role { get; set; }
[DataMember(Name = "rights")]
public AccessRights AccessRights { get; set; }
#endregion
#region Constructor
public TestAccess () { }
#endregion
}
#endregion
#region rights
[DataContract]
public class AccessRights
{
public AccessRights() { }
[DataMember(Name = "testinternal")]
public Boolean Internal { get; set; }
[DataMember(Name = "testexternal")]
public Boolean External { get; set; }
[DataMember(Name = "public")]
public Boolean Public { get; set; }
[DataMember(Name = "max_role")]
public String Max_Role { get; set; }
[DataMember(Name = "grant")]
public Boolean Grant { get; set; }
}
#endregion
#region Recipient
[DataContract]
public class Recipient
{
public Recipient() { }
[DataMember(Name = "id")]
public string ID { get; set; }
[DataMember(Name = "type")]
public string Type { get; set; }
}
#endregion
#endregion
SampleService.svc.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.ServiceModel.Web;
using System.ServiceModel.Security;
using System.Net;
using System.IO;
using System.Threading;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
namespace SampleWCF
{
public class SampleService : ISampleService
{
private ISampleService client = null;
private WebChannelFactory<ISampleService> cf = null;
private Uri uri = null;
private WebHttpSecurityMode mode = WebHttpSecurityMode.Transport;
public const string CERTIFICATE_TRUST_STORE_NAME = "Trust";
//Method to Validate if the server certificate is valid or not
private static bool ValidateServerCertificate(object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
bool result = false;
X509Store store = null;
try
{
// If the certificate is valid signed certificate, return true.
if (SslPolicyErrors.None == sslPolicyErrors)
{
return true;
}
// If there are errors in the certificate chain, look in the certificate store to check
// if the user has already trusted the certificate or not.
if ((0 != (sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors)) ||
(0 != (sslPolicyErrors & SslPolicyErrors.RemoteCertificateNameMismatch)))
{
store = new X509Store(CERTIFICATE_TRUST_STORE_NAME, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
result = store.Certificates.Contains(certificate);
}
}
catch (Exception ex)
{
Console.WriteLine("Could not validate certificate!");
result = false;
}
finally
{
if (store != null)
store.Close();
}
return result;
}
public ISampleService initClient(string servername,
string protocol,
string username,
string password)
{
uri = new Uri(protocol + "://" + servername + ":" + #"/rest");
WebHttpBinding binding = new WebHttpBinding();
binding.ReaderQuotas.MaxStringContentLength = int.MaxValue;
binding.MaxReceivedMessageSize = int.MaxValue;
binding.ReceiveTimeout = TimeSpan.FromMinutes(10.0);
binding.SendTimeout = TimeSpan.FromMinutes(10.0);
System.Net.ServicePointManager.DefaultConnectionLimit = 200;
binding.Security.Mode = mode;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
cf = new WebChannelFactory<ISampleService>(binding, uri);
cf.Credentials.UserName.UserName = username;
cf.Credentials.UserName.Password = password;
client = cf.CreateChannel();
System.Net.ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertificate;
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
Thread.Sleep(500);
return client;
}
public string AddShareToFileNotify(string mFileID_param, string rqst_param)
{
using (new OperationContextScope((IContextChannel)client))
{
string rsp = null;
try
{
rsp = client.AddShareToFileNotify(mFileID_param, rqst_param);
}
catch (Exception ce)
{
Console.WriteLine("Exception found!{0}",ce);
return rsp;
}
return rsp;
}
}
}
}
Main Calling function:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TriggerMain
{
class Program
{
static void Main(string[] args)
{
string mFileID = "xxxxxx";
string rqst = "{"
+"\"access\":{"
+"\"role\":\"VIEWER\","
+"\"sharing\":{"
+"\"external\":false,"
+"\"grant\":false,"
+"\"internal\":false,"
+"\"max_role\":null,"
+"\"public\":false"
+"}"
+"},"
+"\"can_share\": false,"
+"\"days_to_expire\": 30,"
+"\"recipient\": {"
+"\"id\": <yyyyyy>,"
+"\"type\": \"user\""
+"},"
+"\"role\": \"VIEWER\""
+"}";
string rsp = null;
SampleWCF.SampleService sample = new SampleWCF.SampleService();
sample.initClient("<URL1.xxx.com>", "https", "<Username>", "<Password>");
rsp = sample.AddShareToFileNotify(mFileID, rqst);
Console.ReadLine();
}
}
}
While running the application I am getting the following error:
Exception found!System.ServiceModel.ProtocolException: The remote server returned an unexpected response: (400) Bad Request. ---> System.Net.WebException: The remote server returned an error: (400) Bad Request.
at System.Net.HttpWebRequest.GetResponse()
at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)
--- End of inner exception stack trace ---
Server stack trace:
at System.ServiceModel.Channels.HttpChannelUtilities.ValidateRequestReplyResponse(HttpWebRequest request, HttpWebResponse response, HttpChannelFactory`1 factory, WebException responseException, ChannelBinding channelBinding)
at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)
at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Dispatcher.RequestChannelBinder.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at SampleWCF.ISampleService.AddShareToFileNotify(String mFileID_param, String rqst_param)
at SampleWCF.SampleService.AddShareToFileNotify(String mFileID_param, String rqst_param) in c:\Users\SBasu\Documents\Visual Studio 2013\Projects\SampleWCF\SampleWCF\SampleService.svc.cs:line 103
What I have tried : I have changed the timeout for send and receive, the content type is application/json. The request is throwing error for only this server. I have another server in which I have tried and the POST is passing in the server. Both the servers have the same configuration. When I run Fiddler for the erroneous server the POST call succeeds. Sending the exact same request from POSTMAN to the erroneous server gives success (200 OK) status and I am getting proper response in both of these cases.
Note: WEBGET, WEBInvoke DELETE are working fine for the server. Only WEBInvoke POST is not working for the specific server. Can anybody help me regarding this? Thanks in advance.

This part of the payload doesn't look right to me. I changed 1 line, see below.
The only addition was to add quotation marks around the recipient.id value
string rqst = "{"
+"\"access\":{"
+"\"role\":\"VIEWER\","
+"\"sharing\":{"
+"\"external\":false,"
+"\"grant\":false,"
+"\"internal\":false,"
+"\"max_role\":null,"
+"\"public\":false"
+"}"
+"},"
+"\"can_share\": false,"
+"\"days_to_expire\": 30,"
+"\"recipient\": {"
+"\"id\": \"<yyyyyy>\"," // this line I think was wrong. added quotes around the value
+"\"type\": \"user\""
+"},"
+"\"role\": \"VIEWER\""
+"}";

Why do you call the WCF Restful service by using a proxy (channel factory)? If indeed, we should use the service base address instead of the POST URL. In addition, the service contract should be the same as the server.
uri = new Uri(protocol + "://" + servername + ":" + #"/rest") // where is the service port number? also, is the format right?
This code snippet should use a service base address to send a request by a proxy.
In fact, we usually send a request by POSTMan/fiddler while calling the WCF service created by Webhttpbinding. Moreover, we should use the Uri decorated by URITemplate attribute.
Feel free to let me know if the problem still exists.

Related

Azure Log Analytics Workspace Request Forbidden

I am trying to send logs from my application to an Azure Log Analytics Workspace, in order to do that I develop the following code based on what I found in https://learn.microsoft.com/en-us/azure/azure-monitor/logs/data-collector-api
using maintenance.messaging;
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace maintenance.dataaccessobjects
{
public class LogAnalyticsWorkspaceDAO
{
private static LogAnalyticsWorkspaceDAO _Instance { get; set; }
private String WorkspaceId { get; set; } = AzureKeyVaultDAO.Instance.GetSecret("WorkspaceId"); //Get WorkspaceId from KeyVault
private String SharedKey { get; set; } = AzureKeyVaultDAO.Instance.GetSecret("SharedKey"); //Get SharedKey from KeyVault
private String ApiVersion { get; set; } = AzureKeyVaultDAO.Instance.GetSecret("LAWApiVersion"); //Get API Version from KeyVault 2016-04-01
private String LogType { get; set; } = AzureKeyVaultDAO.Instance.GetSecret("LogType"); //Get LogType from KeyVault ApplicationLog
private LogAnalyticsWorkspaceDAO()
{
}
public static LogAnalyticsWorkspaceDAO Instance
{
get
{
if (_Instance == null)
{
_Instance = new LogAnalyticsWorkspaceDAO();
}
return _Instance;
}
}
private string GetSignature(String Method, Int32 ContentLength, String ContentType, DateTime Date, String Resource)
{
string Message = $"{Method}\n{ContentLength}\n{ContentType}\nx-ms-date:{Date}\n{Resource}";
byte[] Bytes = Encoding.UTF8.GetBytes(Message);
HMACSHA256 Encryptor = new HMACSHA256(Convert.FromBase64String(SharedKey));
return $"SharedKey {WorkspaceId}:{Convert.ToBase64String(Encryptor.ComputeHash(Bytes))}";
}
public async Task<String> Post(String Message)
{
DateTime Date = DateTime.UtcNow;
Dictionary<String, String> Headers = new Dictionary<String, String>();
MessageSender MessageSender = new MessageSender(new Uri($"https://{WorkspaceId}.ods.opinsights.azure.com/api/logs?api-version={ApiVersion}"));
Headers.Add("Method", "POST");
Headers.Add("Log-Type", LogType);
Headers.Add("x-ms-date", Date.ToString("r"));
Headers.Add("Authorization", GetSignature("POST", Message.Length, "application/json", Date, "/api/logs"));
return await MessageSender.Post(MessageSender.Message(Headers, Message));
}
}
}
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace maintenance.messaging
{
public class MessageSender : IDisposable
{
private readonly HttpClient Client;
private Uri Url { get; set; }
public MessageSender(Uri Url)
{
this.Client = new HttpClient();
this.Url = Url;
}
public HttpRequestMessage Message(Dictionary<String, String> Headers, String Message)
{
HttpRequestMessage Request = new HttpRequestMessage(HttpMethod.Post, this.Url);
Request.Content = new StringContent(Message, Encoding.UTF8, "application/json");
foreach (KeyValuePair<String, String> Header in Headers)
{
Request.Headers.Add(Header.Key, Header.Value);
}
return Request;
}
public async Task<String> Post(HttpRequestMessage Request)
{
HttpResponseMessage Response = await Client.SendAsync(Request);
Response.EnsureSuccessStatusCode();
return await Response.Content.ReadAsStringAsync();
}
public void Dispose()
{
Client?.Dispose();
}
}
}
However I always fall under a 403 Forbiden, I guess the error should be in the Authorization header (Signature generation)
Do you know what I am missing? I tried looking for other signature generations but didn't find anything new
I may be wrong, but as far as I can see SharedKey is not Base64 encoded, so I just try with
HMACSHA256 Encryptor = new HMACSHA256(Encoding.UTF8.GetBytes(SharedKey));
But get the same error 403 Forbidden
However I always fall under a 403 Forbiden, I guess the error should
be in the Authorization header (Signature generation) Do you know what
I am missing? I tried looking for other signature generations but
didn't find anything new.
Yes you are correct #delucaezequiel, This error is caused due to InvalidAuthorization ,Make sure that you have added the correct value of workspace ID and connection key which are valid.
For the encoded part to configure in code please Refer this SO THREAD as suggested by #GreenRock.
For more information please refer this Blog.

C# application getting disconnected from local api, usure how to reconnect without restarting the program

Hi everyone I'm new to c# and I've done my first 2 weeks in this language, so my knowlege is pretty basic.
I'm playing with an app that connects to a client (League of legends client) and uses varius methods to send and get info (Get, Post, Put and Delete).
What the program does:
Once the app is started there's a public class that is called when the form is loaded.
public LCU lcu = new LCU(); (I'll add the code of LCU down below) <-- this strats the connection
I can send as many requests as I want, here's a working example:
var request = await lcu.http_client.DeleteAsync(lcu.baseURL + "/lol-lobby/v2/lobby").ConfigureAwait(true);
My problem is that when I make too many requests (every 2 seconds or below), the app is disconnected from the client/api and to fix that I would need a task that reconnects.
Now I'm not sure how to do that, I've tried adding LCU lcu = new LCU(); inside a timer, but that didn't work.
I would love to know why it didn't work and if you have some suggestions on how to do it, I'll be happy to know.
Thank you!!
LCU.cs (not the main form)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
namespace LeaguePW5
{
public class LCU
{
public string address { get; set; }
public int port { get; set; }
public string username { get; set; }
public string password { get; set; }
public string protocol { get; set; }
public string process_name { get; set; }
public int process_id { get; set; }
public string baseURL => string.Format("{0}://{1}:{2}", this.protocol, this.address, this.port);
public LCU()
{
Process[] process = Process.GetProcessesByName("LeagueClientUx");
if (process.Length != 0)
{
string lockFile;
using (FileStream stream = File.Open(Path.Combine(Path.GetDirectoryName(process[0].MainModule.FileName), "lockfile"), FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
lockFile = new StreamReader(stream).ReadToEnd();
}
string[] parameters = lockFile.Split(new string[] { ":" }, StringSplitOptions.None);
this.username = "riot";
this.address = "127.0.0.1";
this.process_name = parameters[0];
this.process_id = Convert.ToInt32(parameters[1]);
this.port = Convert.ToInt32(parameters[2]);
this.password = parameters[3];
this.protocol = parameters[4];
}
}
public HttpClient http_client
{
get
{
HttpClientHandler httpClientHandler = new HttpClientHandler();
httpClientHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
httpClientHandler.ServerCertificateCustomValidationCallback = ((HttpRequestMessage httpRequestMessage, X509Certificate2 cert, X509Chain cetChain, SslPolicyErrors policyErrors) => true);
return new HttpClient(httpClientHandler)
{
DefaultRequestHeaders =
{
Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes("riot:" + this.password)))
}
};
}
set
{
}
}
}
}
P.S. If you need the full code, I'll be happy to share it
You have a mistake in http_client initialization. It returns new instance every time you're making the request.
Per HttpClient documentation:
// HttpClient is intended to be instantiated once per application, rather than per-use.
Try this fix and you'll not be disconnected. (in addition I've applied naming policy for property, widely used by Microsoft in .NET)
private HttpClient _httpClient; // backing field
public HttpClient HttpClient
{
get
{
if (_httpClient == null) // create new instance only if still not created
{
HttpClientHandler httpClientHandler = new HttpClientHandler();
httpClientHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
httpClientHandler.ServerCertificateCustomValidationCallback = ((HttpRequestMessage httpRequestMessage, X509Certificate2 cert, X509Chain cetChain, SslPolicyErrors policyErrors) => true);
_httpClient = new HttpClient(httpClientHandler)
{
DefaultRequestHeaders =
{
Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes("riot:" + this.password)))
}
};
}
return _httpClient;
}
}
And usage
await lcu.HttpClient.DeleteAsync(lcu.baseURL + "/lol-lobby/v2/lobby").ConfigureAwait(false);
ConfigureAwait(true) is default. Use false or not use ConfigureAwait to avoid a redundant overhead.
Additionally you may derive LCU class from IDisposable and implement the interface because HttpClient is IDisposable. And call HttpClient.Dispose() in the disposing method. But it makes sense only if you create new LCU() class multiple times.

Getting results via WSDL - C#

I'm trying to add this Header class to my SOAP request but can't see how. The Header class was given to me as part of the implementation but there's no instructions on how to use it and I'm a bit stuck - I've not used WSDL and web services before. I'm sure the answer must be blindingly easy but I just can't see it.
Header Requirements
<soapenv:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-19" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>##USERNAME##</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">##PASSWORD##</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
Header class
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Channels;
using System.Web;
using System.Xml;
using System.Xml.Serialization;
namespace Consuming
{
public class SecurityHeader : MessageHeader
{
private readonly UsernameToken _usernameToken;
public SecurityHeader(string id, string username, string password)
{
_usernameToken = new UsernameToken(id, username, password);
}
public override string Name
{
get { return "Security"; }
}
public override string Namespace
{
get { return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; }
}
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
XmlSerializer serializer = new XmlSerializer(typeof(UsernameToken));
serializer.Serialize(writer, _usernameToken);
}
}
[XmlRoot(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
public class UsernameToken
{
public UsernameToken()
{
}
public UsernameToken(string id, string username, string password)
{
Id = id;
Username = username;
Password = new Password() { Value = password };
}
[XmlAttribute(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd")]
public string Id { get; set; }
[XmlElement]
public string Username { get; set; }
[XmlElement]
public Password Password { get; set; }
}
public class Password
{
public Password()
{
Type = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText";
}
[XmlAttribute]
public string Type { get; set; }
[XmlText]
public string Value { get; set; }
}
}
Code
var mySoapHeader = new SecurityHeader("ID","Username","password");
var client = new GroupWebServiceClient(); // Created from Add Web Reference
client.?????? = mySoapHeader;// I can't see how to add the Header to the request
var response = new groupNameListV1();
response = client.getAllDescendants("6335");//This needs the header - omitting gives "An error was discovered processing the <wsse:Security> header"
EDIT
I figured it out in the end, turns out it was pretty easy - Adding the solution in case anyone else finds it useful
using (new OperationContextScope(client.InnerChannel))
{
OperationContext.Current.OutgoingMessageHeaders.Add(
new SecurityHeader("ID", "USER", "PWD"));
var response = new groupNameListV1();
response = client.getAllDescendants("cng_so_6553");
//other code
}
Generally you need to add behavior extension.
Create a class that implements IClientMessageInspector. In the BeforeSendRequest method, add your custom header to the outgoing message. It might look something like this:
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
HttpRequestMessageProperty httpRequestMessage;
object httpRequestMessageObject;
if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject))
{
httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
if (string.IsNullOrEmpty(httpRequestMessage.Headers[USER_AGENT_HTTP_HEADER]))
{
httpRequestMessage.Headers[USER_AGENT_HTTP_HEADER] = this.m_userAgent;
}
}
else
{
httpRequestMessage = new HttpRequestMessageProperty();
httpRequestMessage.Headers.Add(USER_AGENT_HTTP_HEADER, this.m_userAgent);
request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);
}
return null;
}
Then create an endpoint behavior that applies the message inspector to the client runtime. You can apply the behavior via an attribute or via configuration using a behavior extension element.
Here is a example of how to add an HTTP user-agent header to all request messages. I used this in a few of my clients.
Is this what you had in mind?

Using ServiceStack and RabbitMQ to send a stream

I am attempting to send a stream using RabbitMQ and Servicestack (v1.0.41 using .NET Core).
My Request implements ServiceStack.Web.IRequiresRequestStream, and the stream property is set in the client, but when it gets to the server, the stream is NULL.
Complete Repo
Server Code:
using System;
using System.IO;
using System.Threading.Tasks;
using Funq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.DependencyInjection;
using ServiceStack;
using ServiceStack.Messaging;
using ServiceStack.RabbitMq;
using ServiceStack.Web;
namespace Server
{
class Program
{
public static void Main(string[] args)
{
IWebHost host = new WebHostBuilder()
.UseServer(new RabbitServer())
.UseStartup<Startup>()
.Build();
host.Run();
}
}
public class RabbitServer : IServer
{
public void Dispose(){}
public void Start<TContext>(IHttpApplication<TContext> application){}
public IFeatureCollection Features { get; } = new FeatureCollection();
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseServiceStack((AppHostBase)Activator.CreateInstance<AppHost>());
app.Run((RequestDelegate)(context => (Task)Task.FromResult<int>(0)));
}
}
public class AppHost : AppHostBase
{
public AppHost()
: base("My Test Service", typeof(MyService).GetAssembly())
{
}
public override void Configure(Container container)
{
var mqServer = new RabbitMqServer("127.0.0.1");
container.Register<IMessageService>(mqServer);
mqServer.RegisterHandler<HelloRequest>(ExecuteMessage);
mqServer.Start();
}
}
public class MyService : Service
{
public HelloResponse Any(HelloRequest request)
{
Console.WriteLine($"Stream is null: {request.RequestStream == null}");
return new HelloResponse { Counter = request.Counter };
}
}
public class HelloRequest : IReturn<HelloResponse>, IRequiresRequestStream
{
public int Counter { get; set; }
public Stream RequestStream { get; set; }
}
public class HelloResponse
{
public int Counter { get; set; }
}
}
Client Code:
using ServiceStack;
using ServiceStack.Messaging;
using ServiceStack.RabbitMq;
using ServiceStack.Web;
using System;
using System.IO;
using System.Text;
namespace Client
{
class Program
{
static void Main(string[] args)
{
RabbitMqServer messageService = new RabbitMqServer("127.0.0.1");
RabbitMqQueueClient mqClient = messageService.MessageFactory.CreateMessageQueueClient() as RabbitMqQueueClient;
var responseQueueName = mqClient.GetTempQueueName();
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes("Hello World!")) { Position = 0 };
HelloRequest request = new HelloRequest { Counter = 100, RequestStream = ms }; //Counter is just some arbitary extra data
Guid messageId = Guid.NewGuid();
mqClient.Publish(QueueNames<HelloRequest>.In, new Message<HelloRequest>(request) { ReplyTo = responseQueueName, Id = messageId });
}
}
public class HelloRequest : IReturn<HelloResponse>, IRequiresRequestStream
{
public int Counter { get; set; }
public Stream RequestStream { get; set; }
}
public class HelloResponse
{
public int Counter { get; set; }
}
}
Note: I realise I could just use a byte[] in my request object, but I would quite like to make use of the provided IRequiresRequestStream interface so I can switch back to using HTTP rather than AMQP in the future.
I should also say, that I probably won't be using the RabbitMQ Client provided by servicestack, as I am writing custom logic to convert from HTTP to AMQP, so I will be building the rabbitMQ request manually - the code above just demonstrates the problem I am having in the simplest way possible.
I'm going to assume that this won't just work out of the box with AMQP (as it does with HTTP) - so I was thinking that I need to do something like serialize the stream to a byte[] and include it in the RabbitMQ message and then populate the dto which ServiceStack magically re-hydrates on the Server.
So two questions really...
1. Am I on the right track?
2. If so, how do I hook into the de-serialization code on the server so that I have access to the raw RabbitMQ message in order to convert my byte[] back to a stream and set the stream on my dto?
You can't send a IRequiresRequestStream Request DTO into a MQ because it's not a normal serialized Request DTO, instead it instructs ServiceStack to skip deserializing the Request DTO and instead inject the HTTP Request Stream so the Service can perform its own Deserialization instead, this is different to a normal Request DTO which is serialized and can be sent as the body in an MQ Message.
One option if you want to share implementation between a IRequiresRequestStream Service and a Service that can be called by MQ is to just delegate to a common Service that accepts bytes, e.g:
//Called from HTTP
public object Any(HelloStream request) =>
Any(new HelloBytes { Bytes = request.RequestStream.ReadFully() });
//Called from HTTP or MQ
public object Any(HelloBytes request)
{
//= request.Bytes
}

No matching contract between WCF Service and QuickBooks Web Connector

I am trying to write a small SOAP server, which connects to the QuickBooks Web Connector, but I have some trouble to find the correct contracts. I always get following error:
Web Connector
Method x cannot be processed at the receiver, due to a ContractFilter
mismatch at the EndpointDispatcher. This may be because of either a
contract mismatch (mismatched Actions between sender and receiver) or
a binding/security mismatch between the sender and the receiver.
Check that sender and receiver have the same contract and the same
binding (including security requirements, e.g. Message, Transport,
None).
I created an empty ASP .NET Web Application and added a WCF Service. You will find here a snippet of the authenticate method:
WCF Service interface
[ServiceContract]
public interface IQuickBooks
{
[OperationContract]
AuthenticateResponse authenticate(Authenticate authenticateSoapIn);
}
WCF Service implementation
public class QuickBooks : IQuickBooks
{
public AuthenticateResponse authenticate(Authenticate authenticateSoapIn)
{
return new AuthenticateResponse
{
AuthenticateResult = new[] { "1", "none" }
};
}
}
Request
[DataContract(Name = "authenticate")]
public class Authenticate
{
[DataMember(Name = "strUserName", IsRequired = true)]
public string Username { get; set; }
[DataMember(Name = "strPassword", IsRequired = true)]
public string Password { get; set; }
}
Response
[DataContract(Name = "authenticateResponse")]
public class AuthenticateResponse
{
[DataMember(Name = "authenticateResult", IsRequired = true)]
public string[] AuthenticateResult { get; set; }
}
Here you can find the WSDL from QuickBooks and my WSDL output. Notice that I only implemented the authenticate method for testing. I guess the mismatching wsdl:types cause the error. In the original WSDL from QuickBooks the authenticate type has two primitive types for username and password.
How could I implement a WCF Service with QuickBooks Web Connector? What did I wrong?
Additional information
StackTrace
The message with Action 'http://developer.intuit.com/authenticate' cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions between sender and receiver) or a binding/security mismatch between the sender and the receiver. Check that sender and receiver have the same contract and the same binding (including security requirements, e.g. Message, Transport, None).
More info:
StackTrace = at System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message, WebResponse response, Stream responseStream, Boolean asyncCall)
at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters)
at QBWebConnector.localhost.WCWebServiceDoc.authenticate(String strUserName, String strPassword)
at QBWebConnector.localhost.WCWebService.authenticate(String strUserName, String strPassword)
at QBWebConnector.SOAPWebService.authenticate(String UserName, String Password)
at QBWebConnector.WebService.do_authenticate(String& ticket, String& companyFileName)
This answer describes how to connect a WCF Service with the QuickBooks Web Connecter (e. g. authenticate method). I am not totally sure if it is the best implementation, but it works and I would like to help other people with similar problems. Enchantments and additional suggestions are always welcome.
Create an empty ASP .NET Web Application
Add a WCF Service
Define the service contract
Implement the service behavior
Define the necessary data types
Create the service contract
[ServiceContract(Namespace = QuickBooks.URL, Name = "QuickBooks")]
public interface IQuickBooks
{
[OperationContract(Action = QuickBooks.URL + "authenticate")]
AuthenticateResponse authenticate(Authenticate authenticateSoapIn);
}
Create the service behavior
[ServiceBehavior(Namespace = QuickBooks.URL)]
public class QuickBooks : IQuickBooks
{
public const string URL = "http://developer.intuit.com/";
public AuthenticateResponse authenticate(Authenticate authenticateSoapIn)
{
// Check if authenticateSoapIn is valid
var authenticateResponse = new AuthenticateResponse();
authenticateResponse.AuthenticateResult.Add(System.Guid.NewGuid().ToString());
authenticateResponse.AuthenticateResult.Add(string.Empty);
return authenticateResponse;
}
}
Implement the request and response types
Request
[DataContract(Name = "authenticate")]
[MessageContract(WrapperName = "authenticate", IsWrapped = true)]
public class Authenticate
{
[DataMember(Name = "strUserName", IsRequired = true)]
[MessageBodyMember(Name = "strUserName", Order = 1)]
public string Username { get; set; }
[DataMember(Name = "strPassword", IsRequired = true)]
[MessageBodyMember(Name = "strPassword", Order = 2)]
public string Password { get; set; }
public Authenticate()
{
}
public Authenticate(string username, string password)
{
this.Username = username;
this.Password = password;
}
}
Response
[DataContract(Name = "authenticateResponse")]
[MessageContract(WrapperName = "authenticateResponse", IsWrapped = true)]
public class AuthenticateResponse
{
[DataMember(Name = "authenticateResult", IsRequired = true)]
[MessageBodyMember(Name = "authenticateResult", Order = 1)]
public ArrayOfString AuthenticateResult { get; set; }
public AuthenticateResponse()
{
this.AuthenticateResult = new ArrayOfString();
}
public AuthenticateResponse(ArrayOfString authenticateResult)
{
this.AuthenticateResult = authenticateResult;
}
}
ArrayOfString used in authenticateResponse
[CollectionDataContractAttribute(Name = "ArrayOfString", Namespace = QuickBooks.URL, ItemName = "string")]
public class ArrayOfString : List<string>
{
}
This scheme complies to the SOAP contract and allows the data exchange.

Categories

Resources