45

Im trying to transmit a string from client to ASP.NET MVC4 application.

But I can not receive the string, either it is null or the post method can not be found (404 error)

Client Code to transmit the string (Console Application):

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 Controller:

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

In that case I'm able to call the "Post" method, but "value" is NULL. If I change the method signature to (string value) than it will never called.

Even "without" the [Authorize] setting it has the same strange behavior. -> So it has nothing to do with the user authentication.

Any ideas what I'm doing wrong? I'm grateful for any help.

Ian R. O'Brien
  • 6,682
  • 9
  • 45
  • 73
user1011394
  • 1,656
  • 6
  • 28
  • 41

7 Answers7

62

You seem to have used some [Authorize] attribute on your Web API controller action and I don't see how this is relevant to your question.

So, let's get into practice. Here's a how a trivial Web API controller might look like:

public class TestController : ApiController
{
    public string Post([FromBody] string value)
    {
        return value;
    }
}

and a consumer for that matter:

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

You will undoubtedly notice the [FromBody] decoration of the Web API controller attribute as well as the = prefix of the POST data om the client side. I would recommend you reading about how does the Web API does parameter binding to better understand the concepts.

As far as the [Authorize] attribute is concerned, this could be used to protect some actions on your server from being accessible only to authenticated users. Actually it is pretty unclear what you are trying to achieve here.You should have made this more clear in your question by the way. Are you are trying to understand how parameter bind works in ASP.NET Web API (please read the article I've linked to if this is your goal) or are attempting to do some authentication and/or authorization? If the second is your case you might find the following post that I wrote on this topic interesting to get you started.

And if after reading the materials I've linked to, you are like me and say to yourself, WTF man, all I need to do is POST a string to a server side endpoint and I need to do all of this? No way. Then checkout ServiceStack. You will have a good base for comparison with Web API. I don't know what the dudes at Microsoft were thinking about when designing the Web API, but come on, seriously, we should have separate base controllers for our HTML (think Razor) and REST stuff? This cannot be serious.

Community
  • 1
  • 1
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 2
    Very interesting, if I use your client code, it works. It works with var data = "=..."; If I remove the "=" it doesnt work. – user1011394 Dec 07 '12 at 22:11
  • 2
    @user1011394, yeah, it's a WTF! Checkout [ServiceStack](http://www.servicestack.net/). You're gonna love it. – Darin Dimitrov Dec 07 '12 at 22:14
  • the '=' is apart of the required POST data formatting. Normally this is handled behind the scenes with Model Binding but is required when manually injecting test values. – NickSuperb Dec 07 '12 at 23:28
  • ServiceStack is great for people who want to forget that they are using HTTP, Web Api is great for people who want to take advantage of everything that HTTP has to offer application developers. – Darrel Miller Dec 08 '12 at 01:35
  • ...and there are some very valid technical reasons for having controllers that are not dependent on System.Web. Having two base controllers in the short term is hacky but it will pay off in the long term. – Darrel Miller Dec 08 '12 at 01:37
  • 2
    @DarrelMiller ServiceStack does HTTP very well (not sure what is making you think otherwise), you can [Customize the HTTP Response](https://github.com/ServiceStack/ServiceStack/wiki/Customize-HTTP-Responses) how you like, [return any Response you wish](https://github.com/ServiceStack/ServiceStack/wiki/Service-return-types) and its [HTML Support is implemented as it should be in a Web Fx](http://razor.servicestack.net) i.e. HTML is just another Content-Type. Not sharing base classes is not good design, it's a self-imposed artificial restriction. SS does HTML + Self-Host using same interfaces. – mythz Dec 08 '12 at 07:14
  • @mythz I didn't say it didn't do HTTP very well. I said it did something well that Web API doesn't. That is, allow people to forget they are using HTTP. From my perspective, that was a non-goal for Web API. On the other hand, in a strongly typed language like C#, I think strongly typed headers are big part of "doing HTTP well" in a framework. – Darrel Miller Dec 08 '12 at 17:57
  • 2
    @DarrelMiller just so no one thinks otherwise, ServiceStack also includes typed [HttpMethods](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Common/ServiceClient.Web/HttpMethod.cs) and [HttpHeaders](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Common/Web/HttpHeaders.cs) that's free to substitute instead of strings for those that prefer the use of typed constants. – mythz Dec 08 '12 at 18:02
  • @mythz An enum of header names is not quite the same as strongly typed header values. – Darrel Miller Dec 08 '12 at 18:07
  • 3
    @DarrelMiller There are constants for everything that's likely to be used by application developers. If you're referring to the hashset of all HTTP Verbs, that's the optimal storage used by the Framework to determine if a symbol is a HTTP method. – mythz Dec 08 '12 at 18:40
36

Web API works very nicely if you accept the fact that you are using HTTP. It's when you start trying to pretend that you are sending objects over the wire that it starts to get messy.

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

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

        }

    }

