调用具有多个参数的WCF RESTful服务方法时出现InvalidOperationException?

时间:2021-07-27 16:16:50

I have the following code for a service called AuthenticationService:

我有一个名为AuthenticationService的服务的代码如下:

IAuthenticationService.cs

[ServiceContract]
public interface IAuthenticationService
{
    [OperationContract]
    [WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
    bool Login(string username, string password, string applicationName);
}

AuthenticationService.svc.cs

public sealed class AuthenticationService : IAuthenticationService
{
    public bool Login(string username, string password, string applicationName)
    {
        // TODO: add the logic to authenticate the user

        return true;
    }
}

Web.config

<system.serviceModel>
    <!-- START: to return JSON -->
    <services>
        <service name="ImageReviewPoc.Service.AuthenticationService">
            <endpoint contract="ImageReviewPoc.Service.Contracts.IAuthenticationService" binding="webHttpBinding" behaviorConfiguration="jsonBehavior"/>
        </service>
    </services>
    <!-- END: to return JSON -->
    <behaviors>
        <!-- START: to return JSON -->
        <endpointBehaviors>
            <behavior name="jsonBehavior">
                <webHttp/>
            </behavior>
        </endpointBehaviors>
        <!-- END: to return JSON -->
        <serviceBehaviors>
            <behavior>
                <serviceMetadata httpGetEnabled="true" httpsGetEnabled="false"/>
                <serviceDebug includeExceptionDetailInFaults="false"/>
            </behavior>
        </serviceBehaviors>
    </behaviors>
    <protocolMapping>
        <add scheme="http" binding="webHttpBinding"/>
    </protocolMapping>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/>
</system.serviceModel>

and I have the following console application that consumes the service:

我有以下控制台应用程序使用该服务:

Program.cs

internal class Program
{
    private static void Main(string[] args)
    {
        Login().Wait();
    }

    private static async Task Login()
    {
        var client = new AuthenticationServiceClient();
        var ok = await client.LoginAsync("User", "Password!", "Console Application");
        client.Close();

        Console.WriteLine(ok);
    }
}

App.config

<system.serviceModel>
    <client>
        <endpoint address="http://localhost:62085/AuthenticationService.svc/"
         binding="webHttpBinding"
         contract="AuthenticationServiceReference.IAuthenticationService"
         kind="webHttpEndpoint" />
    </client>
</system.serviceModel>

I used Postman to test the service sending the following JSON data and it worked:

我使用Postman来测试发送以下JSON数据的服务,并且它有效:

{"username": "reviewer", "password": "456", "applicationName": "123"}

However, when I tested the service using the console application I got an

但是,当我使用控制台应用程序测试服务时,我得到了一个

System.InvalidOperationException: Operation 'Login' of contract 'IAuthenticationService' specifies multiple request body parameters to be serialized without any wrapper elements. At most one body parameter can be serialized without wrapper elements. Either remove the extra body parameters or set the BodyStyle property on the WebGetAttribute/WebInvokeAttribute to Wrapped.

System.InvalidOperationException:合同'IAuthenticationService'的操作'Login'指定要序列化的多个请求正文参数,而不包含任何包装元素。最多可以在没有包装元素的情况下序列化一个body参数。删除额外的body参数或将WebGetAttribute / WebInvokeAttribute上的BodyStyle属性设置为Wrapped。

As you can see from IAuthenticationService.cs code, I already set BodyStyle to Wrapped. Can someone guide me to what I'm doing wrong here?

从IAuthenticationService.cs代码中可以看出,我已经将BodyStyle设置为Wrapped。有人可以指导我在这里做错了吗?

Note the following:

请注意以下事项:

  • I already search * and the internet for a solution. Almost all the solutions are about setting BodyStyle. It may have helped others but didn't do much to me.
  • 我已经在*和互联网上搜索解决方案了。几乎所有的解决方案都是关于设置BodyStyle。它可能对其他人有所帮助,但对我没有太大作用。

  • I tried both Login and LoginAsync; it's the same result
  • 我尝试了Login和LoginAsync;这是相同的结果

