1

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>
Community
  • 1
  • 1
Laercio Metzner
  • 1,351
  • 11
  • 22
  • 1
    check out my [answer here](http://stackoverflow.com/a/33349912/881798) for capturing raw data in WCF Rest API. – vendettamit Oct 28 '15 at 13:57
  • vendettamit, I had already tried it, but my AfterReceiveRequest method was not being called. Maybe I missed a xml configuration (the app is going to be deployed on IIS). I'm going to edit my question, including web.config and service.svc, could you check what I missed ? – Laercio Metzner Oct 28 '15 at 14:15
  • Post your configuration as well. Let see what is missed. – vendettamit Oct 28 '15 at 14:17
  • Posted, as you can see, it's very minimalistic because it's not a VisualStudio project, it's a SharpDevelop project. – Laercio Metzner Oct 28 '15 at 14:26
  • I don't see the MessageInspector behavior being injected anywhere in the configuration. This is why your AfterReceiveRequest is not getting called. – vendettamit Oct 28 '15 at 14:28
  • vendettamit, you jus rock, probably i misspelled a namespace name or whatever on the xml configuration the first time i tried. Thank you. – Laercio Metzner Oct 29 '15 at 11:16
  • Answer: [answer from vendettamit](http://stackoverflow.com/questions/33343908/handle-json-request-data-in-wcf-rest-service-post-method/33349912#33349912) – Laercio Metzner Oct 29 '15 at 11:23
  • Shall I add it to answer? – vendettamit Oct 29 '15 at 11:31
  • yes please, this is the answer, but I have another problem, this one probably for another question, how can I share an int value between the AfterReceiveRequest method and my operation PostgateRequest? That's because I'm logging every single request in a database table, it generates an ID when the record is inserted at AfterReceiveRequest, and I have to update the status of the log to Success or Error, depending on what happened at PostgateRequest, and for that, I have to use de ID generated at the time of insertion. – Laercio Metzner Oct 29 '15 at 11:54
  • What is the JsonData type that you use in the solution? – Uber Schnoz Aug 08 '19 at 15:26
  • @UberSchnoz, the only thing I've set up was the parameters RequestFormat and ResponseFormat at the WebInvoke decorator. – Laercio Metzner Aug 08 '19 at 17:31

2 Answers2

1

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.

Community
  • 1
  • 1
vendettamit
  • 14,315
  • 2
  • 32
  • 54
  • It's a good idea to change the message body, but the method JsonReaderWriterFactory.CreateJsonReader is not accepting a StringReader as it's first parameter, it's supposed to be a Stream. – Laercio Metzner Oct 29 '15 at 16:22
  • 1
    I got this working. It's not necessary to change the request content anymore, instead I add a property to the request. I'm going to updtate my question with these changes. Thank you very much! – Laercio Metzner Oct 29 '15 at 17:37
  • Glad it helped you moving forward!! :) – vendettamit Oct 29 '15 at 17:38
  • vendettamit, could you please check my [new question?](http://stackoverflow.com/questions/33545824/wcf-cannot-create-custom-endpointbehavior-only-in-prod-environment) – Laercio Metzner Nov 05 '15 at 19:10
0

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

Ricardo Pontual
  • 3,749
  • 3
  • 28
  • 43