none
JSON and WebInvoke

    Question

  • I am trying to setup a simple service that consumes and outputs JSON.  I have read this post http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2224074&SiteID=1 which suggest that you cannot use WebGet with DataContract's as your method parameters, but you can with WebInvoke.

    Here is my example:

    Code Block

        [ServiceContract(Namespace = "Namespace.PersonService", Name = "PersonService")]
         public interface IPersonService {

            [OperationContract]
            [WebInvoke(
                BodyStyle = WebMessageBodyStyle.Wrapped,
                RequestFormat = WebMessageFormat.Json,
                ResponseFormat = WebMessageFormat.Json,
                UriTemplate = "/Modify")]
            void Modify(Person person);
           
            [OperationContract]
            [WebInvoke(
                BodyStyle = WebMessageBodyStyle.Bare,
                RequestFormat = WebMessageFormat.Json,
                ResponseFormat = WebMessageFormat.Json,
                UriTemplate = "/GetAll")]
            PaginationHelper<Person> GetAll();
    }


    And my configuration:

    Code Block

    <system.serviceModel>
       <bindings>
         <webHttpBinding />
       </bindings>
      <behaviors>
       <serviceBehaviors>
        <behavior name="Namespace.PersonServiceBehavior">
         <serviceMetadata httpGetEnabled="true" />
         <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
          <behavior name="webBehavior">
            <webHttp />
          </behavior>
        </endpointBehaviors>
      </behaviors>
      <services>
       <service behaviorConfiguration="Namespace.PersonServiceBehavior" name="Integral.Server.NetLocks.Web.Services.PersonnelManagement.PersonnelGroupsManager">
        <endpoint behaviorConfiguration="webBehavior" address="" binding="webHttpBinding" contract="Namespace.PersonService" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
       </service>
      </services>
     </system.serviceModel>


    When I call the GetAll method by invoking http://localhost/context/PersonService/GetAll, I get the expected JSON output.   Now I want to call my Modify and use the complex Person type as my parameter.  I have tried sending a POST to the service all sorts of different ways, but with no luck.  I always seem to get the exception:

    Code Block

    <ExceptionType>System.InvalidOperationException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>

    <Message>The incoming message has an unexpected message format 'Raw'. The expected message formats for the operation are 'Xml', 'Json'. This can be because a WebContentTypeMapper has not been configured on the binding. See the documentation of WebContentTypeMapper for more details.</Message>
    <StackTrace>



    Is it possible to do what I am trying to do or do I need to pass in a String of JSON or XML data and deserialize it myself?
    Wednesday, October 31, 2007 6:06 PM

