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.
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; }
}
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.....
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?
Related
I'm building a web Api to catalog my firms bug report onto a server, and I'm having trouble with the post request that I'm doing.
As I've coded the clients they should be either sending bug reports that are formatted like this
public partial class JitCollect {
public DateTime? Timestamp { get; set; }
public string Jit { get; set; }
public int? ProjectId{ get; set; }
}
or they could be sending strings with status reports like "OK" or whatever. My main problem is that if I send the bug reports as "application/x-www-form-urlencoded" and prepending a '=' to my body like I saw online, I lose the DateTime format that NewtonSoft is expecting on the other side:
before "2018-08-14T08:50:17.5608444+02:00"
after "2018-08-14T08:50:17.5608444 02:00"
I could hardcode something to put the '+' back but that's beside the point, I'm interested in how to properly accomplish what I'm trying to do.
if I instead try to send the data as "application/json", I always get empty data on the other side even specifying the from body attribute and the object type (which is not ideal for me because I want to be able to send plain strings as well)
[HttpPost]
public string Post([FromBody] List < JitCollect > jsonPost)
any idea what is the best way to do this? here's one of the many post functions I tried to use
public static void postRequest(string data, string address) {
using (var client = new HttpClient()) {
client.BaseAddress = new Uri(address);
data = $"={data}";
//client.DefaultRequestHeaders.Add("token", token);
var buffer = System.Text.Encoding.UTF8.GetBytes(data);
var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
//byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var result = client.PostAsync("", byteContent).Result;
string resultContent = result.Content.ReadAsStringAsync().Result;
}
}
I made a test with asp.net core web api, it passes the datetime correctly.
Controller
[HttpPost]
public object Post([FromBody]List<JitCollect> jsonPost)
{
var resul = jsonPost.FirstOrDefault().Timestamp.Value.ToString("yyyy’-‘MM’-‘dd’T’HH’:’mm’:’ss.fffffffK");
return new List<JitCollect>() {
new JitCollect{ ProjectId = 1, Jit = "J1", Timestamp = DateTime.Now }
};
}
Request from postman
Result From Controller
I am a beginner in programming and have recently learnt and used http uri to get data and then parse json string from the stream using C# in SSIS and was able to load the data into sql server database.
Below is the sample code I used..
System.Uri uri = new Uri("API URL");
WebRequest webRequest = HttpWebRequest.Create(uri);
HttpWebRequest httpWebRequest = (HttpWebRequest)webRequest;
NetworkCredential networkCredential = new NetworkCredential("LOGIN","Password");
credentialCache.Add(uri,"Basic",networkCredential);
WebResponse webResponse = webRequest.GetResponse();
...
However I am trying to setup the same type of connection for another api which uses POST method.
The query looks something like.. URL + JSON API query which is similar to..
{"JSON" : {
"name": "Dataset",
"ColumnSelect": ["Name","Age","Email"],
"sort":["Name"],
"filterList": [
{
"Attribute": "Age",
"Operator": ">",
"Value": ["25"]
}],"returnObject"
I am not sure how this can be used to query the data and load data to sql like the http request. Can someone please advise me on the right direction to achieve this. Thank you for all your help.
I'm not %100 sure what your scenario is, but as far as I understand you are trying to get data from some server, and store the data in your database.
You have achieved it using HTTP GET, but this time you need to do it with POST.
The basic difference with GET and POST is, GET is used to query the existing data, but in POST, you are delivering something to the server. (of course there are more differences, check this link: https://www.w3schools.com/tags/ref_httpmethods.asp)
This can be done easily:
Prepare your POST request, you need to have a content for your parameters.
Create classes for your incoming response string, from your JSON posted:
public class JSON
{
public string name { get; set; }
public string[] ColumnSelect { get; set; }
public string[] sort { get; set; }
public filterList filterList { get; set; }
}
public class filterList
{
public string Attribute { get; set; }
public string Operator { get; set; }
public string[] Value { get; set; }
}
When you have the response string (stringified JSON), deserialize it via JSON.NET.
Now you have them as .NET objects. You can use Entity Framework to commit them to the database.
I can edit my answer if you need advice on any steps.
I am attempting to wrap the Plivo API (yes I'm aware it's been done) using RestSharp.
However, I cannot find a method to translate the API Naming conventions to my own, for example:
A "Call` (https://www.plivo.com/docs/api/call/#make-an-outbound-call) requires a minimum of:
to, from, and answer_url parameters be provided.
These parameters are also case-sensitive.
I would like to be able to provide a CallRequest Class, wrapping the data required in my preferred naming conventions, and then somehow translate these prior to serialization by RestSharp.
Example:
public class CallRequest
{
/// <summary>
/// The phone number to be used as the caller id (with the country code).For e.g, a USA caller id number could be, 15677654321, with '1' for the country code.
/// </summary>
public string From { get; set; }
/// <summary>
/// The regular number(s) or sip endpoint(s) to call. Regular number must be prefixed with country code but without the + sign). For e.g, to dial a number in the USA, the number could be, 15677654321, with '1' for the country code. Multiple numbers can be sent by using a delimiter. For e.g. 15677654321<12077657621<12047657621. Sip endpoints must be prefixed with sip: E.g., sip:john1234#phone.plivo.com. To make bulk calls, the delimiter < is used. For eg. 15677654321<15673464321<sip:john1234#phone.plivo.com Yes, you can mix regular numbers and sip endpoints.
/// </summary>
public string To { get; set; }
/// <summary>
/// The URL invoked by Plivo when the outbound call is answered.
/// </summary>
public string AnswerUrl { get; set; }
}
This data would then be translated to Plivo's convention in the following functions:
private T Execute<T>(IRestRequest request) where T : new()
{
var client = new RestClient
{
BaseUrl = new Uri(BaseUrl),
Authenticator = new HttpBasicAuthenticator(_accountId, _authToken),
UserAgent = "PlivoSharp"
};
request.AddHeader("Content-Type", "application/json");
request.AddParameter("auth_id", _accountId, ParameterType.UrlSegment);
request.RequestFormat = DataFormat.Json;
client.AddHandler("application/json", new JsonDeserializer());
var response = client.Execute<T>(request);
if (response.ErrorException == null) return response.Data;
const string message = "Error retrieving response. Check inner details for more info.";
var plivoException = new ApplicationException(message, response.ErrorException);
throw plivoException;
}
public CallResponse MakeCall(CallRequest callRequest)
{
var request = new RestRequest
{
RequestFormat = DataFormat.Json,
Resource = "Account/{auth_id}/Call/",
Method = Method.POST
};
//SOMEHOW TRANSLATE THE PROPERTIES INTO THE DATA BELOW
request.AddBody(new
{
to = "17#####",
from = "18#####",
answer_url = "http://m------.xml"
});
return Execute<CallResponse>(request);
}
Unfortunately it looks as though JSON property renaming is not implemented out of the box in RestSharp. You have a couple of options:
Download Restsharp from https://github.com/restsharp/RestSharp and rebuild it yourself enabling the compiler option SIMPLE_JSON_DATACONTRACT. Then you will be able to rename properties using data contract attributes. For more, see here: RestSharp JsonDeserializer with special characters in identifiers
I just rebuilt the most recent version of RestSharp (version 105.1.0)
with this option enabled. Using the following version of your class:
[DataContract]
public class CallRequest
{
[DataMember(Name = "from")]
public string From { get; set; }
[DataMember(Name = "to")]
public string To { get; set; }
[DataMember(Name = "answer_url")]
public string AnswerUrl { get; set; }
}
I was able to generate the following JSON:
var request = new CallRequest { AnswerUrl = "AnswerUrl", From = "from", To = "to" };
var json = SimpleJson.SerializeObject(request);
Debug.WriteLine(json);
// Prints {"from":"from","to":"to","answer_url":"AnswerUrl"}
I'm not sure how thoroughly tested this option is, however, since it's compiled out by default.
Manually serialize and deserialize with a different serializer such as Json.NET that supports property renaming. To do this, see RestSharp - using the Json.net serializer (archived here.)
I am evaluating how to add hypermedia links to DTO responses. Although there is no standard, add List to the response DTOs seems to be the suggested approach.
Do you know of any example or reference of implementation using ServiceStack framework?
Adding List is ok for me, but my doubts are about where to put the logic of the following links (Within the service or a specialized class that holds the state machine?) and where to resolve the routes (A filter?)
Thanks.
[Update] From ServiceStack version v3.9.62 it is posible to access Routes configuration via EndpointHost.Config.Metadata.Routes.RestPath, so the solution provided by tgmdbm can be improved withouth the need of "IReturn + Routes attributes", just using Metadata.Routes information.
In fact all service metadata can be queried and used to cross-cutting concerns. Servicestack rocks.
The way I do this currently is I pass back a response dto which implements an interface
public interface IHaveLinks
{
[IgnoreDataMember]
IEnumerable<Link> Links { get; }
}
public class Link
{
public string Name { get; set; }
public IReturn Request { get; set; }
public string Method { get; set; }
}
Then I use a response filter to generate the urls and populate the response headers with the links.
this.ResponseFilters.Add((req, res, dto) =>
{
if (!(dto is IHaveLinks))
return;
var links = (dto as IHaveLinks).Links
if(links == null || !links.Any())
return;
var linksText = links
.Select(x => string.Format("<{0}>; rel={1}"), x.Request.ToUrl(x.Method), x.Name));
var linkHeader = string.Join(", ", linksText);
res.AddHeader("Link", linkHeader);
});
This seems the cleanest way. The Link object above effectively says "If you make this request with this method you will get back the named resource". The only HTTP thing that bleeds up to the BLL is Method. But you could get rid of that and only pass back GET urls. Or map it to some generalised "operation"?
As an example:
public class ExampleService : Service
{
public ExamplesResponse Get(ExamplesRequest request)
{
var page = request.Page;
var data = // get data;
return new ExamplesResponse
{
Examples = data,
Links = new []
{
new Link { Name = "next", Request = request.AddPage(1), Method = "GET" },
new Link { Name = "previous", Request = request.AddPage(-1), Method = "GET" },
}
}
}
}
[Route("/examples/{Page}")]
public class ExamplesRequest : IReturn<ExamplesResponse>
{
public int Page { get; set; }
// ...
}
(The AddPage method returns a clone of the request and sets the Page property appropriately.)
Hope that helps.
I would like to have ASP.NET MVC return a document stored in MongoDB as JSON, but have no need for it to be serialized to a .NET type first. However, BSONDocument.ToJSON() returns JSON that looks like this:
{_id:ObjectId("someid")}
The browser's JSON parser does not like "ObjectId(nnn)" and so the call fails with a parser error. I am able to get parse-able JSON using a Regex hack:
public ActionResult GetFormDefinitionsJSON()
{
var client = new MongoDB.Driver.MongoClient(ConfigurationManager.ConnectionStrings["mongodb"].ConnectionString);
var db = client.GetServer().GetDatabase("formthing");
var result = db.GetCollection("formdefinitions").FindAll().ToArray();
var sb = new StringBuilder();
sb.Append("[");
var regex = new Regex(#"(ObjectId\()(.*)(\))");
var all = result.Select(x => regex.Replace(x.ToJson(), "$2"));
sb.Append(string.Join(",", all));
sb.Append("]");
return Content(sb.ToString(), "application/json");
}
This returns parse-able JSON:
{_id:"someid"}
But it smells. Is there any way without regex and string building hackery to get the official MongoDB driver to return JSON that can be parsed by the browser? Alternatively, am I missing something on the browser side that would allow {_id:ObjectId("someid")} to be parsed as valid?
You have two options that I can think of.
The first one is to use the JavaScript JsonOutputMode mode. This results in the ID serializing to "_id" : { "$oid" : "51cc69b31ad71706e4c9c14c" } - not quite ideal, but at least it's valid Javascript Json.
result.ToJson(new JsonWriterSettings { OutputMode = JsonOutputMode.JavaScript })
The other option is to serialize your results into an object and use the [BsonRepresentation(BsonType.String)] attribute. This results in much nicer Json: "_id" : "51cc6a361ad7172f60143d97"; however, it requires you to define a class to serialize it in to (this can affect performance)
class Example
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public ObjectId ID { get; set; }
public string EmailAddress { get; set; }
}
// Elsewhere in Code - nb you need to use the GetCollection<T> method so
// that your result gets serialized
var result = database.GetCollection<Example>("users").FindAll().ToArray();
var json = result.ToJson();
Some more details on the differences between JsonOuputModes (Strict, Javascrpt and Mongo):
http://docs.mongodb.org/manual/reference/mongodb-extended-json/