Amazon Simple Notification Service with custom iOS payload not so simple - c#

Sending a plain text notification is easy and well documented. But I've been pulling my hair today regarding sending a custom notification for iOS that has the alert and some fields like userId.
I started with this help page and implemented something similar to the last sample, then I found this answer that seems to invalidate the last sample on the help page, as the "url" property should be outside the "aps" object. I tried a good deal of combinations but each one of them gets sent as text to the app (the whole message, with the "default" property and "APNS" object)...
If I explicitly set MessageStructure to json I get the error: "Invalid parameter: Message Structure - JSON message body failed to parse" but I'm pretty sure my JSON is good, when sent to SNS the string in the Message property looks like this:
{ "default":"You received a new message from X.",
"APNS_SANDBOX":"{ \"aps\": {\"alert\":\"You received a new message from X.\"},
\"event\":\"Message\",
\"objectID\":\"7a39d9f4-2c3f-43d5-97e0-914c4a117cee\"
}",
"APNS":"{ \"aps\": {\"alert\":\"You received a new message from X.\"},
\"event\":\"Message\",
\"objectID\":\"7a39d9f4-2c3f-43d5-97e0-914c4a117cee\"
}"
}
Does anybody have a good example of sending a notification with custom payload through SNS in C#? Because Amazon sure hasn't...

Strangely when I implemented the clean way of doing this by using classes and serializing objects instead of just sending a formatted string it worked. The only difference was the spacing... in the clean version there are no spaces except in the property values:
{"default":"You received a new message from X.","APNS_SANDBOX":"{\"aps\":{\"alert\":\"You received a new message from X.\"},\"event\":\"Message\",\"objectID\":\"7a39d9f4-2c3f-43d5-97e0-914c4a117cee\"}","APNS":"{\"aps\":{\"alert\":\"You received a new message from X.\"},\"event\":\"Message\",\"objectID\":\"7a39d9f4-2c3f-43d5-97e0-914c4a117cee\"}"}
These are the classes that I'm serializing (only for APNS for the moment), use whatever properties you need instead of Event and ObjectID:
[DataContract]
public class AmazonSNSMessage
{
[DataMember(Name = "default")]
public string Default { get; set; }
[DataMember(Name = "APNS_SANDBOX")]
public string APNSSandbox { get; set; }
[DataMember(Name = "APNS")]
public string APNSLive { get; set; }
public AmazonSNSMessage(string notificationText, NotificationEvent notificationEvent, string objectID)
{
Default = notificationText;
var apnsSerialized = JsonConvert.SerializeObject(new APNS
{
APS = new APS { Alert = notificationText },
Event = Enum.GetName(typeof(NotificationEvent), notificationEvent),
ObjectID = objectID
});
APNSLive = APNSSandbox = apnsSerialized;
}
public string SerializeToJSON()
{
return JsonConvert.SerializeObject(this);
}
}
[DataContract]
public class APNS
{
[DataMember(Name = "aps")]
public APS APS { get; set; }
[DataMember(Name = "event")]
public string Event { get; set; }
[DataMember(Name = "objectID")]
public string ObjectID { get; set; }
}
[DataContract]
public class APS
{
[DataMember(Name = "alert")]
public string Alert { get; set; }
}
So I get the Amazon SNS message by doing:
new AmazonSNSMessage(...).SerializeToJSON();