Answers

  • You need to set the Content-Type of the request to "application/json", and pass the appropriate JSON representation of the Person object to the server.

     

    For example, if the Person definition is as follows:

    [DataContract]

    public class Person

    {

        [DataMember]

        public string Name;

        [DataMember]

        public int Age;

    }

     

    Then the following request should work:

    Code Block

    POST /context/PersonService/Modify HTTP/1.1
    Content-Type: application/json; charset=utf-8
    Host: localhost
    Content-Length: 36

     

    {"person":{"Age":27,"Name":"Alice"}}

     

     

     

    Wednesday, October 31, 2007 9:12 PM
  • If you know that your input is arbitrary XML (and you know that it will be an element), then the best type for the input is XmlElement. If you receive Stream you'll need to do the conversion yourself. If your input is a document fragment (multiple elements, then you can use XmlNode[] as the argument).

     

    This example below shows this:

    //Consuming arbitrary XML

    public class Post2348599b

    {

        [ServiceContract]

        public interface ITestService

        {

            [OperationContract, WebInvoke(

                BodyStyle = WebMessageBodyStyle.Bare,

                RequestFormat = WebMessageFormat.Xml,

                ResponseFormat = WebMessageFormat.Xml)]

            string ProcessXml(XmlElement input);

        }

        public class TestService : ITestService

        {

            public string ProcessXml(XmlElement input)

            {

                return input.OuterXml;

            }

        }

        public static void Test()

        {

            string baseAddress = "http://" + Environment.MachineName + ":8000/Service";

            ServiceHost host = new ServiceHost(typeof(TestService), new Uri(baseAddress));

            ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(ITestService), new WebHttpBinding(), "");

            endpoint.Behaviors.Add(new WebHttpBehavior());

            host.Open();

            Console.WriteLine("Host opened");

     

            HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(baseAddress + "/ProcessXml");

            req.Method = "POST";

            req.ContentType = "application/xml; charset=utf-8";

            string reqBody = @"<TextBlock x:Name=""text"" IsHitTestVisible=""false"" Text=""Hello"" Foreground=""black"" xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""/>";

            byte[] reqBodyBytes = Encoding.UTF8.GetBytes(reqBody);

            Stream reqStream = req.GetRequestStream();

            reqStream.Write(reqBodyBytes, 0, reqBodyBytes.Length);

            reqStream.Close();

     

            HttpWebResponse resp = (HttpWebResponse)req.GetResponse();

            Console.WriteLine("HTTP/{0} {1} {2}", resp.ProtocolVersion, (int)resp.StatusCode, resp.StatusDescription);

            Console.WriteLine(new StreamReader(resp.GetResponseStream()).ReadToEnd());

            resp.Close();

     

            Console.WriteLine("Press ENTER to close service");

            Console.ReadLine();

            host.Close();

        }

    }

    Saturday, November 03, 2007 5:09 PM
  • To get real, real bare data, simply declare your operation taking "System.IO.Stream" as an argument - you'll get the raw bytes coming from the wire.

     

    For the URL rewriting, can you post a different question in the forum? This has long gone from its original title, and we want to increase its "searchability".

    Thursday, November 15, 2007 2:36 AM
  • By default, the content-types application/xml and text/xml are mapped to the decoded that handles XML data. If you want to treat is as raw data, you need to tell the service to do so, with a WebContentTypeMapper (see below). Notice that if you use raw data, you don't even need to specify the request format on the WebInvoke attribute (it will be ignored; raw trumps everything).

     

    public class Post2348599c

    {

        [ServiceContract]

        public interface ITestService

        {

            [OperationContract, WebInvoke]

            string ProcessXml(Stream input);

        }

        public class TestService : ITestService

        {

            public string ProcessXml(Stream input)

            {

                return new StreamReader(input).ReadToEnd();

            }

        }

        public class MyContentTypeMapper : WebContentTypeMapper

        {

            public override WebContentFormat GetMessageFormatForContentType(string contentType)

            {

                return WebContentFormat.Raw;

            }

        }

        static Binding CreateBinding()

        {

            CustomBinding result = new CustomBinding(new WebHttpBinding());

            WebMessageEncodingBindingElement webMEBE = result.Elements.Find<WebMessageEncodingBindingElement>();

            webMEBE.ContentTypeMapper = new MyContentTypeMapper();

            return result;

        }

        public static void Test()

        {

            string baseAddress = "http://" + Environment.MachineName + ":8000/Service";

            ServiceHost host = new ServiceHost(typeof(TestService), new Uri(baseAddress));

            WebHttpBinding serverBinding = new WebHttpBinding();

            ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(ITestService), CreateBinding(), "");

            endpoint.Behaviors.Add(new WebHttpBehavior());

            host.Open();

            Console.WriteLine("Host opened");

     

            HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(baseAddress + "/ProcessXml");

            req.Method = "POST";

            req.ContentType = "application/xml; charset=utf-8";

            string reqBody = @"<?xml version=""1.0"" encoding=""utf-8""?>

    <firstElement/>

    <secondElement/>

    <you can even have invalid XML here/>";

            byte[] reqBodyBytes = Encoding.UTF8.GetBytes(reqBody);

            Stream reqStream = req.GetRequestStream();

            reqStream.Write(reqBodyBytes, 0, reqBodyBytes.Length);

            reqStream.Close();

     

            HttpWebResponse resp = (HttpWebResponse)req.GetResponse();

            Console.WriteLine("HTTP/{0} {1} {2}", resp.ProtocolVersion, (int)resp.StatusCode, resp.StatusDescription);

            Console.WriteLine(new StreamReader(resp.GetResponseStream()).ReadToEnd());

            resp.Close();

     

            Console.WriteLine("Press ENTER to close service");

            Console.ReadLine();

            host.Close();

        }

    }

    Thursday, November 15, 2007 3:16 PM