  • I referenced the service by adding it through "Add Service Reference" in Visual Studio 2013 (Ultimate edition, if you care to know)
  • 我通过Visual Studio 2013中的“添加服务引用”添加它来引用该服务(终极版,如果你想知道的话)

  • I know I can call the service using other ways; e.g., HttpClient, but what I want to know is why the auto-generated client doesn't work
  • 我知道我可以用其他方式调用该服务;例如,HttpClient,但我想知道的是为什么自动生成的客户端不起作用

2 个解决方案

#1


1  

This is how you should call your service.

这就是你应该如何打电话给你的服务。

    public void DoLogin()
    {
        string uri = "http://localhost:62085/AuthenticationService.svc/Login";
        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);

        request.Method = "POST";
        request.ContentType = "text/json";

        string data = "{\"username\": \"reviewer\", \"password\": \"456\", \"applicationName\": \"123\"}";

        System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
        byte[] bytes = encoding.GetBytes(data);

        request.ContentLength = bytes.Length;

        using (Stream requestStream = request.GetRequestStream())
        {
            // Send the data.
            requestStream.Write(bytes, 0, bytes.Length);
        }

        using (HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(x))
        {
            using(var responseStream = response.GetResponseStream())
            {
                using(var reader = new StreamReader(responseStream))
                {
                    //Here you will get response
                    string loginResponse = reader.ReadToEnd();
                }

            }
        }
    }

#2


0  

Add to Login/LoginAsync Client Contract Methods

添加到登录/ LoginAsync客户合同方法

 [WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]. 

So your client contract will look like:

所以你的客户合同将如下所示:

    [System.ServiceModel.ServiceContractAttribute(ConfigurationName="ServiceReference1.IAuthenticationService")]
public interface IAuthenticationService {

    [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IAuthenticationService/Login", ReplyAction="http://tempuri.org/IAuthenticationService/LoginResponse")]
    [WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
    bool Login(string username, string password, string applicationName);

    [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IAuthenticationService/Login", ReplyAction="http://tempuri.org/IAuthenticationService/LoginResponse")]
    [WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
    System.Threading.Tasks.Task<bool> LoginAsync(string username, string password, string applicationName);
}

Client contract is generated automatically. So you should do this each time after service reference is updated. enter link description here

客户合同自动生成。因此,每次更新服务引用后都应该这样做。在此输入链接描述

#1


1  

This is how you should call your service.

这就是你应该如何打电话给你的服务。

    public void DoLogin()
    {
        string uri = "http://localhost:62085/AuthenticationService.svc/Login";
        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);

        request.Method = "POST";
        request.ContentType = "text/json";

        string data = "{\"username\": \"reviewer\", \"password\": \"456\", \"applicationName\": \"123\"}";

        System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
        byte[] bytes = encoding.GetBytes(data);

        request.ContentLength = bytes.Length;

        using (Stream requestStream = request.GetRequestStream())
        {
            // Send the data.
            requestStream.Write(bytes, 0, bytes.Length);
        }

        using (HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(x))
        {
            using(var responseStream = response.GetResponseStream())
            {
                using(var reader = new StreamReader(responseStream))
                {
                    //Here you will get response
                    string loginResponse = reader.ReadToEnd();
                }

            }
        }
    }

#2


0  

Add to Login/LoginAsync Client Contract Methods

添加到登录/ LoginAsync客户合同方法

 [WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]. 

So your client contract will look like:

所以你的客户合同将如下所示:

    [System.ServiceModel.ServiceContractAttribute(ConfigurationName="ServiceReference1.IAuthenticationService")]
public interface IAuthenticationService {

    [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IAuthenticationService/Login", ReplyAction="http://tempuri.org/IAuthenticationService/LoginResponse")]
    [WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
    bool Login(string username, string password, string applicationName);

    [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IAuthenticationService/Login", ReplyAction="http://tempuri.org/IAuthenticationService/LoginResponse")]
    [WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
    System.Threading.Tasks.Task<bool> LoginAsync(string username, string password, string applicationName);
}

Client contract is generated automatically. So you should do this each time after service reference is updated. enter link description here

客户合同自动生成。因此,每次更新服务引用后都应该这样做。在此输入链接描述