简体   繁体   中英

How to get the raw request body in an asp.NET 3.5 WCF Web Service

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>

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM