I've successfully created the Hello World example from the ServiceStack web site and modified it for my needs. Read: Basic authentication, a bit of database access. etc.
I'd like to access the hello service from the test client
[Authenticate]
[Route("/hello/{Name}")]
public class HelloRequest : IReturn<HelloResponse>
{
public string Name { get; set; }
}
public class HelloResponse
{
public string Result { get; set; }
}
public class HelloService : Service
{
public object Any(HelloRequest request)
{
var userSession = SessionAs<CustomUserSession>();
var roles = string.Join(", ", userSession.Roles.ToArray());
return new HelloResponse { Result = "Hello, " + request.Name + ", your company: " + userSession.CompanyName};
}
}
I see a few examples out there which appear to be using the "HelloRespnse" and "Hello" types, but I cannot quite figure out how one would properly import the DTO(s) created in the service. From the ServiceStack wiki:
HelloResponse response = client.Get(new Hello { Name = "World!" });
response.Result.Print();
So the summary of my question: How do I easily re-use DTOs created in my service within a C# client?
Sorry in advance for my lack of totally understanding SS and thanks for the help.
The usual way is to create a separate assembly containing just your DTO models, add a reference to this assembly to both your service implementation and client. See Recommended servicestack api structure for more info.
Related
I am unable to connect SAP Business One Service Layer using .NET Framework. It throws the error Internal Server Error with status code 500.
HttpClient client = new HttpClient();
string json = JsonConvert.SerializeObject(new {
CompanyDB = "company",
UserName = "username",
Password = "password"
}
);
var response = await client.PostAsync("service layer URL", new StringContent(json,
Encoding.Default, "application/json"));
Although I am able to connect to the service layer using the .NET Core application with the same code.
I have gone through this blog https://blogs.sap.com/2015/07/15/how-to-consume-service-layer-odata-services-from-net-via-wcf/
But I am unable to Add Service Reference for the service layer.
I am not sure whether this problem belongs to the service layer or my program. Do we need to do extra code to call the OData service (as the service layer is oData V3 or V4) from the .net framework?
Please help me to solve this problem.
Regarding your login problem you could intercept the sent HTTP messages from both of your applications (.NET Framework vs. .NET Core) and compare them. For example by using Fiddler.
You might find a little difference which prevents SL from processing your request.
I was able to connect to SL successfully in a .NET Core project by using the RestSharp library. Maybe this will get you started:
private static void Main(string[] args)
{
var client = new RestClient("https://<host>:<port>/b1s/v2");
client.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
// allow all certificates for testing purposes
return true;
};
var resolver = new CamelCasePropertyNamesContractResolver();
// respect the names defined in the JsonProperty annotations
resolver.NamingStrategy.OverrideSpecifiedNames = false;
client.UseNewtonsoftJson(new JsonSerializerSettings() { ContractResolver = resolver });
var loginRequest = new RestRequest("Login");
loginRequest.AddJsonBody(new LoginBody() { CompanyDB = "yourCompanyDb", UserName = "user1", Password = "myPassword" });
var response = client.Post<LoginResponse>(loginRequest);
if (response.Data != null)
{
var itemRequest = new RestRequest("Items('57387')"); // get an item
// add the session cookie
itemRequest.AddHeader("Cookie", $"B1SESSION={response.Data.SessionId}");
var itemResponse = client.Get<Items>(itemRequest);
}
}
The JSON POCOs:
public class LoginBody
{
[JsonProperty("CompanyDB")]
public string CompanyDB { get; set; }
[JsonProperty("Password")]
public string Password { get; set; }
[JsonProperty("UserName")]
public string UserName { get; set; }
}
public class LoginResponse
{
[JsonProperty("SessionId")]
public string SessionId { get; set; }
}
public class Items
{
[JsonProperty("ItemCode")]
public string ItemCode { get; set; }
[JsonProperty("ItemName")]
public string ItemName { get; set; }
}
Regarding your question to add a Service Reference I am not sure what you want to achieve. If you want to generate a OData client along with all required classes and data models you might want to look at the Unchase OData Connected Service Visual Studio extension. For me this worked well since it generates a functioning SL/OData client based on the Microsoft.OData.Client.DataServiceContext class. Please see the OData client documentation for more examples on how too use it.
I wrote a library called B1SLayer that aims to make Service Layer requests a lot easier with .NET, have a look:
https://github.com/bgmulinari/B1SLayer/
https://www.nuget.org/packages/B1SLayer/
Just create your SLConnection instance and the session is managed automatically. Check the GitHub page for request samples.
var serviceLayer = new SLConnection("https://sapserver:50000/b1s/v1", "CompanyDB", "manager", "12345");
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.
What I would like to do is the following:
var client = new JsonServiceClient(ServiceUrl);
var request = new FooQuery {Id = 1};
IEnumerable<Project> response = client.Get(request);
However, my FooQuery doesn't implement any IReturn, and I'd like it not to (it's in a library without ServiceStack references). Here's my service side:
Library of business objects:
public class ProjectQuery
{
public int Id { get; set; }
}
AppHost:
Routes.Add<ProjectQuery>("/project", "GET");
Service:
public object Get(Foo request)
{
// do stuff.
}
Is there some nice, clean way to create the JsonServiceClient without using the IReturn interface on my business object?
Looks like there's no way not to use IReturn if you don't want to provide a URL to the JsonServiceClient Get() requests. Just decided to create another set of DTOs in my ServiceStack implementation, that are essentially mirrors of the real DTOs in another library. Then when a request comes in to my SS DTO, I create the other library's DTO, set each property, and pass it along.
Not pretty, but that's the best I could find so far.
I had the same problem using IReturn and Routes, as I wanted to use the DTOs
in assemblies with business logic, without ServiceStack references.
It worked for me, using in the Client Model
public class TestRequest
{
public int vendorId {get; set; }
public string barcode {get; set; }
public string username { get; set; }
public string password { get; set; }
}
then in the AppHost
Routes.Add<TestRequest( "/TestAPI/Reservation/{vendorId}/{barcode}"," GET,OPTIONS")
.Add<TestRequest>("/TestAPI/Reservation", "POST, OPTIONS")
and the call for JsonServiceClient with POST
request.vendorId=12344;
request.barcode="AAS1223";
TestResponse response = client.Post<TestResponse>(server_ip + "/TestAPI/Reservation", request);
OR with GET
TestResponse response = client.Get<TestResponse>(server_ip + "/TestAPI/Reservation/12344/AAS1223?username=John&password=99");
Then in the service Get or Post functions
public TestResponse Get(TestRequest request)
{
// request members hold the values of the url.
return DoBusinessLayerWork(request);
}
Using the Send() method from the JsonServiceClient type is the way to go about doing this.
I'm fairly new to restful services, and I've just implemented the test code to get a ServiceStack restful service going with the Swagger plugin working as well, which leads me to my question...
inside swagger-ui/index.html there is a field for 'api_key'. I know the variable name is umm... variable, and I can set it too whatever I like, but I'm slightly confused what it's used for and whether I should be making use of it.
Also, if I do use it, how does servicestack present that value to me on the server side?
Here is the Test Service I've got up and running from the documentation...
[Api("Hello Web Services")]
[Route("/Hello", Summary = #"Noel's ServiceStackSwagger thingy", Notes = "Some more info in here cause these are notes")]
[Route("/Hello/{name}", Summary = #"N031'5 ServiceStackSwagger thingy", Notes = "Some more info in here cause these are notes", Verbs="GET,POST" )]
public class Hello
{
[ApiMember(Name = "Name", Description = "This is a description", ParameterType = "path", DataType = "string", Verb="GET,POST")]
public string Name { get; set; }
}
public class HelloResponse
{
public string Result { get; set; }
}
public class HelloService : Service
{
public object Any(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name };
}
}
to answer my own follow up request of Esker, here is how to use the API Key thingy...
public class HelloService : Service
{
public object Any(Hello request)
{
string api_key = this.Request.Headers["api_key"];
return new HelloResponse { Result = "Hello, " + request.Name };
}
}
but also required is some extra javascript to include it in the header like so (inside swagger-ui/index.html)...
$(function () {
$.ajaxSetup({
beforeSend: function (jqXHR, settings) {
jqXHR.setRequestHeader("api_key", $("#input_apiKey").val());
}
});
});
which I found in an answer in this question...
How to get Swagger to send API key as a http instead of in the URL
Swagger UI has a general concept of supplying an api_key to use in every request sent to your service, documented here. It can either be sent as a query string or header value, and you can also change the name of the parameter as described in the above link.
You only need to configure this if you actually do require an API key in your ServiceStack service (e.g. if you have a request filter that checks for and validates an API key, perhaps).
The reason you might need to configure a default API key value in the JavaScript code to set up Swagger instead of having the user type in an API key is that as soon the index.html page loads, it will send multiple requests to your service to retrieve the metadata, so it wants to know what API key value to send by default for these metadata requests before the user can begin to interact.
I'm trying to test code around a web service that is not available yet. I'm trying to dummy up my own version. According to the specs it will be called like this.
var service = new Service();
service.SD = new ServiceData();
service.SD.ID = "ABC123";
service.SD.Auth = "00000";
string result = service.DoMyThing();
This is the closest I've gotten.
var service = new Service();
service.set_SD(new ServiceData());
service.get_SD().ID = "ABC123";
service.get_SD().Auth = "00000";
service.DoMyThing();
The problem is with the SD property. How do I write the service so that Visual Studio 2008 generates the web reference correctly?
Here is my current dummy web service code.
public class Service : System.Web.Services.WebService
{
// This doesn't show up in the generated proxy at all
public static ServiceData SDTest;
// For extra credit explain why this needs to be static for it to work
private static ServiceData _sd;
public ServiceData SD
{
[WebMethod(EnableSession = true)]
get { return _sd; }
[WebMethod(EnableSession = true)]
set { _sd = value; }
}
[WebMethod]
public string DoMyThing()
{
// Presumably the real service accesses SD in here
return "";
}
}
public class ServiceData
{
public string ID { get; set; }
public string Auth { get; set; }
}
Your design is flawed. Web services are not meant to have properties. They should only expose methods, the reason being that the HTTP protocol is stateless (and web services assume this too), so exposing a property doesn't make sense unless you want it to apply to all callers of the instance (and still, even in that situation, it doesn't make sense to expose it).
Rather, what you want to do is have the DoMyThing method take the instance of ServiceData (if required) and operate on that, returning the appropriate result set.
If you really have a need to expose properties of the service, you would have a GetProperties method (or something like that) which takes no parameters and returns the appropriate data structure with the service information.
I'm with casperOne on this. I think using fakie properties are more annoying than useful.
Still, if you're married to this just eliminate the getter for the property. You don't need it. Do this instead:
var service = new Service();
ServiceData sd = new ServiceData();
sd.ID = "ABC123";
sd.Auth = "00000";
service.SD = sd;
string result = service.DoMyThing();
If Visual Studio still names the setter property incorrectly you can use one of the soap attributes to rename it.
EDIT: You'll also need to define SD as a SOAP Header.
You can't do this, so don't try to "fake it". The best you can do is:
var service = new Service();
ServiceData sd = new ServiceData();
sd.ID = "ABC123";
sd.Auth = "00000";
string result = service.DoMyThing(sd);
For those that may be interested.
This more accurately reflects the spec than my sanitized version above (I didn't know "TypeNameValue" was a key clue, sorry!).
var service = new Service();
service.ServiceDetailsValue = new ServiceDetails();
service.ServiceDetailsValue.ID = "ABC123";
service.ServiceDetailsValue.Auth = "00000";
string result = service.DoMyThing();
And this is the dummy web service code that works.
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(Name="TestService", ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{
public ServiceDetails SDTest;
[WebMethod]
[SoapDocumentMethod(Binding = "TestService")]
[SoapHeader("SDTest", Required = true)]
public string DoMyThing()
{
return "";
}
}
public class ServiceDetails : System.Web.Services.Protocols.SoapHeader
{
public string ID { get; set; }
public string Auth { get; set; }
}