0

Here is a trivial example that is supposed to return "Hello World" string. However, a browser displays something like SGVsbG8gV29ybGQ=. Which is the right way to return plain text from an oldskul-style service?

please know that:

  1. I can't return a string: three Unicode characters will be automatically prepended and the legacy HTTP client won't be able to interoperate.

  2. I could return a Message, but still must keep parsing functionality to extract the data variable. Mixing Message and int types in the same method signature is not allowed AFAIK.

    [ServiceContract(SessionMode=SessionMode.NotAllowed)] 
    public interface IHello 
    {
      [WebGet(UriTemplate = "manager?data={data}")]
      [OperationContract]
      Byte[] DoIt(int data);
    }

    public class Hello : IHello { public Byte[] DoIt(int data) { return Encoding.ASCII.GetBytes("HelloWorld"); } }

UPDATE: It's not gibberish, but correctly encoded response. However, the format is not what I'd expect to receive. I've figured out (as gentlemen below suggest) that the ultimate control of the transport layer messaging format is gained with Message class. However, if I do use one - I loose the possibility of parsing the request (UriTemplate attribute). Hence, it would be great to know how to integrate the Message with UriRequest.

P.S. If the "clean" integration is not possible - than what's the most elegant workaround? Is there any code that is done behind the wire curtain that I can borrow and use in my implementation, please?

BreakPhreak
  • 10,940
  • 26
  • 72
  • 108

3 Answers3

1

That's not gibberish - it's the base64-encoded version of the ASCII bytes for "Hello World" (with a space).

When you ask a web service to transmit bytes, it will use base64... your web service client will perform the base64 decoding automatically, to get you back the original bytes.

(I wouldn't suggest using Encoding.ASCII, mind you.)

It's not clear what your "legacy HTTP client" is, or what it's expecting, so we can't really tell whether returning a byte array is actually the best answer or not.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thanks, so which is the right way to do that, please? Am I digging the correct direction at all? – BreakPhreak Sep 27 '10 at 15:33
  • I've also checked the result of what GetBytes() returns in the debugger. The array of bytes is returned and the values seem to be correct, such as arr[0] = 72 ('H'), arr[1] = 101 ('e') etc. It seems that the framework is mangling with the results. – BreakPhreak Sep 27 '10 at 15:39
  • The legacy code expects to receive a plain text/xml string. Let's assume that if "Hello World" is displayed in the browser and the content type is "text/ascii" - than we are on the right way. – BreakPhreak Sep 27 '10 at 15:41
  • @BreakPhreak: No, nothing's "mangling" the results. GetBytes() will be returning the result of base64-decoding the base64-encoded data, just as it should do. Basically if you try to get your web service to return an arbitrary array of bytes, this will happen. If you don't want that to happen, don't use a byte array. – Jon Skeet Sep 27 '10 at 16:08
  • I cleanly see the bytes array in the debugger and the values are correct. Can't get you about the random bytes, that's cleanly is NOT what I am asking about though :) If anything in the question is not clarified enough - feel welcome to ask; would be great to get and answer that points the right direction, I already know that I am doing something wrong :) – BreakPhreak Sep 27 '10 at 16:13
  • @BreakPhreak: For some reason I've lost the comment that I just wrote, but the gist of it was that you still seem convinced that the framework is doing something wrong: it's not. You asked it to transfer an opaque blob, so it's doing that, using Base64. If you use bytes, that's what will happen. If your legacy client can't handle that, you'll need to take a different approach. It sounds like it can't handle UTF-8 either... could you explain what it *can* handle? – Jon Skeet Sep 27 '10 at 16:27
  • Ah, no-no :) I am totally agreed that I am doing something wrong - maybe the "Gibberish" term causes that feeling. Yes, I need a different approach, being in a "winner" situation of hard deadlines and not-so-expert with WCF. The legacy client code sends raw HTTP request and expects to receive ASCII XML text. I already have that text in StringBuffer, all I need is to convert it to ASCII (only English letters and number etc of course) and still being able to utilize the UriTemplate functionality to parse the request. Does it all sound better now? – BreakPhreak Sep 28 '10 at 07:42
  • @BreakPhreak: Okay, so it sounds like you *possibly* just need to persuade WCF to use ASCII as the encoding instead of UTF-8. It's pretty weird for an XML parser not to accept UTF-8 though... it would be good if you could fix that. To be honest, I don't know the best way to change the server response here. One *possible* option is to insert a filter somewhere in the HTTP handler chain - read the XML document produced by WCF, and just rewrite it in ASCII. That's probably where I'd start looking, but it'll be ugly. – Jon Skeet Sep 28 '10 at 08:38
  • Yes, you are right - the best solution would be to modify/rewrite the legacy code, but it's not a viable option in production right now (though planned). I've figured out (as gentlemen below suggests) that the ultimate control of the transport layer messaging format is gained with Message class. However, if I do use one - I loose the possibility of parsing the request (UriTemplate attribute), which is not nice. Just clarifying my needs - in case someone will have an idea how to integrate the Message and UriRequest - would be great to know. – BreakPhreak Sep 28 '10 at 08:46
1

Googled for a proper way to integrate and found that people are stuck just as I am (please correct me if I am wrong on that). So far the following workaround works fine for me:

  public class Hello : IHello
  {
        [WebGet(UriTemplate = "manager")]
        [OperationContract]
        Message DoIt();
  }

  public class Hello : IHello
  {
        public Message DoIt()
        {
          var webContext = WebOperationContext.Current;
          var request = webContext.IncomingRequest;
          var queryParameters = request.UriTemplateMatch.QueryParameters;
          var data = queryParameters["data"];

          var result = new StringBuilder(@"
              <?xml version='1.0'?>
              ...
          ");

          var response = webContext.CreateTextResponse(result.ToString(), "application/xml", Encoding.ASCII);

          return response;
        }
  }

If there is a better alternative - it would be perfect to know about it.

BreakPhreak
  • 10,940
  • 26
  • 72
  • 108
0

The SOAP specification states that raw binary data should be base64 encoded. So the behavior you witness is correct.

If you need to transmit ASCII character data. You can use one message contract and a raw Message.

Message DoIt(DoItRequest request)

The raw message will create the response containing ASCII data. The message contract is used to retrieve the int parameter you mentioned:

[MessageContract]
public class DoItRequest
{
  [MessageBodyMember] public int data;
}

This page explains how to work with the Message class.

Johann Blais
  • 9,389
  • 6
  • 45
  • 65
  • Thanks a lot! I will read the link and will try it right now. Will I still be able to get my request parsed with UriTemplate? (I'll try it meanwhile just as said, if there is any relevant info/pitfalls - please be kind to share). – BreakPhreak Sep 28 '10 at 07:45
  • Just tried. Operation 'DoIt' in contract 'IHello' has a UriTemplate that expects a parameter named 'data', but there is no input parameter with that name on the operation. – BreakPhreak Sep 28 '10 at 07:50