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.)
Related
When I use azure table storage Insert Entity api to insert data.
There are some types like dateTime,int in my entity class. And as per this doc: By default a property is created as type String, unless you specify a different type.
So I wonder which is the best way to add the odata type to the json payload? The generated json payload with odata type added should like below:
And my current solution is that after the json string is generated, I treat it as string, and add the odata type directly into the json. But I think there should be more better way for this.
My code like below:
Entity class:
public class MyClass
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }
public string Name { get; set; }
public Guid TestId { get; set; }
}
The main method:
static void Main(string[] args)
{
string url_with_sasToken = "https://xxx.table.core.windows.net/ttt?sasToken";
MyClass entity = new MyClass
{
PartitionKey = "ivan2",
RowKey = "y1",
Name = "jack60",
TestId = Guid.NewGuid()
};
//here, I add the odata type for Guid type
string s = JsonConvert.SerializeObject(entity).Replace("}","");
s += ",\"TestId#odata.type\"" + ":" + "\"Edm.Guid\"}";
Console.WriteLine(s);
var content = new StringContent(s, Encoding.UTF8, "application/json");
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new
MediaTypeWithQualityHeaderValue("application/json"));
var t2 = client.PostAsync(url_with_sasToken , content).GetAwaiter().GetResult();
Console.WriteLine(t2.StatusCode);
}
}
IMHO, a better way to do this is to make use of Reflection and dynamically create JSON by iterating over public properties of a class. You can pick only those properties types of which are supported by Table service (e.g. Guid, DateTime etc.)
That way you're not constantly changing the serialization code to reflect changes in your class definition. In any case using + sign for string concatenation is a bad idea :).
For reference, you can take a look at the code for TableEntity class in Storage SDK. I downloaded the code for release 9.3.2 from Github (https://github.com/Azure/azure-storage-net/releases/tag/v9.3.2) as this was the last version that supported Table in the SDK.
I am trying to write wrapper for Capsule CRM API using RestSharp.
I have problem with their API service. It returns JSON object when data is present and empty string when object is absent on CRM.
e.g., look on contacts:
{"organisation":{"id":"97377548","contacts":"","pictureURL":"","createdOn":"2016-02-08T14:27:12Z","updatedOn":"2016-02-08T14:27:12Z","lastContactedOn":"2013-12-03T21:00:00Z","name":"some name"}}
{"organisation":{"id":"97377548","contacts":{"email":{"id":"188218414","emailAddress":"someemail"},"phone":{"id":"188218415","type":"Direct","phoneNumber":"phone"}},"pictureURL":"","createdOn":"2016-02-08T14:27:12Z","updatedOn":"2016-02-08T14:27:12Z","lastContactedOn":"2013-12-03T21:00:00Z","name":"some name"}}
To match contacts I have class:
public class Contacts
{
public List<Address> Address { get; set; }
public List<Phone> Phone { get; set; }
public List<Website> Website { get; set; }
public List<Email> Email { get; set; }
}
and property Contacts in class that I am trying to match:
public Contacts Contacts { get; set; }
Everything works fine when API returns JSON object, but I get exception when I get empty string for contacts from API:
Unable to cast object of type 'System.String' to type
'System.Collections.Generic.IDictionary`2[System.String,System.Object]'.
How to avoid this problem?
Is there any way to make conditional matching based on data that is returned from API?
How I can tell RestSharp do not throw exception, just skip property if it can't match?
As you have control over your API, instead of returning "contacts":"" in the response, return "contacts":"{}" and that should avoid your error.
If you cannot alter the response from the API, you'll need to implement a custom serializer, as "" to object isn't supported by RestSharp.
This article summarizes how to use JSON.Net as the serializer, which would enable you to use whatever rules you needed to for your deserialization.
Article summary
First, implement ISerializer and IDeserializer interfaces in a NewtonsoftJsonSerializer class. This would give you full control on how the JSON is desierialized, so you can make "" work for an empty object.
Then, to use it on a request:
private void SetJsonContent(RestRequest request, object obj)
{
request.RequestFormat = DataFormat.Json;
request.JsonSerializer = new NewtonsoftJsonSerializer();
request.AddJsonBody(obj);
}
and to use it on a response:
private RestClient CreateClient(string baseUrl)
{
var client = new RestClient(baseUrl);
// Override with Newtonsoft JSON Handler
client.AddHandler("application/json", new NewtonsoftJsonSerializer());
client.AddHandler("text/json", new NewtonsoftJsonSerializer());
client.AddHandler("text/x-json", new NewtonsoftJsonSerializer());
client.AddHandler("text/javascript", new NewtonsoftJsonSerializer());
client.AddHandler("*+json", new NewtonsoftJsonSerializer());
return client;
}
I have a sample project that gets data from Fitbit but the information comes in metric system not imperial system. Here is the code that pulls a list of Dates and Strings pair using TimeSeriesDataList class, but in kilometers not miles. How can I modify this code to get the data in miles instead of kilometers? Any help is greatly appreciated.
public class TimeSeriesDataList
{
public List<Data> DataList { get; set; }
public class Data
{
public DateTime DateTime { get; set; }
public string Value { get; set; }
}
}
public class FitbitClient : IFitbitClient
{
/// <summary>
/// Get TimeSeries data for another user accessible with this user's credentials
/// </summary>
/// <param name="timeSeriesResourceType"></param>
/// <param name="startDate"></param>
/// <param name="endDate"></param>
/// <param name="userId"></param>
/// <returns></returns>
private TimeSeriesDataList GetTimeSeries(TimeSeriesResourceType timeSeriesResourceType, DateTime baseDate, string endDateOrPeriod, string userId)
{
string userSignifier = "-"; //used for current user
if (!string.IsNullOrWhiteSpace(userId))
userSignifier = userId;
// Here is where I believe I need to make a change, somehow, so that
// data that comes in when this is string is requested, comes in metric units
string requestUrl = string.Format("/1/user/{0}{1}/date/{2}/{3}.xml", userSignifier, StringEnum.GetStringValue(timeSeriesResourceType), baseDate.ToString("yyyy-MM-dd"), endDateOrPeriod);
RestRequest request = new RestRequest(requestUrl);
request.OnBeforeDeserialization = resp => {
XDocument doc = XDocument.Parse(resp.Content);
//IEnumerable<XElement> links = doc.Descendants("result");
var rootElement = doc.Descendants("result").FirstOrDefault().Descendants().FirstOrDefault();
if (rootElement != null && !string.IsNullOrWhiteSpace(rootElement.Name.LocalName))
request.RootElement = rootElement.Name.LocalName;
};
var response = restClient.Execute<TimeSeriesDataList>(request);
HandleResponse(response);
return response.Data;
}
}
EDIT I am looking to get the data already converted by adding something along the lines of "Accept-Language" to the header, but I do not know how to utilize that concept. Using conversion crossed my mind but at this time I would like to see if there is an easier way by adding a simple header rather than creating a conversion class for every scenario, class holding the data, for each distinct region, etc.
I did a quick search and am assuming that RestRequest is the class from the RestSharp library. If so, according to their web site (http://restsharp.org) you would do something like:
request.AddHeader("Accept-Language", "...");
Not sure what the correct value should be, you'll need to figure that out from FitBit's API documentation.
Whatever value this code provides, you can simply divide it by 1.60934 to get the same distance in miles. Hope that helps you solve it.
double kilometers2 = 321.9;
double miles2 = ConvertDistance.ConvertKilometersToMiles
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?
EDIT : the moment I asked the question I thougt of trying something..
I've set XmlNamespace property on the request and that did the trick..
request.XmlNamespace = "http://musicbrainz.org/ns/mmd-2.0#";
But I don't really understand as to why...
Next problem is getting RestSharp to recognize xml atributes as object properties
I've been going over this for the most of the weekend and I just don't get it to work.
I'm trying to write a wrapper round a RestFul webservice (MusicBrainz). I'm testing with a simple example : get details of one artist and put it in a custom Artist object.
When I do a Execute on the RestClient it ends ok but my object properties are null..
But when I test the deserialization with the XmlDeserializer the objectproperties are filled (But not for properties that correspond to an attribute, but I'll tackle that later)
What happens between deserialization of the response and putting the object in response.data ?
Quite possible it is a "newbie" error I'm making as this are my first steps with RestSharp..
Help would be much appreciated..
Returnded xml :
<metadata>
<artist type="Group" id="f1548c5b-329e-4036-921c-02213a04b525">
<name>Uriah Heep</name>
<sort-name>Uriah Heep</sort-name>
<country>GB</country>
<life-span>
<begin>1970</begin>
</life-span>
</artist>
</metadata>
My class :
public class Artist
{
public int Id { get; set; }
public string Type { get; set; }
public string Name { get; set; }
public string SortName { get; set; }
public string Country { get; set; }
}
In the following code output properties are filled
var output = xml.Deserialize<Artist>(response);
But the same response does not fill properties when calling
var response = client.Execute<T>(request);
Complete code (I've put the test code in the generic method for sake of simplicity) :
public T Execute<T>(RestRequest request) where T : new()
{
var client = new RestClient();
client.BaseUrl = BaseUrl;
client.Authenticator = null;
//does not fill properties
var response = client.Execute<T>(request);
if (response.ErrorException != null)
{
throw response.ErrorException;
}
var xml = new XmlDeserializer();
//fills properties
var output = xml.Deserialize<Artist>(response);
return response.Data;
}
This happens because Execute method, after receiving response, tries to negotiate it based on the request's RootElement and XmlNamespace properties and copies them to the XmlDeserializer.
Here's a code from RestClient:
handler.RootElement = request.RootElement;
handler.DateFormat = request.DateFormat;
handler.Namespace = request.XmlNamespace;
response.Data = handler.Deserialize<T>(raw);
If you pass a RestRequest with a mismatching XmlNamespace, RestSharp's XmlDeserializer (that uses XDocument behind the scenes) won't be able to map response XML to an object properties and you will get default/null values instead.
Now for default implementation (when you create XmlDeserializer manually), if you don't set a XmlNamespace, deserializer will do an auto-detection that basically ignores all namespaces in the response and maps all properties only by their names.
See source code from XmlDeserializer:
// autodetect xml namespace
if (!Namespace.HasValue())
{
RemoveNamespace(doc);
}
Taking all above into account, it's clear why it started working after you explicitly set XmlNamespace property to a matching namespace in your request object with this line:
request.XmlNamespace = "http://musicbrainz.org/ns/mmd-2.0#";
Execute method copied namespace into deserializer and it mapped XML to object appropriately.