Search code examples
c#wcf

WCF to WCF. The remote server returned an unexpected response: (400) Bad Request


We have two WCF services and first with api web method. In interface created like this:

[OperationContract]
[WebInvoke(
   Method = "POST",
   RequestFormat = WebMessageFormat.Json,
   ResponseFormat = WebMessageFormat.Json,
   BodyStyle = WebMessageBodyStyle.WrappedRequest
)]
RequestParams GetAccount(RequestParams requestParams);

I want to call this method from second WCF service. Like this:

try
{
   var myBinding = new WebHttpBinding();
   var myEndpoint = new EndpointAddress(endpointAddress);

   using (var factory = new ChannelFactory<IAccount>(myBinding, myEndpoint))
   {
       IAccount apiService = null;

       try
       {
           factory.Endpoint.Behaviors.Add(new WebHttpBehavior());
           apiService = factory.CreateChannel();

           result = apiService.GetAccount(requestParams);

           ((ICommunicationObject)apiService).Close();
           factory.Close();
        }
        catch (Exception ex)
        {
           Fatal(ex);
           (apiService as ICommunicationObject)?.Abort();
        }
    }
}

When I check GetAccount method with use Postman, it works and return expected value. But when I try to call from second service method is not called (checked in debug mode) and in catch in exception - (400) Bad Request. I think this error is related with json format for returned value. When I try to change to xml in interface, method is called (checked with debug mode). How I can configure WebHttpBehaviour for use json? Thanks


Solution

  • This is actually caused by WebMessageBodyStyle.WrappedResponse. If you change WebMessageBodyStyle to Wrapped, WCF will encapsulate the object again. Here is a demo:

     [OperationContract]
            [WebInvoke(
            Method = "POST",
            ResponseFormat = WebMessageFormat.Json, RequestFormat =
            WebMessageFormat.Json,
            BodyStyle = WebMessageBodyStyle.WrappedResponse
     )]
            string GetAccount(UserData requestParams);
    

    WrappedResponse will encapsulate the response.

    enter image description here

    Because your WebMessageBodyStyle is WrappedRequest, the object you send on the client side must be encapsulated, otherwise 400 Bad Request will appear.

    I think the best solution is to set WebMessageBodyStyle to Bare, Here is a demo:

    [ServiceContract]
        public interface IService1
        {
            [OperationContract]
            [WebInvoke(
            Method = "POST",
            ResponseFormat = WebMessageFormat.Json, RequestFormat =
            WebMessageFormat.Json,
            BodyStyle = WebMessageBodyStyle.Bare
     )]
            string GetAccount(UserData requestParams);
        }
        [DataContract(Name = "user")]
        public class UserData
        {
            [DataMember(Name = "Name")]
            public string Name { get; set; }
            [DataMember(Name = "Password")]
            public string Password { get; set; }
            [DataMember(Name = "Email")]
            public string Email { get; set; }
        }
        public class Service1 : IService1
        {
            public string GetAccount(UserData requestParams)
            {
                return "OK";
            }
        }
        class Program
        {
            static void Main(string[] args)
            {
                ServiceHost selfHost = new ServiceHost(typeof(Service1));
                selfHost.Open();
                Console.WriteLine("Service Open");
                Console.ReadKey();
                selfHost.Close();
            }
        }
    

    This is Program.cs.

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <startup>
            <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
        </startup>
    
        <system.serviceModel>
            <services>
    
                <service name="ConsoleApp113.Service1" behaviorConfiguration="ServiceBehavior">
                    <host>
                        <baseAddresses>
                            <add baseAddress="http://localhost:8012/ServiceModelSamples/service"/>
                        </baseAddresses>
                    </host>
    
                    <endpoint address=""
                              binding="webHttpBinding"
                              contract="ConsoleApp113.IService1"
                              behaviorConfiguration="ESEndPointBehavior" />
                </service>
            </services>
    
    
            <behaviors>
                <endpointBehaviors>
                    <behavior name="ESEndPointBehavior">
                        <webHttp helpEnabled="true"/>
                    </behavior>
                </endpointBehaviors>
    
                <serviceBehaviors>
                    <behavior name="ServiceBehavior">
                        <serviceMetadata/>
                    </behavior>
                </serviceBehaviors>
    
            </behaviors>
    
        </system.serviceModel>
    </configuration>
    

    This is web.config.

    class Program
        {
            [ServiceContract]
            public interface IService1
            {
                [OperationContract]
                [WebInvoke(
                Method = "POST",
                ResponseFormat = WebMessageFormat.Json, RequestFormat =
                WebMessageFormat.Json,
                BodyStyle = WebMessageBodyStyle.Bare
         )]
                string GetAccount(UserData requestParams);
            }
            [DataContract(Name = "user")]
            public class UserData
            {
                [DataMember(Name = "Name")]
                public string Name { get; set; }
                [DataMember(Name = "Password")]
                public string Password { get; set; }
                [DataMember(Name = "Email")]
                public string Email { get; set; }
            }
            static void Main(string[] args)
            {
                var myBinding = new WebHttpBinding();
                var myEndpoint = new EndpointAddress("http://localhost:8012/ServiceModelSamples/service");
    
                using (var factory = new ChannelFactory<IService1>(myBinding, myEndpoint))
                {
                        IService1 apiService = null;
                        factory.Endpoint.Behaviors.Add(new WebHttpBehavior());
                        apiService = factory.CreateChannel();
                        UserData userData = new UserData();
                        userData.Email = "Test";
                        userData.Name = "Test";
                        userData.Password = "Test";
                        string result = apiService.GetAccount(userData);
                        ((ICommunicationObject)apiService).Close();
                        factory.Close();
                        Console.WriteLine(result);
                        Console.ReadLine();
                  
                }
            }
        }
    

    This is the client code.

    enter image description here

    UPDATE:

    Unfortunately, there is no such setting in WCF. But you can encapsulate a wrapped type object yourself:

        [DataContract]
        public class GetAccount {
            [DataMember(Name = "RequestParams ")]
            public RequestParams requestParams ;
        }
    

    You can encapsulate RequestParams into GetAccount, and then send GetAccount directly to the server. So in order to successfully call the WCF service you have to modify the service interface:

    [OperationContract]
    [WebInvoke(
       Method = "POST",
       RequestFormat = WebMessageFormat.Json,
       ResponseFormat = WebMessageFormat.Json,
       BodyStyle = WebMessageBodyStyle.WrappedRequest
    )]
    RequestParams GetAccount(GetAccount requestParams);
    

    enter image description here