Answers such as this seem to go through several steps getting a string for the JSON content returned by an HTTP web request, which is then passed into JSON.net for deserialization.
Is there any shortcut that can be used e.g. do any API methods accept WebResponse or other intermediary objects? I couldn't see any but if they exist it would make for slightly neater code.
No, it does not. JSON.net doesn't mingle with HTTP directly. You have to do manual deserialization.
However, for the sake of neatness, might I suggest RestSharp? It makes my own code look a lot neater. You can implement ISerializer and inside of that, have your serialize method internally utilise all of Newtonsoft. RestClient handles all of the heavy lifting in regards to the WebResponse and will automatically deserialize for you. And no, it's not my library.
public class Employee
{
[JsonProperty(PropertyName = "employee_name")]
public string Name { get; set; }
}
private IRestResponse<Employee> GetEmployee()
{
var request = new RestRequest();
request.Resource = "/api/employees"
request.Method = Method.GET;
var response = Execute<Employee>(request);
}
public IRestResponse<T> Execute<T>(RestRequest request) where T : new()
{
// Pass in reusable ISerializer (can internally use Newtonsoft)
request.JsonSerializer = new JsonNetSerializer();
return client.Execute<T>(request); // Client is of type RestClient
}
Alternatively, you can do this and not implement a serializer at all, because it provides a Content property that you can pass into JSON.net as a string:
public IRestResponse<T> Execute<T>(RestRequest request) where T : new()
{
var response = client.Execute(request); // Client is of type RestClient
return JsonConvert.DeserializeObject<T>(response.Content);
}
Related
Let me start by saying I love the design of ServiceStack as a client. (I've never used it for server side)
I'm writing a C# wrapper for API calls and I keep getting timeout and authentication errors. I've contacted the developers at the other end and they assure me that there are no issues on their end and that I must be doing something wrong. Normally I wouldn't believe them and I'd build a sample project to demonstrate the issue but in this case they pointed me to a web page that will test the same API I'm running in C# and they can re-authenticate as fast as they can click the submit button. I forget the exact site they use for testing but enough of my story... I'm sure I'm doing something wrong I just don't know what.
Here's my Unit Test. If I run it by itself or with one copy it works fine (150-1100ms) but if I make 3 or more copies of it they I will get only 2-3 that pass and the rest will timeout.
[TestMethod]
[Timeout(5000)]
public void Login_Success1()
{
var client = new JsonServiceClient("apiurl");
var response = client.Login("XXXAccessKeyXXX", "XXXSecretKeyXXX");
//Assertions
}
This is my extension method:
public static class Extensions
{
public static (bool Success, string Message, string Token) Login(this JsonServiceClient client, string accessKey, string secretKey)
{
try
{
var response = client.Post(new LoginRequest(accessKey, secretKey));
var authorization = response.Headers.GetValues("Authorization")[0];
return (true, string.Empty, authorization);
}
catch (Exception ex)
{
return (false, $"Authentication failed: {ex.Message}", string.Empty);
}
}
}
And here's the login request:
[Route("/sessions")]
[DataContract]
internal class LoginRequest
{
internal LoginRequest(string accessKey, string secretKey)
{
AccessKey = accessKey ?? throw new ArgumentNullException(nameof(accessKey));
SecretKey = secretKey ?? throw new ArgumentNullException(nameof(secretKey));
}
[DataMember(Name = "accessKey")]
internal string AccessKey { get; set; }
[DataMember(Name = "secretKey")]
internal string SecretKey { get; set; }
}
I think this is all the relevant code but if you feel I missed something please lmk.
Your Request DTO's should implement either IReturn<T> or IReturnVoid otherwise if you're sending just an object you will call the deprecated Post() method:
/// <summary>
/// APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Post(url)) { ... }
/// </summary>
[Obsolete("Use: using (client.Post<HttpWebResponse>(requestDto) { }")]
public virtual HttpWebResponse Post(object requestDto)
{
return Send<HttpWebResponse>(HttpMethods.Post, ResolveTypedUrl(HttpMethods.Post, requestDto), requestDto);
}
Which because ServiceStack doesn't know how you want the Response deserialized it will return the open HttpWebResponse so you can inspect the Response yourself (as you're doing in your example). But this needs to be explicitly disposed as .NET's HttpWebRequest only allows a couple of concurrent requests open per domain which will cause your App to hang/timeout as it's waiting for Requests to be disposed to stay within the concurrent limit.
The preferred solution is to always annotate Request DTO's that you send with ServiceStack clients with a IReturn or a IReturn<T> interface marker, if it has none or you want to ignore the Response implement IReturnVoid otherwise implement IReturn<ResponseDtoType>:
class LoginRequest : IReturnVoid {}
Which instead calls the non-deprecated Post() method which disposes of the HttpWebResponse.
Otherwise if you want to send plain object DTO's you need to dispose of the HttpWebResponse after usage, e.g:
using (var response = client.Post<HttpWebResponse>(new LoginRequest(accessKey, secretKey)))
{
var authorization = response.Headers.GetValues("Authorization")[0];
}
API's which implicitly return HttpWebResponse were deprecated to avoid hard to identify issues like this, instead we recommend using the explicit API above which declares the HttpWebResponse return type at the call-site so it's easier to identify it needs to be disposed.
Also note the ServiceStack Service Clients are opinionated for calling ServiceStack Services, for calling other Services we recommend using HTTP Utils instead.
I have a WebAPI endpoint that to takes in a generic object.
[HttpPost]
[ApiRoute("endpoint/{type}")]
public IHttpActionResult MyPostEndpoint(TypeEnum type, [FromBody] object myObject){}
We work on the object generically but then eventually convert it to our object type, but when we do we have to turn it into a JObject first, so grabbing the object looks like this:
var myfoo = ((JObject) object).ToObject<Foo>();
If I supply Foo directly as my POST parameter (e.g. [FromBody] Foo myObject) then it deserializes the incoming JSON to a Foo, but it won't deserialize to a generic C# object. Is there a way I can get it to deserialize to a generic C# object instead of leaving it a JObject so I can get myfoo like this instead?
var myfoo = (Foo) object;
As generic post method with data returned, I use. Than You can pass any class, so the request is more gerneric
public class Requests
{
//...
public async Task<ResultType> Create<ResultType>(string uri)
{
//TODO implementation of httpclient POST logic go here
var data = await results.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<ResultType>(data);
return result;
}
Call method
List<foo> foos = new List<foo>();
Request requestToServer = new request();
Task.WaitAll(Task.Run(async =>(){
foos = await requestToServer.Create<Foo>("/foo");
}));
Now You can pass any predefined class
I think you can do like following to have a loosely typed method
public static class HttpRequestHelper
{
public static async Task<T> GetDataModelFromRequestBodyAsync<T>(HttpRequestMessage req)
{
dynamic requestBody = await req.Content.ReadAsStringAsync();
object blobModelObject = JsonConvert.DeserializeObject<object>(requestBody);
var blobModel = ((JObject)blobModelObject).ToObject<T>();
return blobModel;
}
}
and usage is like following:
var blobModel = await HttpRequestHelper.GetDataModelFromRequestBodyAsync<RequestBlobModel>(req);
Hope This Helps
.NET CLI
dotnet new web --name "GenericEndpointExample"
cd GenericEndpointExample
dotnet add package SingleApi
Program.cs:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// map generic endpoint
app.MapSingleApi("sapi",
// add your generic request handler
// for example, return the received data (already typed object)
x => Task.FromResult(x.Data),
// add assemblies for resolving received data types
typeof(MyClassName).Assembly, typeof(List<>).Assembly, typeof(int).Assembly);
app.Run();
Example request for type: MyClassName
POST /sapi/MyClassName
{"Name":"Example"}
Example request for generic: Dictionary<string,int?[]>
POST /sapi/Dictionary(String-Array(Nullable(Int32)))
{"key1":[555,null,777]}
GitHub repository with examples
Similar to this old question about prior ASP.NET versions, I want to get the request body of an HTTP POST to be bound to a string. It seems that the method binds, but that value is null, when ASP.NET invokes my controller method:
namespace Demo.Controllers
{
[Route("[controller]")]
public class WebApiDemoController : Controller
{
...
// POST api/values
[HttpPost]
public System.Net.Http.HttpResponseMessage Post([FromBody]string value)
{
// expected: value = json string, actual: json = null.
}
Do I still have to go grab the body from a stream? Or should this just work? When testing the above method, I used the following http headers:
Accept: Application/json
Content-Type: Application/json;charset=UTF-8
I'm passing in the following in the body: { "a": 1 }
I do NOT want to bind to a string variable named a. I want to bind any JSON I get, and then I want to use the JSON content, any arbitrary content at all, from within my method.
If I understood the documentation, the [FromBody] attribute should have done what I wanted, but I'm guessing that the ASP.NET core MVC binding mechanism won't bind a json to a "string value", but perhaps I could do something else that gets me an equivalent level of flexibility.
A similar question here gives me the idea maybe I should have written [FromBody] dynamic data instead of using [FromBody] string value.
Update: There are answers here for .net core 6 and other modern .net core versions.
The cleanest option I've found is adding your own simple InputFormatter:
public class RawJsonBodyInputFormatter : InputFormatter
{
public RawJsonBodyInputFormatter()
{
this.SupportedMediaTypes.Add("application/json");
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
using (var reader = new StreamReader(request.Body))
{
var content = await reader.ReadToEndAsync();
return await InputFormatterResult.SuccessAsync(content);
}
}
protected override bool CanReadType(Type type)
{
return type == typeof(string);
}
}
And in your Startup.cs inside ConfigureServices:
services
.AddMvc(options =>
{
options.InputFormatters.Insert(0, new RawJsonBodyInputFormatter());
});
That will let you get at the raw JSON payload in your controllers:
[HttpPost]
public IActionResult Post([FromBody]string value)
{
// value will be the request json payload
}
The following works in .net core 1.x, but not in .net core 2.x.
As I commented, the solution is to use [FromBody]dynamic data as my parameter list, using dynamic instead of string, and I will receive a JObject.
Caution: If your architecture calls for a single WebApi server to be equally fluent in producing XML and JSON, depending on content-type header entries, this kind of direct-JSON-consumption strategy can backfire on you. (Supporting both XML and JSON on the same service is possible with sufficient work, but then you're taking stuff that was further UP the MVC asset pipeline and moving it down into your controller methods, which turns out to be against the spirit of MVC, where models come to you as POCOs already parsed.)
Once you convert to a string inside the method, converting the incoming JObject (Newtonsoft.JSON in memory data type for JSON) to a string.
Found at other answer here.
Sample code, thanks to Jeson Martajaya:
With dynamic:
[HttpPost]
public System.Net.Http.HttpResponseMessage Post([FromBody]dynamic value)
{
//...
}
Sample code with JObject:
[HttpPost]
public System.Net.Http.HttpResponseMessage Post([FromBody]Newtonsoft.Json.Linq.JObject value)
{
//...
}
Found a solution for ASP.NET Core 3.1 Web API.
Looks like following:
public async Task<IActionResult> PutAsync([FromBody] System.Text.Json.JsonElement entity)
{
// your code here
}
The following two methods works in ASP.NET core 2 to read the raw json string.
1) This one has better performance.
[HttpPost]
public async Task<ActionResult<int>> Process()
{
string jsonString;
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
jsonString = await reader.ReadToEndAsync();
}
2)
[HttpPost]
public async Task<ActionResult<int>> Process([FromBody]JToken jsonbody)
{
var jsonString = jsonBody.ToString();
Alternatively, you could also just accept a JObject and you would be able to use Linq to Json ot even directly ToString() if you really need the string.
Based on Saeb Amini's excellent answer above, this extends his solution to be for plain-text as well. The only changes here are adding the "text/plain" mime-type, and adding a namespace and required usings.
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Formatters;
namespace AspExtensions // or whatever
{
// see: https://stackoverflow.com/a/47807117/264031
public class RawStringBodyInputFormatter : InputFormatter
{
public RawStringBodyInputFormatter()
{
this.SupportedMediaTypes.Add("text/plain");
this.SupportedMediaTypes.Add("application/json");
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
using(var reader = new StreamReader(request.Body)) {
string content = await reader.ReadToEndAsync();
return await InputFormatterResult.SuccessAsync(content);
}
}
protected override bool CanReadType(Type type)
{
return type == typeof(string);
}
}
}
If you don't mine forgoing the automagic binding, this can be placed directly in an Http handler on a Controller:
using StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8);
var value = reader.ReadToEndAsync().GetAwaiter().GetResult();
I see that Sam has already been down voted for saying pretty much the same thing, but in testing using Postman I find that if I set the request body to just a simple double quoted string ASP binds it fine with the default '[FromBody]string value' argument.
"just send your string like this without any curly braces"
Not sure whether application/json is supposed to accept data in this format. Hopefully by posting this someone knowledgeable will pipe up and state whether this is valid or not.
You need a type to bind the data. Example:
public class Person
{
public string Name {get; set;}
}
for data { "Name" : "James"}
If you want to receive a string you need to pass it as a string. Your JSON should be enclosed in quotes:
'{ "a": 1 }'
I'm trying to build some kind of RESTful-like API, I'm aware of that my first draft probably isn't anything near the real RESTful design pattern. However my real question is how should I consume my service using JSON?
In my so called real world example I want my users to sign in via the service so I have this AuthenticationController
namespace RESTfulService.Controllers
{
public class AuthenticationController : ApiController
{
public string Get(string username, string password)
{
// return JSON-object or JSON-status message
return "";
}
public string Get()
{
return "";
}
}
}
Considering the increasing popularity with the technology I assumed that very little code would be needed for consuming the service. Do I really need to serialize the JSON manually with some kind of third party package like json.net? Beneath is my draft for the client
private static bool DoAuthentication(string username, string password)
{
var client = InitializeHttpClient();
HttpResponseMessage response = client.GetAsync("/api/rest/authentication").Result;
if (response.IsSuccessStatusCode)
{
//retrieve JSON-object or JSON-status message
}
else
{
// Error
}
return true;
}
private static HttpClient InitializeHttpClient()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost/");
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
How do I send JSON from service and how do I interpreting it on the client?
Have a look at the System.Net.Http.HttpContentExtensions in System.Net.Http.Formatting.dll. As explained here (and suggested by Mike Wasson in a comment above), you can call ReadAsAsync<T>() on the response content to deserialize from JSON (or XML) to a CLR type:
if (response.IsSuccessStatusCode)
{
var myObject = response.Content.ReadAsAsync<MyObject>();
}
If you need to customize the deserialization, that article links to a further explanation of MediaTypeFormatters.
I have an application written in Windows Service and this app need to make a call to a WebAPI written in Asp.Net MVC 4 WebAPi. this method in WebAPI return a DTO with primitive type, something like:
class ImportResultDTO {
public bool Success { get; set; }
public string[] Messages { get; set; }
}
and in my webapi
public ImportResultDTO Get(int clientId) {
// process.. and create the dto result.
return dto;
}
My question is, how can I call the webApi from the Windows Service? I have my URL and value of parameter, but I don't know how to call and how to deserialize the xml result to the DTO.
Thank you
You could use System.Net.Http.HttpClient. You will obviously need to edit the fake base address and request URI in the example below but this also shows a basic way to check the response status as well.
// Create an HttpClient instance
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:8888/");
// Usage
HttpResponseMessage response = client.GetAsync("api/importresults/1").Result;
if (response.IsSuccessStatusCode)
{
var dto = response.Content.ReadAsAsync<ImportResultDTO>().Result;
}
else
{
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}
You can install this NuGet package Microsoft ASP.NET Web API Client Libraries to your Windows Service project.
Here is a simple code snippet demonstrating how to use HttpClient:
var client = new HttpClient();
var response = client.GetAsync(uriOfYourService).Result;
var content = response.Content.ReadAsAsync<ImportResultDTO>().Result;
(I'm calling .Result() here for the sake of simplicity...)
For more sample of HttpClient, please check this out: List of ASP.NET Web API and HttpClient Samples.