No matching contract between WCF Service and QuickBooks Web Connector - c#

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.

Related

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

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.

ServiceStack: How to unit test a service that serves files

I have a web service that service an Excel file
public class ReportService : Service
{
public IReportRepository Repository {get; set;}
public object Get(GetInvoiceReport request)
{
var invoices = Repository.GetInvoices();
ExcelReport report = new ExcelReport();
byte[] bytes = report.Generate(invoices);
return new FileResult(bytes);
}
}
and I setup the object that is retured from the service as
public class FileResult : IHasOptions, IStreamWriter, IDisposable
{
private readonly Stream _responseStream;
public IDictionary<string, string> Options { get; private set; }
public BinaryFileResult(byte[] data)
{
_responseStream = new MemoryStream(data);
Options = new Dictionary<string, string>
{
{"Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
{"Content-Disposition", "attachment; filename=\"InvoiceFile.xlsx\";"}
};
}
public void WriteTo(Stream responseStream)
{
if (_responseStream == null)
return;
using (_responseStream)
{
_responseStream.WriteTo(responseStream);
responseStream.Flush();
}
}
public void Dispose()
{
_responseStream.Close();
_responseStream.Dispose();
}
}
Now, the webservice works fine when tested through a browser; but it gives an error message when tested from a unit test. Below is the error message:
System.Runtime.Serialization.SerializationException : Type definitions
should start with a '{', expecting serialized type 'FileResult', got
string starting with:
PK\u0003\u0004\u0014\u0000\u0008\u0000\u0008\u0000�\u000b5K���%\u0001\u0000\u0000�\u0003\u0000\u0000\u0013\u0000\u0000\u0000[Content_Types].xml��
at
ServiceStack.Text.Common.DeserializeTypeRefJson.StringToType(TypeConfig
typeConfig, StringSegment strType, EmptyCtorDelegate ctorFn,
Dictionary2 typeAccessorMap) at
ServiceStack.Text.Common.DeserializeType1.<>c__DisplayClass2_0.b__1(StringSegment value) at ServiceStack.Text.Json.JsonReader1.Parse(StringSegment
value) at ServiceStack.Text.Json.JsonReader1.Parse(String value)
at ServiceStack.Text.JsonSerializer.DeserializeFromString[T](String
value) at
ServiceStack.Text.JsonSerializer.DeserializeFromStream[T](Stream
stream) at
ServiceStack.ServiceClientBase.GetResponse[TResponse](WebResponse
webResponse) at
ServiceStack.ServiceClientBase.Send[TResponse](String httpMethod,
String relativeOrAbsoluteUrl, Object request)
Below is the unit test I used to test the webservice:
[Test]
public void TestInvoiceReport()
{
var client = new JsonServiceClient("http://localhost/report/");
var authResponse = client.Send(new Authenticate
{
provider = CredentialsAuthProvider.Name,
UserName = "[User Name]",
Password = "[Password]"
});
var requestDTO = new GetInvoiceReport();
var ret = client.Get<FileResult>(requestDTO);
Assert.IsTrue(ret != null);
}
Edit:
I am including the definition for my request DTO class:
[Route("/invoices", "GET")]
public class GetInvoiceReport: IReturn<FileResult>
{
}
Any help is appreciated.
Note: if you're making a HTTP Request instead of calling the Service in code, it's an Integration Test instead of a Unit Test.
You haven't provided your GetInvoiceReport Request DTO definition, but if you're returning anything that's not a serialized DTO it should be specified it its IReturn<T> interface, e.g:
public class GetInvoiceReport : IReturn<byte[]> { ... }
Then you'll be able to download the raw bytes with:
byte[] response = client.Get(new GetInvoiceReport());
You can use the Service Clients Request Filters for inspecting the HTTP Response Headers.
I'd also recommend checking out ServiceStack's .NET Service Clients docs which contains extensive info for downloading raw Responses.

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?

Consume WCF Restful service with datacontract

I have created the following restfull web service:
Interface
[ServiceContract]
public interface ISIGService
{
[OperationContract]
[WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Xml,
BodyStyle = WebMessageBodyStyle.Bare,
UriTemplate = "GetTicket/")]
Ticket GetTicket(string user, string pwd);
}
Implementation
public class SIGService : ISIGService
{
public Ticket GetTicket(string user, string pwd)
{
return new Ticket()
{
Usuario = "xx",
UsuarioNombre = "xxx",
UsuarioId = "xxx"
};
}
Contract
[DataContract]
public class Ticket
{
[DataMember]
public int UsuarioId { get; set; }
[DataMember]
public string UsuarioNombre { get; set; }
[DataMember]
public string Usuario { get; set; }
}
I need to consume this service, from a web application, and get the typed object Ticket, I have included a service reference for this.
Server side code:
string urlService =
String.Format("http://localhost:22343/SIGService.svc/GetTicket/?user='{0}'&pwd='{1}'",
usuario, password);
var request = (HttpWebRequest)WebRequest.Create(urlService);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream stream = response.GetResponseStream();
StreamReader reader = new StreamReader(stream);
string text = reader.ReadToEnd();
I put a text variable just to get something, sort of lost here.
I don't seem to get this object, could you give some pointers on this?
Most likely, you just need to change your URL from
http://localhost:22343/SIGService.svc/GetTicket/?user='{0}'&pwd='{1}'
to using the proper REST syntax (since you're using a REST service):
http://localhost:22343/SIGService.svc/GetTicket/{user}/{pwd}
Sample:
http://localhost:22343/SIGService.svc/GetTicket/daniel/topsecret
No ? or user= or single quotes necessary ....
With this, the value from {0} will be passed into the user parameter, and the value from {1} to the pwd parameter.
For consuming the service, I would recommend you check out the excellent RestSharp library which makes using your REST service a breeze.
Your code would look something like this:
// set up the REST Client
string baseServiceUrl = "http://localhost:22343/SIGService.svc";
RestClient client = new RestClient(baseServiceUrl);
// define the request
RestRequest request = new RestRequest();
request.Method = Method.GET;
request.RequestFormat = DataFormat.Xml;
request.Resource = "GetTicket/{user}/{pwd}";
request.AddParameter("user", "daniel", ParameterType.UrlSegment);
request.AddParameter("pwd", "top$ecret", ParameterType.UrlSegment);
// make the call and have it deserialize the XML result into a Ticket object
var result = client.Execute<Ticket>(request);
if (result.StatusCode == HttpStatusCode.OK)
{
Ticket ticket = result.Data;
}

Website asynchronously posting to Loggly

I'm trying to work out how to make an asynchronous logging solution for application logging to Loggly.
Looking at Loggly's ducumentation, and thinking of this as a classic Producer-Consumer problem, I came up with this:
Message Models to use for JSON Serialization of data:
[DataContract]
public abstract class LogMessage
{
[DataMember]
public DateTime TimeStamp {get;set;}
[DataMember]
public Guid Id {get;set;}
[DataMember]
public int SentAttemptCount {get;set;}
}
[DataContract]
public class ExceptionMessage : LogMessage
{
[DataMember]
public ExceptionMessageDetails ExceptionDetails {get;set;}
}
[DataContract]
public class ExceptionMessageDetails
{
[DataMember]
public string Message {get;set;}
[DataMember]
public string Type {get;set;}
[DataMember]
public string StackTrace {get;set;}
[DataMember]
public ExceptionMessageDetails InnerException {get;set;}
}
Logger class, that will be passed to anything that needs to log (like an ExceptionFilter). This uses a BlockingCollection to queue messages for sending to Loggly.
public class LogglyLogger
{
private readonly string logglyUrl = "https://logs-01.loggly.com/inputs/xxxx/";
private readonly HttpClient client;
private readonly BlockingCollection<LogMessage> logQueue;
private readonly int maxAttempts = 4;
public LogglyLogger()
{
logQueue = new BlockingCollection<LogMessage>();
client = new HttpClient();
Task.Run(async () =>
{
foreach(var msg in logQueue.GetConsumingEnumerable())
{
try
{
await SendMessage(msg);
}
catch (Exception)
{
if (msg.SentAttemptCount <= maxAttempts)
{
msg.SentAttemptCount += 1;
logQueue.Add(msg);
}
}
}
});
}
public void SendLogMessage<T>(T msg) where T : LogMessage
{
logQueue.Add(msg);
}
private async Task SendMessage<T>(T msg) where T : LogMessage
{
await client.PostAsJsonAsync(logglyUrl, msg);
}
}
Here are my questions:
Is there something wrong with this pattern of setting up the BlockingCollection?
Will JSON.Net figure out the correct subclass of LogMessage, or do I need to send the message differently?
Swallowing exceptions is definitely a code smell, but I'm not sure what should happen if the logger fails to send the message. Any thoughts?
Thanks in advance, SO.
I ended up solving this by taking more direct control of what to send.
The LogMessageEvelope class matured somewhat, adding a non-serialized MessageTags property to pass along desired tag(s) to Loggly.
/// <summary>
/// Send the log message to loggly
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
private void SendMessage(LogMessageEnvelope message)
{
// build list of tags
string tags = string.Join(",", message.MessageTags);
// serialize the message
JsonSerializerSettings settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
};
string content =
JsonConvert.SerializeObject(message, Formatting.Indented, settings);
// build the request
HttpRequestMessage request = new HttpRequestMessage();
request.RequestUri = new Uri(logglyUrl);
request.Method = HttpMethod.Post;
request.Content = new StringContent(content, Encoding.UTF8);
request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Add("X-LOGGLY-TAG", tags);
// send the request
HttpClient client = new HttpClient();
client.SendAsync(request)
.ContinueWith(sendTask =>
{
// handle the response
HttpResponseMessage response = sendTask.Result;
if (!response.IsSuccessStatusCode)
// handle a failed log message post
});
}

Categories

Resources