在将JSON转换为消息之前修改它

时间:2011-12-29 17:14:21

标签: c# wcf json rest

如何将转换为Message之前将传入的JSON修改为WCF REST服务

例如,如果我提交以下内容:

{
    "Name": "Joe Bloggs",
    "Age": 30
}

我希望剥离所有空格,以便将数据包主体转换为:

{"Name":"Joe Bloggs","Age":30}

我正在尝试解决a problem in System.Runtime.Serialization.Json.XmlJsonReader我发现如果数据包中有任何空格,JSON没有正确转换为XML的地方。由于我不能保证我的所有客户端都会发送无空格的JSON,我希望某种预处理器能够在传递给XmlJsonReader之前从JSON中删除空格。

我已尝试使用IDispatchMessageInspector方法实现自定义AfterReceiveRequest。但这已经太晚了,因为JSON已经转换为包含不正确XML的Message。我需要在此阶段之前修改JSON,但是我找不到任何可追溯性的点。

1 个答案:

答案 0 :(得分:1)

如果要在解码之前处理消息,则需要一个自定义消息编码器(这是在线路中的字节和消息对象之间进行转换的组件)。您可以在http://blogs.msdn.com/b/carlosfigueira/archive/2011/11/09/wcf-extensibility-message-encoders.aspx找到有关自定义编码器的更多信息。

下面的自定义编码器会从JSON文档中删除空白区域。由JsonReaderWriterFactory.CreateJsonWriter创建的默认编写器不会进行任何漂亮的打印,因此这基本上就是您所需要的。

public class StackOverflow_8670954
{
    [DataContract(Name = "Person", Namespace = "MyNamespace")]
    public class Person
    {
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public int Age { get; set; }

        public override string ToString()
        {
            return string.Format("Person[Name={0},Age={1}]", Name, Age);
        }
    }
    [DataContract(Name = "Employee", Namespace = "MyNamespace")]
    public class Employee : Person
    {
        [DataMember]
        public int EmployeeId { get; set; }

        public override string ToString()
        {
            return string.Format("Employee[Id={0},Name={1}]", EmployeeId, Name);
        }
    }
    [ServiceContract]
    public interface ITest
    {
        [WebInvoke(RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
        [ServiceKnownType(typeof(Employee))]
        string PrintPerson(Person person);
    }
    public class Service : ITest
    {
        public string PrintPerson(Person person)
        {
            return person.ToString();
        }
    }
    static Binding GetBinding()
    {
        var result = new CustomBinding(new WebHttpBinding());
        for (int i = 0; i < result.Elements.Count; i++)
        {
            MessageEncodingBindingElement mebe = result.Elements[i] as MessageEncodingBindingElement;
            if (mebe != null)
            {
                result.Elements[i] = new MyEncodingBindingElement(mebe);
                break;
            }
        }
        return result;
    }
    class MyEncodingBindingElement : MessageEncodingBindingElement
    {
        MessageEncodingBindingElement inner;
        public MyEncodingBindingElement(MessageEncodingBindingElement inner)
        {
            this.inner = inner;
        }

        public override MessageEncoderFactory CreateMessageEncoderFactory()
        {
            return new MyEncoderFactory(this.inner.CreateMessageEncoderFactory());
        }

        public override MessageVersion MessageVersion
        {
            get { return this.inner.MessageVersion; }
            set { this.inner.MessageVersion = value; }
        }

        public override BindingElement Clone()
        {
            return new MyEncodingBindingElement(this.inner);
        }

        public override bool CanBuildChannelListener<TChannel>(BindingContext context)
        {
            return context.CanBuildInnerChannelListener<TChannel>();
        }

        public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
        {
            context.BindingParameters.Add(this);
            return context.BuildInnerChannelListener<TChannel>();
        }

        class MyEncoderFactory : MessageEncoderFactory
        {
            private MessageEncoderFactory inner;

            public MyEncoderFactory(MessageEncoderFactory inner)
            {
                this.inner = inner;
            }

            public override MessageEncoder Encoder
            {
                get { return new MyEncoder(this.inner.Encoder); }
            }

            public override MessageVersion MessageVersion
            {
                get { return this.inner.MessageVersion; }
            }
        }

        class MyEncoder : MessageEncoder
        {
            private MessageEncoder inner;

            public MyEncoder(MessageEncoder inner)
            {
                this.inner = inner;
            }

            public override string ContentType
            {
                get { throw new NotImplementedException(); }
            }

            public override string MediaType
            {
                get { throw new NotImplementedException(); }
            }

            public override MessageVersion MessageVersion
            {
                get { throw new NotImplementedException(); }
            }

            public override bool IsContentTypeSupported(string contentType)
            {
                return this.inner.IsContentTypeSupported(contentType);
            }

            public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
            {
                if (IsJsonContentType(contentType))
                {
                    // the encoder can also support other types of content (raw, xml), so we don't want to deal with those

                    MemoryStream writeStream = new MemoryStream();
                    XmlDictionaryWriter jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(writeStream, Encoding.UTF8, false);
                    XmlDictionaryReader jsonReader = JsonReaderWriterFactory.CreateJsonReader(buffer.Array, buffer.Offset, buffer.Count, XmlDictionaryReaderQuotas.Max);
                    jsonWriter.WriteNode(jsonReader, true);
                    jsonWriter.Flush();

                    byte[] newBuffer = bufferManager.TakeBuffer(buffer.Offset + (int)writeStream.Position);
                    Array.Copy(writeStream.GetBuffer(), 0, newBuffer, buffer.Offset, (int)writeStream.Position);
                    bufferManager.ReturnBuffer(buffer.Array);
                    buffer = new ArraySegment<byte>(newBuffer, buffer.Offset, (int)writeStream.Position);
                    writeStream.Dispose();
                    jsonReader.Close();
                    jsonWriter.Close();
                }

                return this.inner.ReadMessage(buffer, bufferManager, contentType);
            }

            static bool IsJsonContentType(string contentType)
            {
                return contentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase) ||
                    contentType.StartsWith("text/json", StringComparison.OrdinalIgnoreCase);
            }

            public override Message ReadMessage(Stream stream, int maxSizeOfHeaders, string contentType)
            {
                throw new NotSupportedException("Streamed transfer not supported");
            }

            public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
            {
                return this.inner.WriteMessage(message, maxMessageSize, bufferManager, messageOffset);
            }

            public override void WriteMessage(Message message, Stream stream)
            {
                throw new NotSupportedException("Streamed transfer not supported");
            }
        }
    }
    public static void Test()
    {
        string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
        ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
        host.AddServiceEndpoint(typeof(ITest), GetBinding(), "").Behaviors.Add(new WebHttpBehavior());
        host.Open();
        Console.WriteLine("Host opened");

        string[] inputs = new string[]
        {
            "{\"__type\":\"Employee:MyNamespace\",\"Age\":33,\"Name\":\"John\",\"EmployeeId\":123}",
            "{  \"__type\":\"Employee:MyNamespace\",\"Age\":33,\"Name\":\"John\",\"EmployeeId\":123}",
        };

        foreach (string input in inputs)
        {
            WebClient c = new WebClient();
            c.Headers[HttpRequestHeader.ContentType] = "application/json";
            Console.WriteLine(c.UploadString(baseAddress + "/PrintPerson", input));
        }

        Console.Write("Press ENTER to close the host");
        Console.ReadLine();
        host.Close();
    }
}