I've been required to develop a C# rest api that needs to log (in a database table) every single request to any defined route. Every log needs to record the request body, url, response body and status (Pending, Success or Error)
After a lot of internet research, I found the example below, which is the closest to what I need, but it gives me the data in XML format, I need the original format, which is Json.
var payload = System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.ToString()
Update - Solution
After talking to vendettamit, I got the solution below, which I think is worth sharing here:
This is my service:
using System;
using System.ServiceModel;
using System.ServiceModel.Web;
using AdvLinkForWebService.BusinessRules;
using AdvLinkForWebService.JsonModel;
namespace AdvLinkForWebService
{
[ServiceContract]
public interface IService{
[OperationContract]
[WebInvoke(Method = "POST",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Bare,
UriTemplate = "gaterequest/{param}")]
ReturnMessage PostgateRequest(JsonData data, string param);
}
public class Service : IService
{
// Any new Rout will follow this template:
public ReturnMessage PostgateRequest(JsonData data, string param)
{
// This is the return value
var ret = new ReturnMessage();
try {
// Business Rules resides inside gateBusinessRules
var businessRuleHandler = new gateBusinessRules();
businessRuleHandler.DoPost(data, param);
ret.type = true;
ret.message = "OK";
// Log success, if nothing wrong had happened
Utils.logSuccess();
} catch (Exception e) {
// Log exception, if something wrong had happened
ret.type = false;
ret.message = "NOK: " + e.Message;
Utils.logException(e.ToString());
}
return ret;
}
}
}
This is my Utils class, that encapsulates log operations:
using System;
using System.Data.SqlClient;
using System.Data;
using System.ServiceModel.Web;
namespace AdvLinkForWebService
{
public class Utils
{
public static string DB_CONNECTION_STRING = "Data Source=XXX.XXX.XXX.XXX;User Id=XXX;Password=XXX";
public static int logOperation(string type, string payload){
var url = System.ServiceModel.Web.WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri.OriginalString;
var method = System.ServiceModel.Web.WebOperationContext.Current.IncomingRequest.Method;
var userAgent = System.ServiceModel.Web.WebOperationContext.Current.IncomingRequest.UserAgent;
int key = 0;
// Do stuff to insert url, method, user agent and request payload in the database
// the generated key from the insertion will be returned as the key variable
return key;
}
public static void logResponse(int resCode, string resPayload)
{
int logId = (int) System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.Properties["logID"];
// Do stuff to update the log record in the database based on the ID
// This method updates response code and response payload
}
public static void logSuccess()
{
int logId = (int) System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.Properties["logID"];
// Do stuff to update the log record in the database based on the ID
// This method just updates log status to success
}
public static void logException(string error)
{
WebOperationContext ctx = WebOperationContext.Current;
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.BadRequest;
int logId = (int) System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.Properties["logID"];
// Do stuff to update the log record in the database based on the ID
// This method just updates log status to error and log the error message
}
public Utils()
{
}
}
}
This is the class responsible to log the raw Json from the request and response:
using System;
using System.IO;
using System.Runtime.Serialization.Json;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Text;
using System.Xml;
namespace AdvLinkForWebService.MessageInspector
{
public class IncomingMessageLogger : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
// Set up the message and stuff
Uri requestUri = request.Headers.To;
HttpRequestMessageProperty httpReq = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
MemoryStream ms = new MemoryStream();
XmlDictionaryWriter writer = JsonReaderWriterFactory.CreateJsonWriter(ms);
request.WriteMessage(writer);
writer.Flush();
// Log the message in the Database
string messageBody = Encoding.UTF8.GetString(ms.ToArray());
var logID = Utils.logOperation("I", messageBody);
// Reinitialize readers and stuff
ms.Position = 0;
XmlDictionaryReader reader = JsonReaderWriterFactory.CreateJsonReader(ms, XmlDictionaryReaderQuotas.Max);
Message newMessage = Message.CreateMessage(reader, int.MaxValue, request.Version);
// Put the ID generated at insertion time in a property
// in order to use it over again to update the log record
// with the response payload and, OK or error status
request.Properties.Add("logID", logID);
newMessage.Properties.CopyProperties(request.Properties);
request = newMessage;
return requestUri;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
MemoryStream ms = new MemoryStream();
XmlDictionaryWriter writer = JsonReaderWriterFactory.CreateJsonWriter(ms);
reply.WriteMessage(writer);
writer.Flush();
// Log the response in the Database
HttpResponseMessageProperty prop = (HttpResponseMessageProperty) reply.Properties["httpResponse"];
int statusCode = (int) prop.StatusCode;
string messageBody = Encoding.UTF8.GetString(ms.ToArray());
Utils.logResponse(statusCode, messageBody);
// Reinitialize readers and stuff
ms.Position = 0;
XmlDictionaryReader reader = JsonReaderWriterFactory.CreateJsonReader(ms, XmlDictionaryReaderQuotas.Max);
Message newMessage = Message.CreateMessage(reader, int.MaxValue, reply.Version);
newMessage.Properties.CopyProperties(reply.Properties);
reply = newMessage;
}
}
public class InsepctMessageBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new IncomingMessageLogger());
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
public class InspectMessageBehaviorExtension : BehaviorExtensionElement
{
public override Type BehaviorType
{
get { return typeof(InsepctMessageBehavior); }
}
protected override object CreateBehavior()
{
return new InsepctMessageBehavior();
}
}
}
And finally, this is the xml configuration necessary in order to get it all working:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="AdvLinkForWebService.Service">
<endpoint address=""
binding="webHttpBinding"
contract="AdvLinkForWebService.IService"
behaviorConfiguration="defaultWebHttpBehavior"/>
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="defaultWebHttpBehavior">
<inspectMessageBehavior/>
<webHttp defaultOutgoingResponseFormat="Json"/>
</behavior>
</endpointBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="inspectMessageBehavior"
type="AdvLinkForWebService.MessageInspector.InspectMessageBehaviorExtension, AdvLinkForWebService"/>
</behaviorExtensions>
</extensions>
</system.serviceModel>
</configuration>