The key that just fixed this for me was realizing that the outer JSON specific to SNS (e.g. the "default" and "APNS" properties) MUST NOT be escaped, ONLY the inner payload. For instance, this Message value succeeded (just posting the start):
{"APNS":"{\"aps\" ...
Notice the first property "APNS" is not escaped, but then it's value (your actual payload that will hit the device) IS escaped. The following gets the job done:
JObject payload = ... // your apns, etc payload JObject
var snsMsgJson = new JObject(
new JProperty("default", "message 1 2 3"), // "default" is optional if APNS provided
new JProperty("APNS", payload.ToString(Formatting.None))
);
string str = snsMsgJson.ToString(Formatting.None);
I figured this out looking at the example above, thanks! But, I knew the above 'clean classes' so-called solution couldn't and shouldn't actually be a requirement. So when he says: "The only difference was the spacing... in the clean version there are no spaces except in the property values" that is not correct. The real difference is as I said, outer (SNS specific) JSON shall not be escaped, but inner must.
Soap box: How about that documentation eh? So many things in this API that wasted major time and just as importantly: one's sense of well-being. I do appreciate the service though regardless.

Related

Posting XML to webapi is only binding non-collection properties on the [FromBody]request model

(Initial question solved but in the update section below, I have questions about why what I've done works)
We have a asp.net webApi project with a single POST operation that previously only needed to take a request formatted as JSON and would respond with whatever the accept header said (be it application/xml or application/json).
Now the POST needs to be able to accept XML as the request. This should just be a matter of setting content-type to application/xml and all should be good.
However, the model binding isn't quite working, it's working partly but not all
Here's a simplified version of the model:
[DataContract(Namespace = "")]
public class PostModel
{
[DataMember(Name = "Identifier", Order = 0)]
[Required]
public string Identifier { get; set; }
[DataMember(Name = "ReturnNullItems", Order = 1)]
public bool? ReturnNullItems { get; set; }
[DataMember(Name = "RequestedItems", Order = 2)]
[Required]
public List<string> RequestedItems { get; set; }
[DataMember(Name = "Mileage", Order = 3)]
public int Mileage { get; set; }
}
Here's a simplified version of the controller method:
[HttpPost]
[Route("")]
public async Task<ResponseMessageResult> Post([FromBody]PostModel postModel)
{
var formatter = GetMediaTypeFormatter();
if (postModel == null)
{
var message = new ErrorMessage { Message = "Malformed Request"};
return GetResponseMessageResult(message, HttpStatusCode.BadRequest, formatter);
}
if (!ModelState.IsValid)
{
var message = new ErrorMessage { Message = string.Join("; ", ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage)) };
return GetResponseMessageResult(message, HttpStatusCode.BadRequest, formatter);
}
}
Here's a sample bit of body xml from the request:
<PostModel>
<Identifier>ya05fom</Identifier>
<ReturnNullItems>false</ReturnNullItems>
<RequestedItems>
<string>EngineCapacity</string>
<string>FuelType</string>
<string>ForwardGears</string>
<string>Transmission</string>
</RequestedItems>
<Mileage>20000</Mileage>
</PostModel>
What we're seeing is that in postModel, the Identifer, ReturnNullItems and Mileage properties, which are all simple properties, are bound but the RequestedItems property which is collection property is not.
So in the postModel we'd see:
postModel.Identifer -> "ya05fom"
postModel.ReturnNullItems -> false
postModel.Mileage -> 20000
postModel.RequestItems -> null
The RequestItems property is null. I've tried having that property be a List of string and an string Array and neither works.
So this looks like there's an issue binding the nested child elements in the xml to a collection property in the PostModel class but I simply am at a loss as to what I'm missing.
UPDATE:
Thanks to Matt G in the comments, his comment gave me a nudge as to what to look at in the .net documentation and that gave me a nudge as to what to try.
Previously I had to add an XmlMediaTypeFormatter to the formatters on GlabalConfiguration:
GlobalConfiguration.Configuration.Formatters.Add(new System.Net.Http.Formatting.XmlMediaTypeFormatter());
There was also this below commented out:
//var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
//xml.UseXmlSerializer = true;
I commented the above two lines back in and voila it works.
I'm guessing that I'm telling the deserialiser to use the System.Net.Http.Formatting.XmlMediaTypeFormatter to deserialise the request body. I also guess that the default DataContractSerialiser doesn't do the job of deserialising xml with hierarchical xml very well without some extra information (which I don't know how to give).
So now it works but I only have guesses as to how and/or why. Can any body point me in the right direction or let me know why what I've done has fixed it?

Building json response

I'm working on a self hosting rest api used to monitor de status of several servers.
I was tasked that, when everything is working correctly, I should only return
{"response":"ok"}
But, when there's an error on queried server, or servers, I must return
{ "response" : [ {"agent":"<server>:<port>","port":"<port>" ,"Error":"<Description of the error>"} ] }
I was thinking on building a helper class to build object on this schema and returning them over the rest api
public class HelperErrorResponseClass
{
public string agent { get; set; }
public string port { get; set; }
public string Error { get; set; }
}
This is no problem, the issue is, how to deal when everything it ok. I have this Api response helper class
public class Response
{
public string response { get; set; }
}
But I'm seeing that I'll need to change the response property to List<HelperErrorResponseClass> in order to send the error response. Do you think that, if I stringify the List<HelperErrorResponseClass> object with Json.Net it will be returned in the desired format?
Edit: Forgot to add that, I-m using Web Api to build the rest service.
UDPATE:
After further research, I found a way to work this out.
Following this post, I was able to rewrite the helper classes like this
[DataContract]
[KnownType(typeof(List<HelperErrorResponseClass>))]
public class Response
{
[DataMember]
public object response { get; set; }
}
[DataContract]
public class HelperErrorResponseClass
{
[DataMember(EmitDefaultValue = false)]
public string agent { get; set; }
[DataMember(EmitDefaultValue = false)]
public string port { get; set; }
[DataMember(EmitDefaultValue = false)]
public string error { get; set; }
}
This work to fulfill my and my client needs... except for one little thing. When I get the result from a List, and given that I added the KnownTypes directive, my response is now this
{"response":[{"__type":"HelperErrorResponseClass:#AppCommonLib","Error":"ERROR","InstanceId":"<InstanceId> : <Port>","PortType":"<PortType>"},{"__type":"HelperErrorResponseClass:#AppCommonLib","Error":"ERROR","InstanceId":"<InstanceId> : <Port>","PortType":"<PortType>"}]}
Any idea how to get rid of that __type property of the response? make that it must be explicit to only return the declared properties of the helper class?
Simplest way to deal with this is to set the return type on the handling function to string, then you can check for errors and do something like;
//pseudo code to give an idea
if (errorsList.Count() > 0)
{
return JsonConvert.SerializeObject(errorsList);
}
else
{
return JsonConvert.SerializeObject(new Response("ok"));
}
Now this being said... Unless the people providing requirements aren't at all flexible you should just redo the design. How about just returning the errors array and the person calling the API can infer that if it's length is 0 then everything is working OK. Seems pretty straight forward, right? You could also just put all the properties on one object and those fields would just come back as null or empty strings. Or you could change you serializer settings to exclude them if they don't have a value.
Keep things simple and use an anonymous type.
if (condition)
{
return JsonConvert.SerializeObject(new { response = new { agent = "x", port = "y", error = "z" }});
}
else
{
return JsonConvert.SerializeObject(new { response = "ok"});
}
More info:
https://msdn.microsoft.com/en-us/library/bb397696.aspx
I personally don't think you need a Response class, especially that it is of object type. IMHO, you've overcomplicated the very simple issue that you have. It is not only the __type, but also other info like HelperErrorResponseClass:#AppCommonLib that isn't supposed to be there.
Another Issue you have is the incorrect name of the HelperErrorResponseClass class. This is not a helper class. It is a standard data-object class.
A helper class is a class filled with static methods. It is usually used to isolate a "useful" algorithm.
This is how I would do it:
I'd get rid of the Response class.
I'd use your original simple HelperErrorResponseClass class, but rename it to something more meaningful like ErrorDetails.
I'd return the response like this:
.
if (errorsList.Count() > 0) {
return JsonConvert.SerializeObject(new { response = errorsList});
}
else {
return JsonConvert.SerializeObject(new { response = "ok"});
}
However, if you really want to stick to your updated solution, an easy way to get rid of the __type is simply removing it from the final serialized string:
if (errorsList.Count() > 0) {
string r = JsonConvert.SerializeObject(new { response = errorsList});
return r.Replace("__type", "");
}
else {
return JsonConvert.SerializeObject(new { response = "ok"});
}

RESTAPI body parameters are passed null when calling in c#

