I am having a problem deserializing json in my rest self-hosted service.
I have a test page that invokes the self-hosted REST with JSON, here is the code:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<script type="text/javascript">
function doFunction() {
xhr = new XMLHttpRequest();
var url = "https://localhost:1234/business/test/testing2/endpoint";
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-type", "application/json");
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
var json = JSON.parse(xhr.responseText);
alert(json);
}
}
var data = JSON.stringify({ testing : "test" });
xhr.send(data);
}
</script>
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<input id="clickMe" type="button" value="clickme" onclick="doFunction();" />
</div>
</form>
</body>
</html>
And here is the interface and contracts of my self-hosted service:
[DataContract]
public class OperationInput
{
[DataMember]
public string testing { get; set; }
}
[DataContract]
public class OperationOutput
{
[DataMember]
public int Status { get; set; }
[DataMember]
public string Message { get; set; }
[DataMember]
public string AddInfo { get; set; }
[DataMember]
public string PartnerID { get; set; }
[DataMember]
public string SessionID { get; set; }
}
[ServiceContract]
interface IRegisterOperation
{
[OperationContract]
[WebInvoke(UriTemplate = "/endpoint",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json, Method = "*")]
OperationOutput Operation(OperationInput order);
}
And here is the implementation of the interface:
public class RegisterOperation : IRegisterOperation
{
public OperationOutput Operation(OperationInput input)
{
System.IO.StreamWriter file = new System.IO.StreamWriter("c:\\testing.txt", false);
file.WriteLine(input.testing);
file.Close();
OperationOutput output = new OperationOutput();
output.Status = 200;
output.Message = "The action has been successfully recorded on NAVe";
output.AddInfo = "";
return output;
}
}
I am creating the self-host using this code:
host = new ServiceHost(implementationType, baseAddress);
ServiceEndpoint se = host.AddServiceEndpoint(endpointType, new WebHttpBinding(WebHttpSecurityMode.Transport), "");
se.Behaviors.Add(new WebHttpBehavior());
host.Open();
Using debug I notice that it hits the breakpoint inside my service, so the call to localhost is working but the parameter of input is null, as you can see in the image below:
Here is 2 images capturing the POST Request with JSON on fiddler:
Have you any idea why I am getting null ? instead of the string "test" as I do on the invocation in javascript?
Thank you very much in advance ;)
EDIT:
I activated HTTPS Decryption on Fiddler and now pressed "Yes" to install certificate on trusted ca instead of pressin "no", and the breakpoint is hitted, and fiddler now captured an options request as the following images represent
Shouldn't this be a post request instead of a options request?? maybe that's why I don't see the json?
Thanks a lot
I figured it out, the problem was the OPTIONS Request, I need to receive a POST Request so I get the JSON.
I added a behaviour attribute to my service host so that it responds to options request allowing the wcf service host to receive cross origin requests.
So I added the code from the answer of this (Cross Origin Resource Sharing for c# WCF Restful web service hosted as Windows service) question and now I get a POST Request after the first Options request:
If the link is no longer available, here is the solution of the question:
CODE:
Create 2 classes as follows:
MessageInspector implementing IDispatchMessageInspector.
BehaviorAttribute implementing Attribute, IEndpointBehavior and IOperationBehavior.
With the following details:
//MessageInspector Class
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Description;
namespace myCorsService
{
public class MessageInspector : IDispatchMessageInspector
{
private ServiceEndpoint _serviceEndpoint;
public MessageInspector(ServiceEndpoint serviceEndpoint)
{
_serviceEndpoint = serviceEndpoint;
}
/// <summary>
/// Called when an inbound message been received
/// </summary>
/// <param name="request">The request message.</param>
/// <param name="channel">The incoming channel.</param>
/// <param name="instanceContext">The current service instance.</param>
/// <returns>
/// The object used to correlate stateMsg.
/// This object is passed back in the method.
/// </returns>
public object AfterReceiveRequest(ref Message request,
IClientChannel channel,
InstanceContext instanceContext)
{
StateMessage stateMsg = null;
HttpRequestMessageProperty requestProperty = null;
if (request.Properties.ContainsKey(HttpRequestMessageProperty.Name))
{
requestProperty = request.Properties[HttpRequestMessageProperty.Name]
as HttpRequestMessageProperty;
}
if (requestProperty != null)
{
var origin = requestProperty.Headers["Origin"];
if (!string.IsNullOrEmpty(origin))
{
stateMsg = new StateMessage();
// if a cors options request (preflight) is detected,
// we create our own reply message and don't invoke any
// operation at all.
if (requestProperty.Method == "OPTIONS")
{
stateMsg.Message = Message.CreateMessage(request.Version, null);
}
request.Properties.Add("CrossOriginResourceSharingState", stateMsg);
}
}
return stateMsg;
}
/// <summary>
/// Called after the operation has returned but before the reply message
/// is sent.
/// </summary>
/// <param name="reply">The reply message. This value is null if the
/// operation is one way.</param>
/// <param name="correlationState">The correlation object returned from
/// the method.</param>
public void BeforeSendReply(ref Message reply, object correlationState)
{
var stateMsg = correlationState as StateMessage;
if (stateMsg != null)
{
if (stateMsg.Message != null)
{
reply = stateMsg.Message;
}
HttpResponseMessageProperty responseProperty = null;
if (reply.Properties.ContainsKey(HttpResponseMessageProperty.Name))
{
responseProperty = reply.Properties[HttpResponseMessageProperty.Name]
as HttpResponseMessageProperty;
}
if (responseProperty == null)
{
responseProperty = new HttpResponseMessageProperty();
reply.Properties.Add(HttpResponseMessageProperty.Name,
responseProperty);
}
// Access-Control-Allow-Origin should be added for all cors responses
responseProperty.Headers.Set("Access-Control-Allow-Origin", "*");
if (stateMsg.Message != null)
{
// the following headers should only be added for OPTIONS requests
responseProperty.Headers.Set("Access-Control-Allow-Methods",
"POST, OPTIONS, GET");
responseProperty.Headers.Set("Access-Control-Allow-Headers",
"Content-Type, Accept, Authorization, x-requested-with");
}
}
}
}
class StateMessage
{
public Message Message;
}
}
//BehaviorAttribute Class
using System;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace OpenBetRetail.NFCReaderService
{
public class BehaviorAttribute : Attribute, IEndpointBehavior,
IOperationBehavior
{
public void Validate(ServiceEndpoint endpoint) { }
public void AddBindingParameters(ServiceEndpoint endpoint,
BindingParameterCollection bindingParameters) { }
/// <summary>
/// This service modify or extend the service across an endpoint.
/// </summary>
/// <param name="endpoint">The endpoint that exposes the contract.</param>
/// <param name="endpointDispatcher">The endpoint dispatcher to be
/// modified or extended.</param>
public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
EndpointDispatcher endpointDispatcher)
{
// add inspector which detects cross origin requests
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(
new MessageInspector(endpoint));
}
public void ApplyClientBehavior(ServiceEndpoint endpoint,
ClientRuntime clientRuntime) { }
public void Validate(OperationDescription operationDescription) { }
public void ApplyDispatchBehavior(OperationDescription operationDescription,
DispatchOperation dispatchOperation) { }
public void ApplyClientBehavior(OperationDescription operationDescription,
ClientOperation clientOperation) { }
public void AddBindingParameters(OperationDescription operationDescription,
BindingParameterCollection bindingParameters) { }
}
}
After this all you need to do is add this message inspector to service end point behavior.
ServiceHost host = new ServiceHost(typeof(myService), _baseAddress);
foreach (ServiceEndpoint EP in host.Description.Endpoints)
EP.Behaviors.Add(new BehaviorAttribute());
Thanks for everyone help ;)
You have Method = "*"
I would experiment with:
Method = "POST" ....
[ServiceContract]
interface IRegisterOperation
{
OperationOutput Operation(OperationInput order);
like so:
[OperationContract]
[WebInvoke(UriTemplate = "/registeroperation",
Method = "POST",
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Bare)]
OperationOutput Operation(OperationInput order);
APPEND:
Your json does not look right (from the screen shots)
I would expect something simple like:
{
"ACTPRDX": "test"
}
Can you do an "alert" right after you stringify the object?
And show the results?
But (in general)...if your json is messed up, the "auto voodoo" of the Wcf Service Method won't work.
.....
This might be nit-picky, but try this:
Note the capital "T" after the hyphen.
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
I just found this in my code:
var jsonObject = { ACTPRDX : "test" };
var whatToSendOverTheWire = JSON.stringify(jsonObject);
Try that.
As mentioned, your json is wrong. the fix is to figure out how its being screwed up.
You actually pass primitive instead of expected object
var data = JSON.stringify({ ACTPRDX : "test" });
The above data would work for method :
public XYZ Something(string ACTPRDX)
You should send object to your method like that
var obj= new Object();
obj.ACTPRDX = "test";
var data = JSON.stringify({ order: obj});
Also interface and implementation have different name for parameter OperationInput -> order and input.
Related
I would like to an HTTP header to my WCF SOAP Service. My end goal is to send API keys through this HTTP header.
Below is my code:
[ServiceBehavior(Namespace = "http://****.com/**/1.1")]
public class MyWcfSvc : IMyVerify
{
const int MaxResponseSize = 0xffff; // 64K max size - Normally it will be MUCH smaller than this
private static readonly NLogLogger Logger;
static MyWcfSvc()
{
Logger = new NLogLogger();
// Add an HTTP Header to an outgoing request
HttpRequestMessageProperty requestMessage = new HttpRequestMessageProperty();
requestMessage.Headers["User-Auth"] = "MyHttpHeaderValue";
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestMessage;
}
}
I do not see User-Auth header under HTTP request headers.
I also tried with another way.
public AnalyzeResponse Analyze(AnalyzeRequest analyzeRequest)
{
HttpRequestMessageProperty requestMessage = new HttpRequestMessageProperty();
requestMessage.Headers["User-Auth"] = "MyHttpHeaderValue";
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestMessage;
.
.
. Rest of the service implementation
.
.
.
}
But, still, I don't see any HTTP header information with the request message. I am using SOAP UI to send the requests and to view the responses.
How should I go about this? Am I suppose to make changes to the Service related to class? Or I need to make some changes to web.config file?
SOAP Header
To add a SOAP header, use the following code client-side:
using (OperationContextScope scope = new OperationContextScope((IContextChannel)channel))
{
MessageHeader<string> header = new MessageHeader<string>("MyHttpHeaderValue");
var untyped = header.GetUntypedHeader("User-Auth", ns);
OperationContext.Current.OutgoingMessageHeaders.Add(untyped);
// now make the WCF call within this using block
}
And then, server-side, grab it using:
MessageHeaders headers = OperationContext.Current.IncomingMessageHeaders;
string identity = headers.GetHeader<string>("User-Auth", ns);
NB. ns is The namespace URI of the header XML element.
HTTP Header
To add an Http header:
// Add a HTTP Header to an outgoing request
HttpRequestMessageProperty requestMessage = new HttpRequestMessageProperty();
requestMessage.Headers["MyHttpHeader"] = "MyHttpHeaderValue";
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestMessage;
And to grab it server-side
IncomingWebRequestContext request = WebOperationContext.Current.IncomingRequest;
WebHeaderCollection headers = request.Headers;
Console.WriteLine(request.Method + " " + request.UriTemplateMatch.RequestUri.AbsolutePath);
foreach (string headerName in headers.AllKeys)
{
Console.WriteLine(headerName + ": " + headers[headerName]);
}
If you are trying to add an HTTP request header to the client request, you can follow the procedure below.
Create a client message inspector. For example:
public class CustomInspector : IClientMessageInspector
{
public void AfterReceiveReply(ref Message reply, object correlationState)
{
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
HttpRequestMessageProperty reqProps = request.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
if(reqProps == null)
{
reqProps = new HttpRequestMessageProperty();
}
reqProps.Headers.Add("Custom-Header", "abcd");
request.Properties[HttpRequestMessageProperty.Name] = reqProps;
return null;
}
}
Create an endpoint behavior to load this inspector:
public class CustomBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new CustomInspector());
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
Finally add this behavior to the endpoint.
class Program
{
static void Main(string[] args)
{
ChannelFactory<ICalculator> factory = new ChannelFactory<ICalculator>("BasicHttpsBinding_ICalculator");
factory.Endpoint.EndpointBehaviors.Add(new CustomBehavior());
var client = factory.CreateChannel();
var number = client.Add(1, 2);
Console.WriteLine(number.ToString());
}
}
The above example works on my side. I could see the request header with Fiddler.
There is better solution on client-side than Leonardo's. His solution requires to manually modify each request. Here is solution with ClientMessageInspector, which automatically adds header to each outgoing request.
1: Define MessageInspector with overrides:
Bellow is the only one override method, the rest is empty.
public class ClientMessageInspector : IClientMessageInspector
{
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
HttpRequestMessageProperty property = new HttpRequestMessageProperty();
property.Headers["User-Agent"] = "value";
request.Properties.Add(HttpRequestMessageProperty.Name, property);
return null;
}
...
}
Bind this Message inspector to EndPointBehavior.
Bellow is the only one override method, the rest is empty.
public class CustomEndpointBehavior : IEndpointBehavior
{
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new ClientMessageInspector());
}
...
}
The last step is to add the new behavior to the WCF endpoint:
Endpoint.EndpointBehaviors.Add(new CustomEndpointBehavior());
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.
We are working on WCF Routing Service which redirects different soap actions to different endpoints.
We want to this service rewrite query string included in router url: #router url#?param=param to endpoint: #endpoint url#?param=param.
Our webservices accepts query strings when call directly, this strings are visible in router (context) but on the end this strings are removed from url.
Do you know how add this strings to the end of endpoint url in every request?
We solved the problem.
You must create new binding:
public class QueryHttpBinding : BasicHttpBinding
{
public override BindingElementCollection CreateBindingElements()
{
var result = base.CreateBindingElements();
var http = result.Find<HttpTransportBindingElement>();
if (http != null)
{
http.ManualAddressing = true;
}
var https = result.Find<HttpsTransportBindingElement>();
if (https != null)
{
https.ManualAddressing = true;
}
return result;
}
}
And Client message inspector:
public class CustomInspectorBehavior : IClientMessageInspector
{
object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel)
{
UriBuilder builder = new UriBuilder(channel.RemoteAddress.ToString());
builder.Path += "?" + ((HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name]).QueryString;
request.Headers.To = builder.Uri;
return null;
}
void IClientMessageInspector.AfterReceiveReply(ref Message reply, object correlationState)
{
}
}
Next you must create new endpoint Behavior:
public class Behavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
var inspector = new CustomInspectorBehavior();
clientRuntime.MessageInspectors.Add(inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
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?
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
});
}