将字符串POST到ASP.NET Web Api应用程序 - 返回null

45

我正在尝试从客户端传输一个字符串到ASP.NET MVC4应用程序。

但是我无法接收到该字符串,要么它为空,要么post方法找不到(404错误)

客户端传输字符串的代码(控制台应用程序):

HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost:49032/api/test");
request.Credentials = new NetworkCredential("user", "pw");
request.Method = "POST";
string postData = "Short test...";
byte[] byteArray = Encoding.UTF8.GetBytes(postData);
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = byteArray.Length;

Stream dataStream = request.GetRequestStream();
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();

WebResponse response = request.GetResponse();
Console.WriteLine(((HttpWebResponse)response).StatusDescription);
dataStream = response.GetResponseStream();

StreamReader reader = new StreamReader(dataStream);
string responseFromServer = reader.ReadToEnd();
Console.WriteLine(responseFromServer);
reader.Close();
dataStream.Close();
response.Close();
Console.ReadLine();

ASP.NET Web Api 控制器:

public class TestController : ApiController
{
    [Authorize]
    public String Post(byte[] value)
    {
        return value.Length.ToString();
    }
}

在这种情况下,我能够调用“Post”方法,但“value”为NULL。 如果我将方法签名更改为(string value),它将永远不会被调用。

即使“没有”[Authorize]设置,它仍然具有相同的奇怪行为。->因此,它与用户身份验证无关。

我做错了什么,有什么想法吗? 感谢任何帮助。

7个回答

63
您似乎在Web API控制器操作上使用了一些[Authorize]属性,我不明白这与您的问题有何关联。
那么,让我们开始实践。下面是一个微不足道的Web API控制器的示例:
public class TestController : ApiController
{
    public string Post([FromBody] string value)
    {
        return value;
    }
}

而且也适用于消费者:

class Program
{
    static void Main()
    {
        using (var client = new WebClient())
        {
            client.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
            var data = "=Short test...";
            var result = client.UploadString("http://localhost:52996/api/test", "POST", data);
            Console.WriteLine(result);
        }
    }
}

您无疑会注意到Web API控制器属性的[FromBody]修饰以及客户端POST数据的=前缀。我建议您阅读Web API如何进行参数绑定,以更好地理解相关概念。
[Authorize]属性而言,它可以用于保护服务器上某些操作仅对经过身份验证的用户可访问。实际上,您在这里要实现什么目标还不太清楚。顺便说一下,您应该在问题中更明确地说明您的意图。您是想了解ASP.NET Web API中的参数绑定工作原理(如果是,请阅读我链接的文章),还是尝试进行身份验证和/或授权?如果是后者,您可能会发现我在这个主题上写的以下帖子很有趣,可以为您入门。
如果你和我一样,在阅读了我提供的材料后,你会想:哇塞,我只需要向服务器端点POST一个字符串,就需要做这么多工作吗? 不可能。那么,请查看ServiceStack。 你将有一个好的比较基础来与Web API进行比较。 我不知道微软的人在设计Web API时在想什么,但是请认真考虑,我们应该为我们的HTML(想想Razor)和REST内容分别拥有不同的基本控制器吗? 这不可能是认真的。

2
非常有趣,如果我使用你的客户端代码,它可以工作。 它可以与 var data = "=..."; 一起工作。如果我删除“=”符号,它就不能工作了。 - user1011394
2
@user1011394,是的,这确实很神奇!请查看ServiceStack。你会喜欢它的。 - Darin Dimitrov
2
@DarrelMiller ServiceStack非常擅长处理HTTP(不确定是什么让您产生了其他想法),您可以自定义HTTP响应,以任何您喜欢的方式返回任何响应,并且其HTML支持已按Web Fx应该实现,即HTML只是另一种Content-Type。不共享基类不是好的设计,这是一种自我强加的人为限制。SS使用相同的接口进行HTML +自托管。 - mythz
2
@DarrelMiller 只是为了让大家不会误会,ServiceStack 还包括类型化的 HttpMethodsHttpHeaders,可以免费替代字符串使用,以满足那些偏好使用类型常量的人。 - mythz
3
应用程序开发人员使用的所有常量都是经过精心设计的。如果您正在引用所有HTTP动词的哈希集合,那么这是框架用来确定符号是否为HTTP方法的最佳存储方式。请注意,本文不包含解释或额外内容。 - mythz
显示剩余5条评论

36

如果你能接受使用HTTP协议的事实,Web API会非常好用。但是,当你开始试图假装在传输对象时,情况就会变得混乱。

 public class TextController : ApiController
    {
        public HttpResponseMessage Post(HttpRequestMessage request) {

            var someText = request.Content.ReadAsStringAsync().Result;
            return new HttpResponseMessage() {Content = new StringContent(someText)};

        }

    }