I am trying to call a rest api method from c#. Problem is for all content types it passes null to body parameter.I shared my code below.Apart from this code I have tried to write body parameter to request as stream.It didn't work either. I have also tried 'application/x-www-form-urlencoded' as content type.
Calling rest api method from c# sample:
string token = Server.UrlEncode("v0WE/49uN1/voNwVA1Mb0MiMrMHjFunE2KgH3keKlIqei3b77BzTmsk9OIREken1hO9guP3qd4ipCBQeBO4jiQ==");
string url = "http://localhost:2323/api/Applications/StartProcess?token=" + token;
string data = #"{""ProcessParameters"": [{ ""Name"":""flowStarter"",""Value"": ""Waffles"" }],
""Process"": ""RESTAPISUB""}";
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
client.BaseAddress = new System.Uri(url);
byte[] cred = UTF8Encoding.UTF8.GetBytes("username:password");
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(cred));
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
System.Net.Http.HttpContent content = new StringContent(data, UTF8Encoding.UTF8, "application/json");
HttpResponseMessage messge = client.PostAsync(url, content).Result;
string description = string.Empty;
if (messge.IsSuccessStatusCode)
{
string result = messge.Content.ReadAsStringAsync().Result;
description = result;
}
Rest api Method:
[HttpPost]
[ActionName("StartProcess")]
public int StartProcess([FromUri]string token,[FromBody]WorkflowStartParameters parameters)
{
try
{
LoginInformation info = CasheProcesses.ReadCashe(token);
eBAWSAPI api = Service.GetWSService();
WorkflowProcess proc = api.StartProcess(info.Id, info.Password, info.ImpersonateUserId, info.Language, parameters);
return proc.ProcessId;
}
catch (Exception ex)
{
throw new Exception("An error occured when starting process,exception detail:" + ex);
}
}
WorkflowStartParameters class structure:
public class WorkflowStartParameters
{
public WorkflowParameter[] ProcessParameters;
public string Process { get; set; }
}
public class WorkflowParameter
{
public string Name { get; set; }
public string Value { get; set; }
}
I have searched this problem a lot. It seems as a very common problem. I just found this solution working properly, passing request parameter to rest api method and reading body parameter from there. But it is not a valid solution for me.
If you have any idea,feel free to share.
Thanks,
Zehra
I don´t know if it can solve your problem, but let me try.
I guess you don´t have to utilize Server.UrlEncode in your call, but:
Dim myUri As New Uri(Token)
And I guess you must not encode also your username and password - try pass them as string.
Your problem appear to be here:
public class WorkflowStartParameters
{
public WorkflowParameter[] ProcessParameters; <---- needs get/set
public string Process { get; set; }
}
This needs to be a public property to serialize properly. Currently you have it set up as a public field. Just add { get; set; } and give that a try. I would also look into serializing with Newtonsoft.Json to ensure your object is properly serialized. Trying to do it with escape strings will be messing the more data you are sending.
By the way there can be issues sometimes serializing arrays, I would change that to :
public List<WorkflowParameter> ProcessParameters{get;set;}
Finally I have achieved to send filled out data to server. It was about serialization problem. But it didn't work with json serialization before send data. I have added DataContract attribute to my class and it works properly.
Unfortunately still I couldn't figure out this when I make ajax calls from java script it works without DataContract attribute but if I call it in c# it needs DataContract attribute. If someone share the information about this I would appreciate!
I am sharing new class structure, everything else but this still same:
[Serializable]
[DataContract]
public class WorkflowParameter
{
[DataMember]
public string Name { get; set; }
[DataMember]
public string Value { get; set; }
}
[Serializable]
[DataContract]
public class WorkflowStartParameters
{
[DataMember]
public WorkflowParameter[] ProcessParameters { get; set; }
[DataMember]
public string Process { get; set; }
}

c# post complex object to php [Xamarin]

What I want to achieve is to send to PHP complex object to PHP, currently using wsd-data but only sends the properties values from the root, ie:
public class Post : Collection<Post>
{
public string Title { get; set; }
public string Content { get; set; }
public File Image { get; set; }
}
byte[] fileContents = ....;
Post post = new Post();
post.Title = "Post title";
post.Content = "Post content";
post.Image = new File ("FileName.png", "image/png", fileContents);
await post.Save();
In this case works perfectly because it handles the File case internally, but if I add a nested dependency like
public class Post : Collection<Post>
{
public string Title { get; set; }
public string Content { get; set; }
public File Image { get; set; }
public Author Author { get; set; }
}
Lets say that Author is a class that have name, id, etc, but when I post it only sends the Author.toString() value, I tried to add an array like key to post to PHP like:
MultipartFormDataContent form = new MultipartFormDataContent ();
form.Add (new StringContent (post.Author.Name), "Author[Name]");
form.Add (new StringContent (post.Author.Id), "Author[Id]");
await httpClient.PostAsync (url, form).ConfigureAwait (false);
Then in PHP I want to receive something like this:
<?php
echo $_POST['Author']['Name']; // must print the author name
?>
But I just got an empty $_POST['Author'] variable, dunno how to achieve with c#, if I need to change internally how to create the form body just let me know, but would like to use form-data because it support file submission.
Regards
Sounds like using serialization would be a better option than trying to process your input as form data. I haven't done much PHP in along time, but I've done plenty of web services. Just serialize it on the Xamarin end, and deserialize it on the PHP end.
I've found a solution (was a flaw on the library) its here
Basically I map recursively the complex dictionary to a much simple one (one level with values). i.e.
// Pseudo class with data
Post {
Id=0,
Title="Hello world",
List<Comment> Comments=[
Comment { Id=0, Description="This is a comment" }
]
}
// Pseudo dictionary result
Dictionary<string, string> data = {
Id=0,
Title="Hello World",
Comments[0][Id]=0,
Comments[0][Description]="This is a comment"
}
// And then in php get (all keys are converted to lowercase
echo $_POST['id']; // 0
echo $_POST['title']; // Hello World
echo $_POST['comments'][0]['id']; // 0
echo $_POST['comments'][0]['description']; // This is a comment

