Getting a POST endpoint to work in self-hosted (WebServiceHost) C# webservice?
Asked Answered
S

2

1

So, I have been messing around with webservices for a while now, and I keep getting back to some basics, that I never seem to get right.

Question 1:

When using a WebServiceHost in .NET/C#, you can define a method/endpoint as using GET/POST/etc. Setting up a GET-method is easy and it works pretty much directly, and its easy to understand how it works. For example:

[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "/PutMessage/{jsonString}", BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
string PutMessage(string jsonString);

If I call http:///MyWebService/PutMessage/{MyJsonString} I get passed on the the method, and all is well (more or less).

But then what does it mean when I define this as a POST instead?

[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "/PutMessage/{jsonString}", BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
string PutMessage(string jsonString);

What does the UriTemplate do here? If I do a POST, I expect the data to be contained not in the URI, but in the "data section" of the post. But do I define the variable name in the data section? How does the WebServiceHost/.NET know that what is contained in the "data section" of the post is to be put into the variable jsonString? How do I post the data from the client side (not C#, let's say JQuery instead) so that it is interpreted correctly on the serer side?

(And how does the WebMessageFormat affet things? I have read everywhere about this (MSDN, Stackoverflow etc) but haven't found a clear and good answer.)

Question 2:

In my attempts to understand this, I thought I'd make a very simple POST-method, like this:

[OperationContract]
[WebInvoke]
string PutJSONRequest(string pc);

I then try call this method using Fiddler, but that does not work at all. I just get a 400 error back, saying "HTTP/1.1 400 Bad Request". I have a breakpoint on the very first line in the code of the method, and the method itself contains nothing:

public string PutJSONRequest(string pc)
{
    return null;
}

Again, how does .NET know that what I POSTed using Fiddler should be contained in the "string pc"? How does it interpret it as a string, and what type of string (UT8, ASCII etc)?

This is the RAW HTTP request, sent from Fiddler:

POST http://<myip>:8093/AlfaCustomerApp/PutJSONRequest HTTP/1.1
User-Agent: Fiddler
Host: <myip>:8093
Content-Length: 3
Content-type: application/x-www-form-urlencoded; charset=UTF-8

asd

and it doesnt matter what type of Content-type I use, as far as I can see.

The response is a standard-thing, that I am not in control of myself:

HTTP/1.1 400 Bad Request
Content-Length: 1165
Content-Type: text/html
Server: Microsoft-HTTPAPI/2.0
Date: Mon, 15 Oct 2012 15:45:02 GMT

[then HTML code]

Any help would be appreciated. Thanks.

Solidstate answered 15/10, 2012 at 15:53 Comment(0)
S
3

I found out the answer. This is how to define a method, that can take the raw data sent in a HTTP POST:

[OperationContract]
[WebInvoke(BodyStyle=WebMessageBodyStyle.Bare)]
Stream PutMessage(Stream data);

and the implementation is like this:

public Stream PutMessage(Stream data)
{
    byte[] buffer = new byte[65535];

    int bytesRead, totalBytes = 0;
    do
    {
        bytesRead = data.Read(buffer, 0, 65535);
        totalBytes += bytesRead;
    }
    while (bytesRead > 0);

    // Then you could interpret it as a String for example:
    string jsonString = Encoding.UTF8.GetString(buffer, 0, totalBytes);
    // yada yada
}
Solidstate answered 29/11, 2012 at 21:10 Comment(2)
Any reason for the downvote? This answer works like a charm, and behaves just as expected.Solidstate
Having just the stream is the most fexible approach. I recommend this answer!Chong
M
5

I think a simple code can answer all your questions

Task.Factory.StartNew(()=>StartServer());
Thread.Yield();
StartClient();

void StartServer()
{
    Uri uri = new Uri("http://localhost:8080/test");
    WebServiceHost host = new WebServiceHost(typeof(WCFTestServer), uri);
    host.Open();
}

void StartClient()
{
    try
    {
        WebClient wc = new WebClient();

        //GET
        string response1 = wc.DownloadString("http://localhost:8080/test/PutMessageGET/abcdef");
        //returns: "fedcba"

        //POST with UriTemplate
        string response2 = wc.UploadString("http://localhost:8080/test/PutMessagePOSTUriTemplate/abcdef",
                                            JsonConvert.SerializeObject(new { str = "12345" }));
        //returns: fedcba NOT 54321


        //POST with BodyStyle=WebMessageBodyStyle.WrappedRequest
        //Request: {"str":"12345"}
        wc.Headers["Content-Type"] = "application/json";
        string response3 = wc.UploadString("http://localhost:8080/test/PutMessagePOSTWrappedRequest",
                                            JsonConvert.SerializeObject(new { str="12345" }));

        //POST with BodyStyle=WebMessageBodyStyle.Bare
        wc.Headers["Content-Type"] = "application/json";
        string response4 = wc.UploadString("http://localhost:8080/test/PutMessagePOSTBare", "12345" );

    }
    catch (WebException wex)
    {
        Console.WriteLine(wex.Message);
    }
}

[ServiceContract]
public class WCFTestServer
{
    [OperationContract]
    [WebInvoke(Method = "GET", UriTemplate = "/PutMessageGET/{str}", BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
    public string PutMessageGET(string str)
    {
        return String.Join("", str.Reverse());
    }

    [OperationContract]
    [WebInvoke(Method = "POST", UriTemplate = "/PutMessagePOSTUriTemplate/{str}", BodyStyle = WebMessageBodyStyle.WrappedRequest, ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
    public string PutMessagePOSTUriTemplate(string str)
    {
        return String.Join("", str.Reverse());
    }

    [OperationContract]
    [WebInvoke(Method = "POST", BodyStyle=WebMessageBodyStyle.WrappedRequest, ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
    public string PutMessagePOSTWrappedRequest(string str)
    {
        return String.Join("", str.Reverse());
    }

    [OperationContract]
    [WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
    public string PutMessagePOSTBare(string str)
    {
        return String.Join("", str.Reverse());
    }
}

PS: You can find the JsonConvert here

Masqat answered 15/10, 2012 at 18:34 Comment(10)
Thanks... So, in PutMessagePOSTUriTemplate - where does the JSON-part go?Solidstate
Would you mind posting the replies/answers to PutMessagePOSTWrappedRequest and PutMessagePOSTBare too?Solidstate
Lastly, if I try a simple POST method, as described in my question, the method on the server-side is never called.Solidstate
a) PutMessagePOSTUriTemplate: Server Ignores the json and uses the parameter in url b) Replies of : PutMessagePOSTWrappedRequest and PutMessagePOSTBare : "54321" c) I don't know how you use fiddler to POST messages but it can be related to Content-Type (application/json)Masqat
Fiddler has a "composer" function, where you can "compose" a POST request manually. Thats the one I am using. So, in a) the data of the POST is ignored? How is the parameters sent, query string somehow?Solidstate
Did you see the url http://localhost:8080/test/PutMessagePOSTUriTemplate/**abcdef**Masqat
yes, I understand how to build the request. But I also am a bit puzzled what it actually means to do a POST, but then sending the data in teh query string. Doesnt that take away the whole point of doing a POST? Perhaps the different between GET and POSt is nothing more than the words "POST" and "GET", but it has really no other meaning?Solidstate
@Solidstate I think this is a implementation decision. If you say "I will pass the parameter in the url" but send it both in url and in message, server can pick whichever it wants...Masqat
Sorry for very late reply. I think I sort of understand it. If I pass data in the "data" area of a POST, then its "raw" format, so I need my own protocol there to interpret what is being sent. If I use the query string, then I get the different variables, like &var1=somevalue. I just need to figure out how to get the data in the POST so that it works...Solidstate
I still havent figured out how to access the "data" in a POST. The examples above assumes that there is a variable called "str", but where is that variable defined? Where does it come from? How does .NET know to place the "data" in the "string str"?Solidstate
S
3

I found out the answer. This is how to define a method, that can take the raw data sent in a HTTP POST:

[OperationContract]
[WebInvoke(BodyStyle=WebMessageBodyStyle.Bare)]
Stream PutMessage(Stream data);

and the implementation is like this:

public Stream PutMessage(Stream data)
{
    byte[] buffer = new byte[65535];

    int bytesRead, totalBytes = 0;
    do
    {
        bytesRead = data.Read(buffer, 0, 65535);
        totalBytes += bytesRead;
    }
    while (bytesRead > 0);

    // Then you could interpret it as a String for example:
    string jsonString = Encoding.UTF8.GetString(buffer, 0, totalBytes);
    // yada yada
}
Solidstate answered 29/11, 2012 at 21:10 Comment(2)
Any reason for the downvote? This answer works like a charm, and behaves just as expected.Solidstate
Having just the stream is the most fexible approach. I recommend this answer!Chong

© 2022 - 2024 — McMap. All rights reserved.