这个控制器会处理HTTP请求,从有效载荷中读取一个字符串并将该字符串返回。

您可以使用HttpClient通过传递StringContent的实例调用它。StringContent将默认使用text/plain作为媒体类型。这正是您要传递的内容。

    [Fact]
    public void PostAString()
    {

        var client = new HttpClient();

        var content = new StringContent("Some text");
        var response = client.PostAsync("http://oak:9999/api/text", content).Result;

        Assert.Equal("Some text",response.Content.ReadAsStringAsync().Result);

    }

+1 - 我喜欢 Web API。今天我刚学到了 HttpResponseMessage,以及如何手动创建响应,绕过内容协商和序列化。我想知道是否有类似的方法适用于请求。结果发现它就在这里! - Karthic Raghupathi
2
接受现实,你正在使用HTTP。喜欢它。 :) - Alex Beynenson
1
这是最好的答案,因为它不需要客户端对发布的字符串进行任何奇怪的操作。 - StuartQ

4

对于WebAPI,以下是检索正文文本的代码,无需经过特殊的[FromBody]绑定。

public class YourController : ApiController
{
    [HttpPost]
    public HttpResponseMessage Post()
    {
        string bodyText = this.Request.Content.ReadAsStringAsync().Result;
        //more code here...
    }
}

3
我使用这段代码来发布 HttpRequests。
/// <summary>
        /// Post this message.
        /// </summary>
        /// <param name="url">URL of the document.</param>
        /// <param name="bytes">The bytes.</param>
        public T Post<T>(string url, byte[] bytes)
    {
        T item;
        var request = WritePost(url, bytes);

        using (var response = request.GetResponse() as HttpWebResponse)
        {
            item = DeserializeResponse<T>(response);
            response.Close();
        }

        return item;
    }

    /// <summary>
    /// Writes the post.
    /// </summary>
    /// <param name="url">The URL.</param>
    /// <param name="bytes">The bytes.</param>
    /// <returns></returns>
    private static HttpWebRequest WritePost(string url, byte[] bytes)
    {
        ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true;

        HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url);
        Stream stream = null;
        try
        {
            request.Headers.Clear();
            request.PreAuthenticate = true;
            request.Connection = null;
            request.Expect = null;
            request.KeepAlive = false;
            request.ContentLength = bytes.Length;
            request.Timeout = -1;
            request.Method = "POST";
            stream = request.GetRequestStream();
            stream.Write(bytes, 0, bytes.Length);
        }
        catch (Exception e)
        {
            GetErrorResponse(url, e);
        }
        finally
        {
            if (stream != null)
            {
                stream.Flush();
                stream.Close();
            }
        }
        return request;
    }

关于你的代码,请尝试去掉content.Type (request.ContentType = "application/x-www-form-urlencoded";)。 更新 我认为问题出在如何检索值上。当您通过流发送字节进行POST时,它们不会作为参数传递到操作中。您需要在服务器上通过流检索字节。
在服务器上,尝试从流中获取字节。以下代码是我使用的。
     /// <summary> Gets the body. </summary>
     /// <returns> The body. </returns>
     protected byte[] GetBytes()
     {
       byte[] bytes;
        using (var binaryReader = new BinaryReader(Request.InputStream))
        {
            bytes = binaryReader.ReadBytes(Request.ContentLength);
        }

         return bytes;
     }

1
嗨,Chuck,谢谢你的回答。我试过了。但结果与我的代码相同。 在服务器端执行“Post”方法,但值为NULL。 - user1011394
1
你对在服务器端获取InputStream的建议对我也不起作用... 我们还有其他可能性只获取一个字符串而不是byte[]吗 - 这就是我所需要的全部吗? - user1011394
1
我不确定还有什么其他建议。上面的代码是我们用来实现你试图达到的目标的代码。 - Chuck Conway

3

Darrel的回答当然是正确的。值得注意的是,尝试绑定包含单个标记“hello”的正文是行不通的,因为它并不是URL编码形式的数据。通过在前面加上“=”像这样:

=hello

这里涉及到的是it技术,具体内容为将一个空名称和值为“hello”的键值对进行URL表单编码。

然而,更好的解决方案是在上传字符串时使用application/json格式:

POST /api/sample HTTP/1.1
Content-Type: application/json; charset=utf-8
Host: host:8080
Content-Length: 7

"Hello"

使用HttpClient,您可以按以下方式执行操作:
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.PostAsJsonAsync(_baseAddress + "api/json", "Hello");
string result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);

亨利克


这对我没有起作用,最终导致我的参数为空。不得不采用表单数据方法。感觉很不专业 :/ - Alex Spence

2

1
这对我有用,但请查看文章中的顶部评论以了解“为什么”。简而言之,如果您正在使用AngularJS,则JSON.stringify(value)更好。 - Chris

-2
([FromBody] IDictionary<string,object> data)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接