【问题标题】:InvalidOperationException when calling a WCF RESTful service method with multiple arguments?调用具有多个参数的 WCF RESTful 服务方法时出现 InvalidOperationException?
【发布时间】:2015-01-20 00:13:57
【问题描述】:

我有一个名为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>

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

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>

我使用Postman 来测试发送以下 JSON 数据的服务并且它工作正常:

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

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

System.InvalidOperationException:合约“IAuthenticationService”的操作“登录”指定要序列化的多个请求主体参数,而无需任何包装器元素。最多一个 body 参数可以在没有包装元素的情况下被序列化。删除额外的正文参数或将 WebGetAttribute/WebInvokeAttribute 上的 BodyStyle 属性设置为 Wrapped。

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

注意以下几点:

  • 我已经在 Stackoverflow 和互联网上搜索解决方案。几乎所有的解决方案都是关于设置BodyStyle。它可能帮助了其他人,但对我没有多大帮助。
  • LoginLoginAsync 都试过了;结果是一样的
  • 我通过 Visual Studio 2013(终极版,如果您想知道的话)中的“添加服务引用”添加它来引用该服务
  • 我知道我可以使用其他方式调用该服务;例如,HttpClient,但我想知道的是为什么自动生成的客户端不起作用

【问题讨论】:

  • 在更改为BodyStyle 后,您是否已经在客户端更新了您的服务参考?
  • 我认为,进入的 json 请求不正确。你能和提琴手核对一下,看看请求是什么样的。可能是请求格式不正确...
  • @khlr,是的,我更改了服务并更新了参考至少 739,000 次 :)
  • @Saravanan,我会检查一下。我不确定它是否能到达服务器。当客户端(控制台应用程序)尝试调用该方法时,会在客户端(控制台应用程序)中引发异常。
  • @Saravanan,正如我所怀疑的,它甚至没有到达服务器。

标签: c# json wcf rest


【解决方案1】:

这就是你应该调用你的服务的方式。

    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();
                }

            }
        }
    }

【讨论】:

  • 为什么“应该”?为什么我不能只使用自动生成的客户端?我知道我可以通过 Postman、Fiddler、HttpWebRequestHttpClient 和其他数千种方式成功调用该服务。我要问的是“为什么它不起作用”而不是“我还能如何消费它”。无论如何,我在最后更新了注释以避免混淆。
  • 很遗憾,您无法使用自动生成的客户端调用 REST 服务。
  • 这是基于经验还是官方记录的限制?如果是后者,请您指导我到源头吗?
  • 与 SOAP 端点(如 BasicHttpBinding、WSHttpBinding 等)不同,SOAP 端点(如 BasicHttpBinding、WSHttpBinding 等)可以通过端点中所有操作/参数的信息来公开有关自身的元数据(WSDL 或 Mex),目前没有标准方法公开非 SOAP 端点的元数据,例如基于 webHttpBinding 的端点。因此自动生成的代理类对调用 REST 服务没有用处。
【解决方案2】:

添加到 Login/LoginAsync 客户端合同方法

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

因此您的客户合同将如下所示:

    [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);
}

客户合同是自动生成的。因此,您应该在每次更新服务参考后执行此操作。 enter link description here

【讨论】:

  • 你是说我需要修改自动生成的代理?为什么?这是一个已知问题/限制吗?你能指导一下官方(或其他可靠的)来源吗?
  • 我找不到任何关于 Restful WCF Proxy 的官方信息。你可以阅读这个link
  • 谢谢。这是一种解决方法,在急需使用自动生成的情况下很有用(我不是)。我想我现在确信这是一个限制。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-06
  • 2011-07-28
  • 2011-11-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多