Reputation: 1359
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>
Upvotes: 1
Views: 3861
Reputation: 14677
You need to implement a custom IDispatchMessageInspector to capture raw request in method AfterReceiveRequest see my answer here.
Update(for recent comment):
Adressing your recent comment, You can modify the Message content to add additional information like in your case an ID; If you look at the code in sample inside the method MessageString it is creating a new message writer based on the type of WebContent received. If it's Json then JsonReader will be used. Just add your information in Message body string like below:
string messageBody = Encoding.UTF8.GetString(ms.ToArray());
messageBody = messageBody.Insert(<correct index position>, "<your new ID>");
ms.Position = 0;
XmlDictionaryReader reader = JsonReaderWriterFactory.CreateJsonReader(new StringReader(messageBody), XmlDictionaryReaderQuotas.Max);
Message newMessage = Message.CreateMessage(reader, int.MaxValue, message.Version);
Note: This strategy would require to have additional 'ID' in your JsonData
class. So that the value gets derserialized. But this is not the only way to achieve it. Probably when you ask another question put all scenarios.
Upvotes: 1
Reputation: 3757
You set your method to receive (Invoke) json message, and you can also set it to return json as response adding WebGet to your operation:
[OperationContract]
[WebInvoke(Method = "POST",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Bare,
UriTemplate = "gaterequest/{param}")]
[WebGet(ResponseFormat = WebMessageFormat.Json)]
ReturnMessage PostgateRequest(JsonData data, string param);
Hope it helps
Upvotes: 0