I have a question on the implementation of android -> JSON -> RESTful WCF webserive architecture. I'm primarily coming from a C# pattern based background so finding the android architecture hard to get my head around. The way I have designed my C# webservice is to have a whole "model" namespace that defines all my business objects such as User, UserProfile, UserSearchCriteria, UserPhoto etc as CLR objects.
I have exposed my webservice interface through WCF using the RESTful model, combined with request/response pattern which I am used to. One example of a REST web method is as follows.
[OperationContract]
[WebInvoke(
Method = "POST",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
UriTemplate = "/GetSeeds"
)]
GetSeedsResponse GetSeeds(GetSeedsRequest request);
Response object
[DataContract]
public class GetSeedsResponse
{
public GetSeedsResponse(List<Seed> seeds)
{
Seeds = seeds;
}
[DataMember]
public List<Seed> Seeds { get; set; }
}
Request object
[DataContract]
public class GetSeedsRequest : BaseRequest
{
public GetSeedsRequest(int userId, int numberToTake, int startPosition)
{
NumberToTake = numberToTake;
StartPosition = startPosition;
UserId = userId;
}
[DataMember]
public int NumberToTake { get; set; }
[DataMember]
public int StartPosition { get; set; }
[DataMember]
public int UserId { get; set; }
}
Note: all my request objects inherit from my own custom base request object which merely provides them all a common header for authentication, as follows (I am not sure if this is the best way to handle authentication but it is what I am familiar with so have done it this way for now)
[DataContract]
public abstract class BaseRequest
{
[DataMember]
public Header Header { get; set; }
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.2022")]
[System.SerializableAttribute]
[System.Diagnostics.DebuggerStepThroughAttribute]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:MyStory:Web:Services:ServiceModel:Headers")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="urn:MyStory:Web:Services:ServiceModel:Headers", IsNullable=true)]
public class Header
{
public int UserId { get; set; }
public string SessionToken { get; set; }
}
So far I have managed to successfully call the RESTful webmethod from android with the following code in an AsyncTask background worker.
protected ArrayList<Seed> doInBackground(String... params)
{
try
{
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(SERVICE_URI);
post.setHeader("Accept", "application/json");
post.setHeader("Content-type", "application/json");
JSONObject data = new JSONObject();
data.put("UserId", 1);
data.put("NumberToTake", 10);
data.put("StartPosition", 0);
StringEntity entity = new StringEntity(data.toString());
post.setEntity(entity);
HttpResponse response = client.execute(post);
}
...
}
I would then seek to extract the response and manually construct a "Seed" object based on an android class I would define, matching the properties of the CLR version, and manually extracting from the JSON response the indexed fields for which I would know which one corresponded to which property.
My question is in two parts
1) I cannot figure out how to pass the header object. I have tried the following
JSONObject header = new JSONObject();
header.put("UserId", 1);
header.put("SessionToken", "");
JSONObject data = new JSONObject();
data.put("UserId", 1);
data.put("NumberToTake", 10);
data.put("StartPosition", 0);
data.put("Header", header);
But this fails to work with a generic "Bad request error" exception and the webservice doesn't even enter the webmethod (I have set a break point on line 1 which gets hit without the header, but doesn't get hit when I include the header), so I assume I am not understanding JSON properly or the way to pass an embedded object in the Request object
2) Am I doing the whole architectural approach correct? It seems quite laborious and prone to error to be manually constructing array indexed requests and responses for every web service call. How should I be structuring my project, creating matching android objects for all CLR objects my webservice wants to accept as parameters or return as response objects? Is there a lot more power to JSON that I am not aware of that I should be using? With the requests, do I need to create bespoke individual AsyncTask objects for every single web service call I might make? There are about 40 different web methods my app will call, does this mean 40 AsyncTask objects kept in a package somewhere in my codebase? I'm struggling to figure out the "best practices" for designing a decent sized app with decent amounts of web service interaction, if anyone has any books they could reccomend or tutorials that touch on best practices for android project design, architecture for my scenario then I would be very grateful.
Apologies for the length of the post
Decouple the Android side from the Windows server side. The reason we use JSON and REST is that they are system agnostic. Don't worry about the structure on the Windows side. Instead focus on what JSON request will work for the server, and focus on creating that JSON on Android.
First, get a REST client like POSTMan and figure out what the REST call looks like, and what the JSON body looks like.
Then, figure out how to make Android send that body.
Finally, figure out how to make Android generate the JSON body programatically. For this last one, I strongly recommend Gson. It will let you work with higher level objects instead of constructing your JSON manually. If you update your request with the JSON body I'll update this response with how it could be generated in Gson.
As a side note, I recommend you look at OkHttp instead of the Apache HTTP client. It's much more full featured and robust. This isn't necessary at all, but it will make your life easier.
Related
I have a webapi myproj.csproj that is a .net core 3.0 web api. it compiles to a myproj client that contains all the necessary api endpoints and all types explicitly associated with those endpoints.
I have an endpoint that is like the following:
public async Task<string> GetReportDataAsync(Guid reportType, long id)
where I pass in a guid of the report type and another id, it does a calculation and returns a json string. I would like to make the type used to serialize to json on the server side available in my client so I can easily deserialize the string:
string reportOutput = await ApiClient.GetReportDataAsync(<reportTypeGuid>, <id>);
JsonConvert.DeserializeObject<MyReportType>(reportOutput);
without having to create a new API endpoint for every report type I add. Is there a way to force the web api to compile the types I ask it to for easy client consumption?
I could create a new api endpoint for every single report type, but I would rather not do that if I have 10+ report types. The existing endpoint already does what I need it to, I just want to deserialize the type.
I realize it's designed this way to have only what the client would need, just wondering if anyone has run into this.
Report Type (there are more properties than this but they are all of type long):
namespace Reports.ReportTypes
{
public class MyReportType : ReportTypeBase
{
public long JobRunId { get; set; }
public long Size { get; set; }
public long Count { get; set; }
//...
public long SomeOtherCount { get; set; }
}
}
How can I export a C# class (DTO) in the dtos.ts file generated with npm run typescript-ref http://localhost:5000 src/myproject without referencing in the request class?
Note: we have several C# DTO classes (MutationAddressChange, MutationCEOChange...) that we map to the domain class using automapper. So we want to use the C# DTO classes as well in Angular to populate the corresponding type (e.g.MutationAddressChangesCreateDTO) and send it to the web server. Therefore, in the CreateMutationRequest class, we accept an object instead of a specific class.
example DTO-Class:
public class MutationAddressChangesCreateDTO
{
public string Street { get; set; }
public string POBox { get; set; }
public string Zipcode { get; set; }
}
ServiceStack Request-Class
public class CreateMutationRequest : IPost
{
public object Mutation { get; set; }
}
Angular expected use:
{
var mutationAddressChangesCreateDTO= new MutationAddressChangesCreateDTO();
mutationAddressChangesCreateDTO.dateOfMutation = ...
const request = new CreateMutationRequest ({
mutation: mutationAddressChangesCreateDTO,
});
this.client.post(request)
...
}
A limitation of Add ServiceStack Reference feature is that your DTOs cannot have any object or interface properties which creates a black hole in your Services contract that's impossible to generate a Typed API for.
I'd recommend against having any object or interface properties in your DTOs which other than being a source of runtime issues is also limited by security restrictions.
You could use an untyped data structure like a Dictionary<string,string> to store arbitrary values, you can some find other alternatives in this Customer Forums thread.
Although it's discouraged you could still have object properties in your ServiceStack Request DTOs, you just wont be able to generate a typed API for them but you should still be able to send them as an anonymous arg, e.g:
this.client.post(request, { mutation: dto });
Object properties are handled with JS Utils by default which should deserialize it into a Dictionary<string,object> which you should be able to convert back into a C# type using ServiceStack's Reflection Utils, e.g:
public object Any(CreateMutationRequest request)
{
var payload = request.Mutation as Dictionary<string,object>;
var payloadRequest = payload.FromObjectDictionary(typeof(TheType));
}
A similar approach to this that avoids using object is to send a serialized JSON payload in a string property, e.g:
request.mutation = JSON.stringify(payload);
Which you can deserialize using JS Utils again, e.g:
public object Any(CreateMutationRequest request)
{
var payload = JSON.parse(request.Mutation);
var payloadRequest = payload.FromObjectDictionary(typeof(TheType));
}
With that said I don't recommend any of these untyped strategies and would personally create Typed services for each API that's needed which is more intuitive, discoverable & resilient, any shared functionality can easily be handled in your Services implementation using ServiceStack's AutoMapping and .NET's powerful reflection capabilities.
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.
Apparently my education has failed me, because I didn't realize that methods in C# cannot be serialized. (Good to know.)
I am trying to create a WCF service that returns a simple class I created. The problem is that this simple class contains methods that I want to expose, and the caller of my service won't have any access to them (assuming they won't have a .dll containing the class declaration).
public class Simple
{
public string Message { get; set; }
private const string _Hidden = "Underpants";
public string Hidden
{
get { return _Hidden; }
}
public string GetHidden()
{
return _Hidden;
}
}
I set up a WCF service (let's call it MyService) to return an instance of my Simple class. To my frustration, I'm only getting a partial build of my class back.
public void CallService()
{
using (var client = new MyService.Serviceclient())
{
Simple result = client.GetSimple();
string message = result.Message; // this works.
string hidden = result.Hidden; // this doesn't.
string fail = result.GetHidden(); // Underpants remains elusive.
}
}
Is there any type of workaround where I'm able to set up a property or method on my class that will be accessible to whomever calls my service? How does one handle constants or other methods that are set up in a class that only exists in a service?
Typically you would create three different projects.
1. Service project
2. Client project
3. Data project
The Data project contains only the data classes - no application code. The methods and constants in these data classes should be independent of the Service/Client projects.
The Data project is included as a reference in both the Service and Client projects so that serialization and deserialization happen against the same binary - and you get to retain your constants/methods/etc.
The downside here is that all your clients will either have to be .NET apps, or you will have to provide different data libraries for each platform you wish to support.
As far as I know the only things that can be returned in a WCF service are primitives or a class with public properties that have a get method on them. From a high level WCF exists to allow you to specify a contract between the client and the server that it in theory transportation agnostic (ie you can swap out an HTTP endpoint for a netTcp endpoint and the service will function the same way from a contractual level).
The question to answer then is what data are you trying to pass back in this service call. If it's an object called simple with the data points of Message and Hidden then I would advise creating a data class called Simple that has those values as properties:
[DataContract]
public class Simple
{
[DataMember]
public string Hidden { get; set; }
[DataMember]
public string Message { get; set; }
}
When the client receives the response back Message and Hidden will be populated with whatever you have set their values to on the server side.
The DataMember attribute can only be used on properties and fields. This means that a WCF response can only serialize these types.
If you really want to only use the const in your WCF contract You could convert it to a field and place the DataMember attribute on it:
public class Simple
{
[DataMember]
public string Message { get; set; }
[DataMember]
public const string Hidden = "Underpants";
}
To be able to do this the field must be accessible (public).
Add the DataMember attribute to your property. To do so, you must have both a get and a set defined.
[DataMember]
public string Hidden
{
get { return _Hidden; }
set { }
}
technically you could do
public class thingToSerialize{
public Func<ArgType1,ArgType2...,ReturnType> myFunction{get;set;}
}
and then assign it a lambda that takes the arguments and returns the return type
before serializing
I want to deserialise a JSON response from a Silverlight client.
I have my DTOs in a Portable Class Library, referenced from both server and client.
public class MyDTOResponse
{
public IEnumerable<MyType> ResponseData {get; set; }
}
When I use the ServiceStack C# client (ie NOT from Silverlight), everything works fine: MyType gets hydrated on the client.
From Silverlight, however ResponseData is null.
Simple types work fine from Silverlight also. For example, this works:
public class MyDTOResponse
{
public IEnumerable<string> ResponseData {get; set; }
}
Note: no annotations on the DTOs.
On the client, I am using:
var serviceClient = new ServiceStack.ServiceClient.Web.JsonServiceClient(baseUri);
My work around is to change the DTOs so they use just simple types, then manually hydrate my business objects on the client.
Can I do better than this?
Try adding [DataContract] Attribute to the class MyDTOResponse and [DataMember] Attribute to the Property ResponseData