All replies

  • You need to set the Content-Type of the request to "application/json", and pass the appropriate JSON representation of the Person object to the server.

     

    For example, if the Person definition is as follows:

    [DataContract]

    public class Person

    {

        [DataMember]

        public string Name;

        [DataMember]

        public int Age;

    }

     

    Then the following request should work:

    Code Block

    POST /context/PersonService/Modify HTTP/1.1
    Content-Type: application/json; charset=utf-8
    Host: localhost
    Content-Length: 36

     

    {"person":{"Age":27,"Name":"Alice"}}

     

     

     

    Wednesday, October 31, 2007 9:12 PM
  • Once you verified what I was doing was correct, I used Tamper Data to check out the actually request that I was sending.  I added the header 'application/json' to my request, but it turns out either Firefox or my Javascript framework (I am using ExtJS) adds a second content type:

     

    Content-Type: application/x-www-form-urlencoded,application/json

     

    I stripped out the applicaiton/x-www-form-urlencoded and it worked properly.  Now I just need to figure out where that is getting added and remove it.

     

    Thank you for the assistance.

    Wednesday, October 31, 2007 9:38 PM
  • I verified that ExtJS is the cause of the extra header.  It adds a default header to every POST.  You can modify the behavior (ExtJS 2.0) by setting the default header to:

    Ext.lib.Ajax.defaultPostHeader = 'application/json';

    Alternatively, you can turn off default headers and set the 'application/json' header every time.
    Thursday, November 01, 2007 1:14 PM
  • Carlos,
    I am trying to do the same thing with xml.  I'm trying to make the most simple service that consumes and output xml.  I followed one of your previous posts
    http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2282952&SiteID=1
    and am able to output xml properly, but after almost a day, I am not any closer to consuming xml.

    Here is what my contract looks like.  Its a modification of the sample you posted in the post i mentioned.

    [ServiceContract]
    public interface ITestService
    {
        [OperationContract, WebInvoke(
                BodyStyle = WebMessageBodyStyle.Bare,
                RequestFormat = WebMessageFormat.Xml,
                ResponseFormat = WebMessageFormat.Xml)]
        XmlElement GetAsXml(Stream input);
    }

    I am using fiddler to simulate a client.  Here is what I am sending using fiddler:
    POST http://127.0.0.1:60000/TestService.svc/GetAsXml HTTP/1.1
    User-Agent: Fiddler
    Host: 127.0.0.1:60000
    Content-Length: 204

    <TextBlock x:Name="text" IsHitTestVisible="false" Text="Hello" Foreground="black" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"/>

    The response I get it is HTTP/1.1 415 Unsupported Media Type

    If I add Content-Type: application/xml; charset=utf-8 to the request headers, I get the following response:
    HTTP/1.1 400 Bad Request

    Any ideas.  All I want to do is parse the xml that is in the post request on the server

    Saturday, November 03, 2007 5:52 AM
  • It's actually a good sign that you are getting the 400 error.  At least you know WCF is processing your request.  You need to turn on tracing for the WCF service.  You can do that using the service confguration tool.  Then use the service trace viewer to see what your exception is.

     

    Just search on WCF tracing if you need assistance getting that working.

    Saturday, November 03, 2007 2:36 PM
  • If you know that your input is arbitrary XML (and you know that it will be an element), then the best type for the input is XmlElement. If you receive Stream you'll need to do the conversion yourself. If your input is a document fragment (multiple elements, then you can use XmlNode[] as the argument).

     

    This example below shows this:

    //Consuming arbitrary XML

    public class Post2348599b

    {

        [ServiceContract]

        public interface ITestService

        {

            [OperationContract, WebInvoke(

                BodyStyle = WebMessageBodyStyle.Bare,

                RequestFormat = WebMessageFormat.Xml,

                ResponseFormat = WebMessageFormat.Xml)]

            string ProcessXml(XmlElement input);

        }

        public class TestService : ITestService

        {

            public string ProcessXml(XmlElement input)

            {

                return input.OuterXml;

            }

        }

        public static void Test()

        {

            string baseAddress = "http://" + Environment.MachineName + ":8000/Service";

            ServiceHost host = new ServiceHost(typeof(TestService), new Uri(baseAddress));

            ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(ITestService), new WebHttpBinding(), "");

            endpoint.Behaviors.Add(new WebHttpBehavior());

            host.Open();

            Console.WriteLine("Host opened");

     

            HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(baseAddress + "/ProcessXml");

            req.Method = "POST";

            req.ContentType = "application/xml; charset=utf-8";

            string reqBody = @"<TextBlock x:Name=""text"" IsHitTestVisible=""false"" Text=""Hello"" Foreground=""black"" xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""/>";

            byte[] reqBodyBytes = Encoding.UTF8.GetBytes(reqBody);

            Stream reqStream = req.GetRequestStream();

            reqStream.Write(reqBodyBytes, 0, reqBodyBytes.Length);

            reqStream.Close();

     

            HttpWebResponse resp = (HttpWebResponse)req.GetResponse();

            Console.WriteLine("HTTP/{0} {1} {2}", resp.ProtocolVersion, (int)resp.StatusCode, resp.StatusDescription);

            Console.WriteLine(new StreamReader(resp.GetResponseStream()).ReadToEnd());

            resp.Close();

     

            Console.WriteLine("Press ENTER to close service");

            Console.ReadLine();

            host.Close();

        }

    }

    Saturday, November 03, 2007 5:09 PM
  • Thanks, that worked great.
    Saturday, November 03, 2007 10:07 PM
  • Carlos,
    So I tried to change

            string ProcessXml(XmlElement input);


    to


            string ProcessXml(XmlDocument input);


    and I get an error saying that

    Type 'System.Xml.XmlDocument' cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute.

    Whats strange is that from looking at the metadata generated source in VS, XmlElement doesnt appear to have the DataContractAttribute either. 

    If I want to send a whole Xml document including the declaration, do I have to use a Stream and then parse it?




    Also I tried using XmlNode[] to get multiple elements, but I got the same HTTP/1.1 400 Bad Request response.  Do I have to do anything different with the request.

    Here is the code:

    [OperationContract]
        [WebInvoke(BodyStyle = WebMessageBodyStyle.Bare,
    RequestFormat=WebMessageFormat.Xml,
    ResponseFormat=WebMessageFormat.Xml,
    UriTemplate="GetFriends/{pHashId}")]
        XmlElement GetFriends(String pHashId, XmlElement[] pFriends);

    POST http://127.0.0.1:60000/Api.svc/GetFriends/asdsdasd HTTP/1.1

    User-Agent: Fiddler
    Host: 127.0.0.1:60000
    Content-Length: 23
    Content-Type: application/xml; charset=utf-8

    <friends/>
    <friends2/>


    Sunday, November 04, 2007 4:06 AM
  • XmlElement and XmlDocument aren't serializable entities, but WCF has a special case for XmlElement and XmlNode[] (for when people want to send "bare" XML to the service.

     

    In the code above, try replacing XmlElement[] pFriends with XmlNode[] pFriends, that should work.

     

    Sunday, November 04, 2007 4:41 PM
  • Hi Carlos,
    I can't seem to get that to work.  I keep getting HTTP/1.1 400 Bad Request from the server.  Here is my configuration.

    1) Web config contains:
    <system.serviceModel>
            <behaviors>
                <endpointBehaviors>
                    <behavior name="WebEndpointBehavior">
                        <webHttp/>
                    </behavior>
                </endpointBehaviors>
            </behaviors>
            <services>
                <service name="Api">
                    <endpoint behaviorConfiguration="WebEndpointBehavior" address="" binding="webHttpBinding" contract="IApi"/>
                </service>
            </services>
        </system.serviceModel>

    2) I am sending:
    POST http://127.0.0.1:60000/Api.svc/GetFriends

    User-Agent: Fiddler
    content-type: application/xml; charset=utf-8
    Host: 127.0.0.1:60000
    Content-Length: 51

    <?xml version="1.0" encoding="UTF-8"?>
    <friends />

    3) The Service is as follows:
        [OperationContract]
        [WebInvoke(BodyStyle = WebMessageBodyStyle.Bare, RequestFormat=WebMessageFormat.Xml, ResponseFormat=WebMessageFormat.Xml)]
        XmlElement GetFriends(XmlElement[] pFriends);

    4) and the implemenation is:
        public XmlElement GetFriends(XmlNode[] pFriends)
        {

            return null;
        }

    not that it matters because it never reaches into that function.  WCF is rejecting the request somewhere else along the line.

    It doesnt seem to like the xml and cant serialize it I guess.  I tried sending
    <friend />

    and

    <friend1 />
    <friend2 />

    but it still never gets into the GetFriends function.

    If I dont send anything, the function gets called.

    Any ideas?
    Wednesday, November 14, 2007 2:33 PM
  • I missed something in my previous post for this thread - if you want to use XmlNode[], you need a wrapper over the actual XML elements that are being sent, to let the service know that there is such an array coming. This should work:

     

    POST http://127.0.0.1:60000/Api.svc/GetFriends

    User-Agent: Fiddler
    content-type: application/xml; charset=utf-8
    Host: 127.0.0.1:60000
    Content-Length: 51

    <?xml version="1.0" encoding="UTF-8"?>
    <a:ArrayOfXmlNode xmlns:a="http://schemas.datacontract.org/2004/07/System.Xml"> <friends /><friends2 /> </a:ArrayOfXmlNode>

    Wednesday, November 14, 2007 4:06 PM
  • That does work.  Thanks!!!

    Although having to put in the extra root element wont work for the scenario I am trying to do.  isn't there any way to just get what the user is sending as text and then parse it on my own.

    Something like:
        [OperationContract]
        [WebInvoke(BodyStyle = WebMessageBodyStyle.Bare, RequestFormat=WebMessageFormat.Xml, ResponseFormat=WebMessageFormat.Xml)]
        XmlElement GetFriends(String pFriends);

    or

        [OperationContract]
        [WebInvoke(BodyStyle = WebMessageBodyStyle.Bare, RequestFormat=WebMessageFormat.Xml, ResponseFormat=WebMessageFormat.Xml)]
        XmlElement GetFriends(Byte[] pFriends);

    That way they can send whatever they want and I will use my code to figure it out.  Like is there a way to have the RequestFormat be WebMessage.Format.Bare or something like that to tell WCF dont mess with the data coming in,  let me deal with it.

    It doesnt even need to be with WCF or even with services per se,  i'm just looking for some way to send and recieve bare text with Asp.net and then I can parse the text on my own.

    Also do you know of any resources on the web that demonstrate url rewriting for wcf so instead of:

    www.mywebsite.com/Api.svc/GetFriends

    the user uses

    api.mywebsite.com/GetFriends

    and I can reroute it to

    www.mywebsite.com/Api.svc/GetFriends.

    I cant even switch frameworks if it would let me do this.  Like maybe if monorail.Net lets me do this, I would switch. 

    Thanks very much for your help.

    Matt
    Wednesday, November 14, 2007 11:49 PM
  • To get real, real bare data, simply declare your operation taking "System.IO.Stream" as an argument - you'll get the raw bytes coming from the wire.

     

    For the URL rewriting, can you post a different question in the forum? This has long gone from its original title, and we want to increase its "searchability".

    Thursday, November 15, 2007 2:36 AM
  • Thanks Carlos, that worked.  I moved the WCf and rewriting post to a new thread:
    http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2411891&SiteID=1

    So here's the last fly in this WCF xml goodness ointment Smile

    Bare data using Stream as the argument works if the client sends the xml with the following content type in the http header:
    content-type = "application/text; charset=utf-8"

    If i keep what I had originally:
    content-type = "application/xml; charset=utf-8"

    or use

    content-type = "text/xml; charset=utf-8"

    I get a (400) Bad Request.

    I think it has something to do with:
    RequestFormat = WebMessageFormat.Xml, ResponseFormat = WebMessageFormat.Xml in the WebInvoke Attribute, but I tried taking those out and it didnt change anything.  The only other option is Json and that obviously didnt work either.

    Is there someway that I can force the request format to also be bare so that WCF doesnt try to decode it and just lets my function handle it completely?

    Thanks in advance.

    Matt







    Thursday, November 15, 2007 11:33 AM
  • By default, the content-types application/xml and text/xml are mapped to the decoded that handles XML data. If you want to treat is as raw data, you need to tell the service to do so, with a WebContentTypeMapper (see below). Notice that if you use raw data, you don't even need to specify the request format on the WebInvoke attribute (it will be ignored; raw trumps everything).

     

    public class Post2348599c

    {

        [ServiceContract]

        public interface ITestService

        {

            [OperationContract, WebInvoke]

            string ProcessXml(Stream input);

        }

        public class TestService : ITestService

        {

            public string ProcessXml(Stream input)

            {

                return new StreamReader(input).ReadToEnd();

            }

        }

        public class MyContentTypeMapper : WebContentTypeMapper

        {

            public override WebContentFormat GetMessageFormatForContentType(string contentType)

            {

                return WebContentFormat.Raw;

            }

        }

        static Binding CreateBinding()

        {

            CustomBinding result = new CustomBinding(new WebHttpBinding());

            WebMessageEncodingBindingElement webMEBE = result.Elements.Find<WebMessageEncodingBindingElement>();

            webMEBE.ContentTypeMapper = new MyContentTypeMapper();

            return result;

        }

        public static void Test()

        {

            string baseAddress = "http://" + Environment.MachineName + ":8000/Service";

            ServiceHost host = new ServiceHost(typeof(TestService), new Uri(baseAddress));

            WebHttpBinding serverBinding = new WebHttpBinding();

            ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(ITestService), CreateBinding(), "");

            endpoint.Behaviors.Add(new WebHttpBehavior());

            host.Open();

            Console.WriteLine("Host opened");

     

            HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(baseAddress + "/ProcessXml");

            req.Method = "POST";

            req.ContentType = "application/xml; charset=utf-8";

            string reqBody = @"<?xml version=""1.0"" encoding=""utf-8""?>

    <firstElement/>

    <secondElement/>

    <you can even have invalid XML here/>";

            byte[] reqBodyBytes = Encoding.UTF8.GetBytes(reqBody);

            Stream reqStream = req.GetRequestStream();

            reqStream.Write(reqBodyBytes, 0, reqBodyBytes.Length);

            reqStream.Close();

     

            HttpWebResponse resp = (HttpWebResponse)req.GetResponse();

            Console.WriteLine("HTTP/{0} {1} {2}", resp.ProtocolVersion, (int)resp.StatusCode, resp.StatusDescription);

            Console.WriteLine(new StreamReader(resp.GetResponseStream()).ReadToEnd());

            resp.Close();

     

            Console.WriteLine("Press ENTER to close service");

            Console.ReadLine();

            host.Close();

        }

    }

    Thursday, November 15, 2007 3:16 PM
  • How do I create this binding in the config file:

    this is what i have so far:

    <bindings>
          <customBinding>
            <binding name="MyCustomBinding">
              <webMessageEncoding ContentTypeMapper="MyContentTypeMapper" />
            </binding>
          </customBinding>
        </bindings>


    But, i cant figure out where to say that the customBinding is based of a WebHttpBinding.  I think as a result, WCF thinks that ContentTypeMapper is not a valid attribute.

    Parser Error Message: Unrecognized attribute 'ContentTypeMapper'. Note that attribute names are case-sensitive.

    Source Error:

    Line 106:      <customBinding>
    Line 107: <binding name="MyCustomBinding">
    Line 108: <webMessageEncoding ContentTypeMapper="MyContentTypeMapper" />
    Line 109: </binding>
    Line 110: </customBinding>

    Friday, November 16, 2007 12:19 AM
  • The equivalent of that WebHttpBinding as a custom binding in config would be the following:

     

    <customBinding>

      <binding name="MyCustomBinding">

        <webMessageEncoding webContentTypeMapperType="<the assembly-qualified name of your mapper type>" />

        <httpTransport manualAddressing="true" />

      </binding>

    </customBinding>

     

    The assenbly-qualified name of the type is in the form "Namespace.TypeName, AssemblyName" (you can also add the version, but this shouldn't be required).

    Friday, November 16, 2007 6:38 PM
  • Is there something I need to do to make the webContentTypeMapperType visible to WCF.  I dont get any intellisense on that attribute name and when I go to Api.svc, I get the following error:

    Server Error in '/' Application.

    Configuration Error

    Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.

    Parser Error Message: Unrecognized attribute 'webContentTypeMapperType'. Note that attribute names are case-sensitive.

    Source Error:

    Line 106:      <customBinding>
    Line 107: <binding name="MyCustomBinding">
    Line 108: <webMessageEncoding webContentTypeMapperType="MyContentTypeMapper" />
    Line 109: <httpTransport manualAddressing="true" />
    Line 110: </binding>

    Friday, November 16, 2007 10:40 PM
  • This attribute for config isn't available on Beta 2, only on the RC bits. Is this the version you're using? If so, then the only way to use the mapper to define the binding via code instead of config.

    Friday, November 16, 2007 11:26 PM
  • Is there anyway to reference a class in code from the web.config.

    I tried this:

    public class MyCustomBinding : CustomBinding
    {

    }

    and my config is
    <services>
                <service name="Api">
                    <endpoint behaviorConfiguration="WebEndpointBehavior" address="" binding="MyCustomBinding" contract="IApi"/>
                </service>
            </services>

    but Wcf says it can't find MyCustomBinding because it is looking in the web.config file:

    Configuration binding extension 'system.serviceModel/bindings/MyCustomBinding' could not be found. Verify that this binding extension is properly registered in system.serviceModel/extensions/bindingExtensions and that it is spelled correctly.

    Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

    Exception Details: System.Configuration.ConfigurationErrorsException: Configuration binding extension 'system.serviceModel/bindings/MyCustomBinding' could not be found. Verify that this binding extension is properly registered in system.serviceModel/extensions/bindingExtensions and that it is spelled correctly.


    Saturday, November 17, 2007 12:52 AM
  • You won't be able to do that in config. The .svc file below shows how to change the endpoint configuration in code

     

    <%@ServiceHost language=c# Debug="true" Service="MyTest.TestService" Factory="MyTest.MyServiceHostFactory" %>
    namespace MyTest
    {
     using System;
     using System.IO;
     using System.ServiceModel;
     using System.ServiceModel.Activation;
     using System.ServiceModel.Channels;
     using System.ServiceModel.Description;
     using System.ServiceModel.Web;

        [ServiceContract]
        public interface ITestService
        {
            [OperationContract, WebInvoke]
            string ProcessXml(Stream input);
        }

        public class TestService : ITestService
        {
            public string ProcessXml(Stream input)
            {
                return new StreamReader(input).ReadToEnd();
            }
        }

        public class MyServiceHostFactory : ServiceHostFactory
        {
            protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
            {
                return new MyServiceHost(typeof(TestService), baseAddresses);
            }
        }
        public class MyServiceHost : ServiceHost
        {
            public MyServiceHost(Type serviceType, params Uri[] baseAddresses)
                : base(serviceType, baseAddresses)
            {
            }
            protected override void ApplyConfiguration()
            {
                base.ApplyConfiguration();
                foreach (ServiceEndpoint endpoint in this.Description.Endpoints)
                {
                    if (endpoint.Name == "TheEndpointThatNeedsToBeChanged")
                    {
                        CustomBinding newBinding = new CustomBinding(endpoint.Binding);
                        WebMessageEncodingBindingElement webMEBE = newBinding.Elements.Find<WebMessageEncodingBindingElement>();
                        webMEBE.ContentTypeMapper = new MyWebContentTypeMapper();
                        endpoint.Binding = newBinding;
                    }
                }
            }
        }
        public class MyWebContentTypeMapper : WebContentTypeMapper
        {
            public override WebContentFormat GetMessageFormatForContentType(string contentType)
            {
                return WebContentFormat.Raw;
            }
        }
    }

     

    This assumes the following web.config:

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <system.serviceModel>
            <behaviors>
                <endpointBehaviors>
                    <behavior name="WebBehavior">
                        <webHttp />
                    </behavior>
                </endpointBehaviors>
            </behaviors>
            <services>
                <service name="MyTest.TestService">
                    <endpoint address="" behaviorConfiguration="WebBehavior" binding="webHttpBinding"
                        bindingConfiguration="" name="TheEndpointThatNeedsToBeChanged"
                        contract="MyTest.ITestService" />
                </service>
            </services>
        </system.serviceModel>
    </configuration>

    Saturday, November 17, 2007 1:25 AM
  • Carlos,

    Thanks!! Everything works now.

     

    Matt

    Monday, November 19, 2007 10:46 PM
  • Hi Carlos,

    The
    WebContentFormat.Raw seems only to work for XML, but other content-types like application/pdf give back http 400 responses.  Is it not possible to send any opaque data to WebInvoke with a Stream input?

    Thanks

    Marshall
    Friday, November 23, 2007 9:50 AM
  • It should work for everything. On the example I sent on this thread at 15 Nov 2007, 3:16 PM UTC, for example, the input is invalid XML. Can you enable tracing on the service to see why the request is being considered bad?

     

    If you can't figure out the issue from the traces, can you open another thread about this WebContentFormat.Raw problem? This thread has long gotten away from its root. Thank you.

    Friday, November 23, 2007 2:47 PM
  • Hi Carlos,

    I found out what the issue was, I had to up the allowed size

    HttpTransportBindingElement http = newBinding.Elements.Find<HttpTransportBindingElement>();
    http.TransferMode = TransferMode.StreamedResponse;
    http.MaxReceivedMessageSize = 1024 * 1024;


    thanks

    Marshall
    Wednesday, November 28, 2007 6:03 PM
  • Hi Caros,

     

    I am getting following error / exception on line Stream reqStream = req.GetRequestStream();

    "This stream does not support seek operations."

     

    //Initialisation

    HttpWebRequest WebReq = (HttpWebRequest)WebRequest.Create("http://localhost:8888/products/create");

    //Our method is post, otherwise the buffer (postvars) would be useless

    WebReq.Method = "POST";

    //We use form contentType, for the postvars.

    WebReq.ContentType = "application/soap+xml; charset=utf-8";

     

    string reqBody = @"<Product xmlns=\""http://schemas.datacontract.org/2004/07/ProductRESTLibrary\"" xmlns:i=\""http://www.w3.org/2001/XMLSchema-instance\""><ProductID>88</ProductID><ProductName>New Product</ProductName><UnitPrice>19.0000</UnitPrice><Uri>http://localhost/ProductREST/Product.svc/2</Uri></Product>";

     

    byte[] reqBodyBytes = Encoding.UTF8.GetBytes(reqBody);

    Stream reqStream = WebReq.GetRequestStream();

    reqStream.Write(reqBodyBytes, 0, reqBodyBytes.Length);

    reqStream.Close();

    HttpWebResponse WebResp = (HttpWebResponse)WebReq.GetResponse();

    //Let's show some information about the response

    Console.WriteLine(WebResp.StatusCode);

    Console.WriteLine(WebResp.Server);

    //Now, we read the response (the string), and output it.

    Stream Answer = WebResp.GetResponseStream();

    StreamReader _Answer = new StreamReader(Answer);

    Console.WriteLine(_Answer.ReadToEnd());

    Sunday, December 02, 2007 5:27 PM