Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WCF WebInvoke which can accept content-type: text/plain?

I'm writing a WCF REST service to receive AWS SNS Notification Message with my WCF REST Service.

However, WCF REST only supports XML and JSON, but because of legacy reasons Amazon SNS posts their notifications with the Content-Type: text/plain; charset=UTF-8 header, according to the Amazon documentation:

POST / HTTP/1.1
Content-Type: text/plain; charset=UTF-8
// ...

{
  "Type" : "Notification",
  // ...
  "UnsubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=Unsubscribe&..."
}

When I call my service with this "text/plain" content type like Amazon, there is an error which says:

Request Error.

The server encountered an error processing the request. The exception message is '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.'. See server logs for more details.

My current code:

public interface MyServiceInterface
{
    [OperationContract]
    [WebInvoke(Method = "POST", UriTemplate = "/AmazonIPChanges")]
    Task AmazonIPChanges(SNSNotificationMessage data);
}

[DataContract]
public class SNSNotificationMessage
{
    [DataMember]
    public string Type { get; set; }
    // ...
    [DataMember]
    public string UnsubscribeURL { get; set; }
} 

The DataContract maps to the Amazon SNS message. This code is working when I perform a POST with content-type "application/json", but how can I let it accept Amazon's text/plain content-type?

like image 285
Kerim Doruk Akıncı Avatar asked Oct 18 '22 12:10

Kerim Doruk Akıncı


1 Answers

You can fix this, as the error message indicates, by creating and applying a custom WebContentTypeMapper. It should look like this:

namespace StackOverflow36216464
{
    public class RawContentTypeMapper : WebContentTypeMapper
    {
        public override WebContentFormat GetMessageFormatForContentType(string contentType)
        {
            switch (contentType.ToLowerInvariant())
            {
                case "text/plain":
                case "application/json":
                    return WebContentFormat.Json;
                case "application/xml":
                    return WebContentFormat.Xml;
                default:
                    return WebContentFormat.Default;
            }
        }
    }
}

This one interprets the content-type of the request, and returns the appropriate WebContentFormat enum member.

You can then apply it to your service in the form of a custom binding:

<system.serviceModel>
    <bindings>
        <customBinding>
            <binding name="textPlainToApplicationJson">
                <webMessageEncoding webContentTypeMapperType="StackOverflow36216464.RawContentTypeMapper, StackOverflow36216464, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
                <httpTransport manualAddressing="true" />
            </binding>
        </customBinding>
    </bindings>
    <behaviors>
        <serviceBehaviors>
            <behavior name="debugServiceBehavior">
                <serviceDebug includeExceptionDetailInFaults="true" />
            </behavior>
        </serviceBehaviors>
        <endpointBehaviors>
            <behavior name="restEndpointBehavior">
                <webHttp />
            </behavior>
        </endpointBehaviors>
    </behaviors>
    <services>
        <service
          name="StackOverflow36216464.Service1"
          behaviorConfiguration="debugServiceBehavior">
            <host>
                <baseAddresses>
                    <add baseAddress="http://localhost:65393/"/>
                </baseAddresses>
            </host>
            <endpoint address=""
                  binding="customBinding"
                  contract="StackOverflow36216464.IService1"
                  behaviorConfiguration="restEndpointBehavior"
                  bindingConfiguration="textPlainToApplicationJson"/>        
        </service>
    </services>
</system.serviceModel>

The relevant part being the <customBinding> element, where the custom mapper is configured, and the servcices/service/endpoint/bindingConfiguration attribute where it is applied.

like image 130
CodeCaster Avatar answered Oct 21 '22 09:10

CodeCaster