This controller will handle a HTTP request, read a string out of the payload and return that string back.

You can use HttpClient to call it by passing an instance of StringContent. StringContent will be default use text/plain as the media type. Which is exactly what you are trying to pass.

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

    }
Darrel Miller
  • 139,164
  • 32
  • 194
  • 243
  • +1 - I love Web API. I just learnt about `HttpResponseMessage` today and how you can create the response manually bypassing content-negotiation and serialization. I was wondering if there was something like that I could do for the request. Lo and behold here it is! – Karthic Raghupathi Aug 23 '13 at 02:34
  • 2
    "accept the fact that you are using HTTP" Love it. :) – Alex Beynenson Feb 06 '14 at 20:46
  • 1
    This is the best answer, since it doesn't require the client to do anything weird with the posted string. – StuartQ May 28 '15 at 10:24
4

For WebAPI, here is the code to retrieve body text without going through their special [FromBody] binding.

public class YourController : ApiController
{
    [HttpPost]
    public HttpResponseMessage Post()
    {
        string bodyText = this.Request.Content.ReadAsStringAsync().Result;
        //more code here...
    }
}
Jeson Martajaya
  • 6,996
  • 7
  • 54
  • 56
3

I use this code to post 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;
    }

In regards to your code, try it without the content.Type (request.ContentType = "application/x-www-form-urlencoded";)

update

I believe the problem lies with how you are trying to retrieve the value. When you do a POST and send bytes via the Stream, they will not be passed into the action as a parameter. You'll need to retrieve the bytes via the stream on the server.

On the server, try getting the bytes from stream. The following code is what I use.

     /// <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;
     }
Chuck Conway
  • 16,287
  • 11
  • 58
  • 101
  • 1
    Hi Chuck, thank you for your answer. I tried it. But the result is the same as with my code. On the server side the "Post" method is executed, but the value is NULL. – user1011394 Dec 07 '12 at 21:32
  • 1
    Your suggestion to retrieve the InputStream on the server side also doesn't work for me... Do we have any other possibilites to retrieve just a string instead of byte[] - that's all I need? – user1011394 Dec 07 '12 at 21:44
  • 1
    I am not sure what else to suggest. The above code, is the code we use to accomplish what you are attempting to achieve. – Chuck Conway Dec 07 '12 at 21:47
3

Darrel is of course right on with his response. One thing to add is that the reason why attempting to bind to a body containing a single token like "hello".

is that it isn’t quite URL form encoded data. By adding “=” in front like this:

=hello

it becomes a URL form encoding of a single key value pair with an empty name and value of “hello”.

However, a better solution is to use application/json when uploading a string:

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

"Hello"

Using HttpClient you can do it as follows:

HttpClient client = new HttpClient();
HttpResponseMessage response = await client.PostAsJsonAsync(_baseAddress + "api/json", "Hello");
string result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);

Henrik

Henrik Frystyk Nielsen
  • 3,092
  • 1
  • 19
  • 17
  • This did not work for me, ended up with my parameter being null. Had to go with the form data method. Feels really hacky :/ – Alex Spence Jun 21 '13 at 21:07
2

i meet this problem, and find this article. http://www.jasonwatmore.com/post/2014/04/18/Post-a-simple-string-value-from-AngularJS-to-NET-Web-API.aspx

The solution I found was to simply wrap the string value in double quotes in your js post

works like a charm! FYI

Bill.Zhuang
  • 657
  • 7
  • 16
  • 1
    This worked for me, but check the top comment on the article for the "why". In short, `JSON.stringify(value)` is better if you are using AngularJS. – Christopher Jun 16 '15 at 16:01
-2
([FromBody] IDictionary<string,object> data)
Nea
  • 227
  • 2
  • 3