Exception when returning list of objects with servicestack

I am attempting to get ServiceStack to return a list of objects to a C# client, but I keep getting this exception:
"... System.Runtime.Serialization.SerializationException: Type definitions should start with a '{' ...."
The model I am trying to return:
public class ServiceCallModel
{
public ServiceCallModel()
{
call_uid = 0;
}
public ServiceCallModel(int callUid)
{
this.call_uid = callUid;
}
public int call_uid { get; set; }
public int store_uid { get; set; }
...... <many more properties> ......
public bool cap_expense { get; set; }
public bool is_new { get; set; }
// An array of properties to exclude from property building
public string[] excludedProperties = { "" };
}
The response:
public class ServiceCallResponse
{
public List<ServiceCallModel> Result { get; set; }
public ResponseStatus ResponseStatus { get; set; } //Where Exceptions get auto-serialized
}
And the service:
public class ServiceCallsService : Service
{
// An instance of model factory
ModelFactory MyModelFactory = new ModelFactory();
public object Any(ServiceCallModel request)
{
if (request.call_uid != 0)
{
return MyModelFactory.GetServiceCalls(request.call_uid);
} else {
return MyModelFactory.GetServiceCalls() ;
}
}
}
The client accesses the service with:
JsonServiceClient client = new ServiceStack.ServiceClient.Web.JsonServiceClient("http://172.16.0.15/");
client.SetCredentials("user", "1234");
client.AlwaysSendBasicAuthHeader = true;
ServiceCallResponse response = client.Get<ServiceCallResponse>("/sc");
The "model factory" class is a DB access class which returns a list. Everything seems to work just fine when I access the service through a web browser. The JSON returned from the service starts:
"[{"call_uid":70...."
And ends with:
"....false,"is_new":true}]"
My question is, what here might be causing serialization/deserialization to fail?
Solution
Thanks to the answer from mythz, I was able to figure out what I was doing wrong. My misunderstanding was in exactly how many DTO types there are and exactly what they do. In my mind I had them sort of merged together in some incorrect way. So now as I understand it:
Object to return (In my case, called "ServiceCallModel": The actual class you wish the client to have once ServiceStack has done its job. In my case, a ServiceCallModel is a key class in my program which many other classes consume and create.
Request DTO: This is what the client sends to the server and contains anything related to making a request. Variables, etc.
Response DTO: The response that the server sends back to the requesting client. This contains a single data object (ServiceCallModel), or in my case... a list of ServiceCallModel.
Further, exactly as Mythz said, I now understand the reason for adding "IReturn" to the request DTO is so the client will know precisely what the server will send back to it. In my case I am using the list of ServiceCallModel as the data source for a ListView in Android. So its nice to be able to tell a ListViewAdapter that "response.Result" is in fact already a useful list.
Thanks Mythz for your help.
This error:
Type definitions should start with a '{'
Happens when the shape of the JSON doesn't match what it's expecting, which for this example:
ServiceCallResponse response = client.Get<ServiceCallResponse>("/sc");
The client is expecting the Service to return a ServiceCallResponse, but it's not clear from the info provided that this is happening - though the error is suggesting it's not.
Add Type Safety
Although it doesn't change the behavior, if you specify types in your services you can assert that it returns the expected type, e.g Change object to ServiceCallResponse, e.g:
public ServiceCallResponse Any(ServiceCallModel request)
{
...
}
To save clients guessing what a service returns, you can just specify it on the Request DTO with:
public class ServiceCallModel : IReturn<ServiceCallResponse>
{
...
}
This lets your clients have a more succinct and typed API, e.g:
ServiceCallResponse response = client.Get(new ServiceCallModel());
instead of:
ServiceCallResponse response = client.Get<ServiceCallResponse>("/sc");
See the New API and C# Clients docs for more info.

Categories