1

I'm using ServiceStack for a while now and I'm very happy with the functionality it provides. Already implemented serveral services with it and it works like a charm.

Recently however I've faced a problem with calling other service with a sophisticated URL that has to be encoded properly.

The code is the following:

The Request:

[Route("/instruments/{Names}")]
internal class Request
{
    public List<string> Names { get; set; }
}

And the method call:

var request = new Request { Names = list };
var c = new JsonServiceClient("http://host:12345/");

Response[] response;
try
{
    response = c.Get<Response[]>(request);
}
catch (WebServiceException ex)
{
    HandleWebException(ex, list);
    yield break;
}

And now the problem is that sometimes the name can contain a special characters like space or /. I'd like to have those propery encoded. For instance I'd like to be able to call the remote service with the following parameters: "D\S N" with is supposed to be encoded to "D%5CS%20N". So the called URL should look something like this:

http://host:12345/instruments/D%5CS%20N

And now the problem is that JsonServiceClient does the bad encoding here. What I call is actually:

http://host:12345/instruments/D/S%20N

With is obviously wrong. Any help how to sort this out is appeciated.

3 Answers 3

1

You shouldn't register complex types like List<T> in the PathInfo, try with a single string:

[Route("/instruments/{Name}")]
public class Request
{
    public string Name { get; set; }
}

Or take it out of the /pathinfo so it will get serialized as a complex type on the QueryString:

[Route("/instruments")]
public class Request
{
    public List<string> Names { get; set; }
}
Sign up to request clarification or add additional context in comments.

6 Comments

Hi Demis, thanks for your answer. Unfortunetly it doesn't change anything. Client is still calling the following URL: host:12345/instruments/D/S%20N. Funny thing is that when I change the Names parameter to be a query parameter instead of Path parameter, it works correctly: host:12345/instruments?names=D%5CS%20N. Unfortuneatly I cannot change this interface.
You can't have a / in the param it would be treated like a separate component. You can change it to use a wildcard param which will soak up all the path components, i.e. [Route("/instruments/{Name*}")]
The problem here is that I have \ in my param, not /. But after I pass a name with \ to the client, it somehow changes it to /. I have D\S N and it should be encoded to D%5CS%20N but instead I can see that Client is calling an url with this name encoded as D/S%20N. Why is that?
@HubertR.Skrzypek the issue is because backslashes are illegal in urls which browsers automatically convert to /. Likewise in HttpListener the \ is automatically converted to / whereas the ASP.NET webdev server will automatically throw a 400 Invalid Request. Basically backslashes does not have reliable behavior in urls so they should be avoided.
Yep, but I'd like to use encoded backslashes in my URLs, the problem is that ServiceStack somehow instead of encoding them as %5C uses /. When I call the other service directly from the browser with URL encoded as following host:12345/instruments?names=D%5CS%20N it works as a charm.
|
1

I believe ServiceStack could be improved here.

If I use a request DTO defined like this:

[Route("/hello/{Name}/{Message}")]
public class Hello
{
    public string Name { get; set; }
    public string Message { get; set; }
}

Then a client calling like this:

var resp = cli.Get(new Hello { Message = "test", Name = "foo/bar" });

will fail. Same happens if I replace the slash with a backslash.

I have made a patch to ServiceStack that fixes this behaviour (works for backslash too), so that Name will be correctly encoded client side and decoded server side. Demis, is this something you might be interested in taking a look at?

BTW this works fine out-of-the-box with Java Jersey.....

Comments

0

I am encountering the same problem. My string is not a complex object. Just a string with a slash in it. It seems like ServiceStack is in fact receiving the URL encoded string correctly. Servicestack then appears to be decoding the URL encoded string before it passes it to routing (this is just a guess on my part) , instead of using the Route information in the request DTO to first determine which part of the URL is routing and which part is a parameter, then routing, then decoding the URL encoded parameter. I receive an error from service stack like so:

Handler for Request not found (404):

  Request.HttpMethod: GET
  Request.PathInfo: /QuoteFeed/GetQuote/ACO/X CN
  Request.QueryString: 
  Request.RawUrl: /QuoteFeed/GetQuote/ACO%2FX%20CN

Request is defined as follows:

[Route("/QuoteFeed/GetQuote/{Symbol}", Summary = "Retreive a price quote for the requested symbol.")]
public class GetQuote : IReturn<QuoteDataResponse>
{
    [DataMember, ProtoMember(1)]
    [ApiMember(Name = "Symbol",
         Description = "The symbol, in the providers given format, for which a quote should be given.",
         DataType = "string",
         IsRequired = true)]
    public string Symbol { get; set; } 
}

Seems fragile to require the client to replace slashes with some other special character which the service would swap back to a slash. Is the only way around this to force the request through POST?

Edit 1

This is my best attempt at getting this to work:

On the client side:

dim client = new JsonServiceClient (My.Settings.MarketDataServiceUri)
dim request = New GetQuote
request.Symbol =  WebUtility.UrlEncode(txtBoxSymbol.Text.ToUpperInvariant)

On the server side (in AppHost.Init):

base.RequestBinders.Add(typeof(GetQuote), httpReq =>
{
    var requestPath = string.Empty;
    if (typeof(GetQuote).GetCustomAttributes(typeof(RouteAttribute), true
                                     ).FirstOrDefault() is RouteAttribute dnAttribute)
    {
        requestPath = dnAttribute.Path;
    }

    var routePath = requestPath;
    var paramIndex = requestPath.IndexOf("{");
    if (paramIndex > 0)
    {
        routePath = requestPath.Substring(0, paramIndex);
    }

    return new GetQuote
    {
        Symbol = WebUtility.UrlDecode(httpReq.PathInfo.Replace(routePath, string.Empty))
    };
});

This is really ugly, but I can't find any other way around this. The symbol being passed in must have the slashes since that is the format required by the downstream quote provider and is what the users expect to enter. Is this the best way to do this?

10 Comments

I'd avoid using slashes in the /path/info at all, backslashes are illegal in URLs , explained in existing comment thread below whilst forward slashes represents a different path component + matching route. You can avoid any ambiguity by sending it in the queryString, e.g. ?symbol=ACO%2FX+CN or change the Symbol to a wildcard path, e.g. {Symbol*} so it matches multiple path segments.
I thought asking a new question was not appropriate since it's pretty much the same problem, just my situation is a bit different in that I am not doing an complex object inside the string. I tried sending it through the query string but same problem: Handler for Request not found (404): Request.HttpMethod: GET Request.PathInfo: /QuoteFeed/GetQuote Request.QueryString: symbol=ACO%2fX+CN Request.RawUrl: /QuoteFeed/GetQuote?symbol=ACO%2FX+CN
Right now I am pre-urlEncoding the string before I assign the symbol to the request Dto and then using a RequestBinder to UrlDecode it on the server side, it's not pretty but it works. Would be nice if there was a less fiddly way to do this.
Do you want me to delete and repost?
Wonderful, looks like removing {symbol} from the route did the trick. I have been working off examples where the variable placeholder was always included so I just assumed it was required for serialization of dto properties. Thank you very much for your help, more RTFM is in my future.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.