How to avoid JSON deserialize / serialize in Web API? - asp.net-core

I have the following code in a Web API controller in ASP.NET 2.0:
[HttpGet]
[Route("{controllerName}/{nodeId}/ConfigurationValues")]
public async Task<IActionResult> GetConfigurationValues(string controllerName, byte nodeId, string code)
{
string payload = ...
HttpResponseMessage response = await deviceControllerRepository.ExecuteMethodAsync(controllerName, "GetNodeConfigurationValues", payload);
string responseJson = await response.Content.ReadAsStringAsync();
var configurationValues = JsonConvert.DeserializeObject<List<ConfigurationValue>>(responseJson);
return Ok(configurationValues);
}
How can I avoid having to de-serialize the responseJson into a .NET object before returning it as it already is in the correct JSON format?
I tried to change the method into returning HttpResponseMessage, but that resulted in incorrect JSON being passed back to the caller.

ControllerBase class has a Content() method for this purpose. Make sure you set correct Content-Type header in output packet:
return Content(responseJson, "application/json");

You can return ContentResult instead of Ok() for avoiding unnecessary serialization:
return new ContentResult
{
Content = responseJson,
ContentType = "application/json",
StatusCode = 200
};

Related

how to convert string response to JSON response

Here if the response is success I want to return JSON responseStr with status code System.Net.HttpStatusCode.Accepted. But using this code I am able to return string. But I want to return Json response.
public async Task<IActionResult> Receipt()
{
var response = await api.Get(string.Format(url));
var responseStr = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
return StatusCode((int)System.Net.HttpStatusCode.Accepted, responseStr);
}
else
{
_logger.LogError(responseStr);
return BadRequest(responseStr);
}
}
Ordinarily a 202 / Accepted response wouldn't contain a body in the response.
But you can achieve this by either of the following:
return Accepted(responseStr);
Note that this will return the responseStr field as a string - because it is a string.
If you want to return it as a json object - you could deserialise it first and return the object in the same manner:
var asObject = JsonConvert.DeserializeObject<Type>(responseStr);
return Accepted (asObject);
Also, you should move the ReadAsStringAsync call to inside the if (Successful) {} block.

.Net WebAPI returning Protocol Buffer: Protocol message contained a tag with an invalid wire type

I have a *.proto file that defines two messages: MyRequest and MyResponse. This is defined in a common .NET Standard 2.0 library and uses the following NuGet packages:
<PackageReference Include="Google.Protobuf" Version="3.18.0" />
<PackageReference Include="Grpc" Version="2.40.0" />
<PackageReference Include="Grpc.Tools" Version="2.40.0">
The Server
I have a .NET Framework (4.8) WebAPI and the following Action Method:
[HttpPost]
public async Task<IHttpActionResult> FetchSomeData()
{
using (var ms = new MemoryStream(2048))
{
await content.CopyToAsync(ms).ConfigureAwait(false);
byte[] bytes = ms.ToArray();
MyRequest request = MyRequest.Parser.ParseFrom(bytes);
MyResponse response = await SomeMethodAsync(request).ConfigureAwait(false);
byte[] responseByteArray = response.ToByteArray();
return this.Ok(responseByteArray);
}
}
So this successfully receives a MyRequest object sent via HttpPost, and based on the data in that object, generates a MyResponse object and returns that.
The Client
I have a .Net 5 client that consumes this service with an HttpClient:
// Prepare Request
MyRequest webRequest = new() { ... };
byte[] byteArray = webRequest.ToByteArray();
ByteArrayContent byteContent = new(byteArray);
// Send data
HttpClient client = this.HttpClientFactory.CreateClient("Blah");
HttpResponseMessage webResponse = await client.PostAsync(new Uri("...", UriKind.Relative), byteContent).ConfigureAwait(false);
// Read response
HttpContent content = webResponse.Content;
byte[] webMethodResponseAsByteArray = await content.ReadAsByteArrayAsync().ConfigureAwait(false);
MyResponse webMethodResponse = MyResponse.Parser.ParseFrom(webMethodResponseAsByteArray);
The HttpClient "Blah" is only configured with a base URL and security token.
However....when I call the ParseFrom on the last line I get the following exception:
Google.Protobuf.InvalidProtocolBufferException
HResult=0x80131620
Message=Protocol message contained a tag with an invalid wire type.
Source=Google.Protobuf
StackTrace:
at Google.Protobuf.UnknownFieldSet.MergeFieldFrom(ParseContext& ctx)
at Google.Protobuf.UnknownFieldSet.MergeFieldFrom(UnknownFieldSet unknownFields, ParseContext& ctx)
at namespace.MyResponse.pb::Google.Protobuf.IBufferMessage.InternalMergeFrom(ParseContext& input) in ....
Not sure how to solve this one...
I solved the problem I was facing by changing the .NET Framework (4.8) Server's method:
From
[HttpPost]
public async Task<IHttpActionResult> FetchSomeData()
{
using (var ms = new MemoryStream(2048))
{
await content.CopyToAsync(ms).ConfigureAwait(false);
byte[] bytes = ms.ToArray();
MyRequest request = MyRequest.Parser.ParseFrom(bytes);
MyResponse response = await SomeMethodAsync(request).ConfigureAwait(false);
byte[] responseByteArray = response.ToByteArray();
return this.Ok(responseByteArray);
}
}
TO
[HttpPost]
public async Task<HttpResponseMessage FetchSomeData()
{
using (var ms = new MemoryStream(2048))
{
await content.CopyToAsync(ms).ConfigureAwait(false);
byte[] bytes = ms.ToArray();
MyRequest request = MyRequest.Parser.ParseFrom(bytes);
MyResponse response = await SomeMethodAsync(request).ConfigureAwait(false);
byte[] responseByteArray = response.ToByteArray();
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(responseByteArray),
};
return result;
}
}
However, also considering the gRPC approach mentioned by #MarcGravell (see comments/conversation).

Error in ASP.NET Core MVC and Web API project

I have an ASP.NET Core MVC and also Web API project.
This error occurs when I try to send project information to the API (of course API works fine and I do not think there is a problem):
UnsupportedMediaTypeException: No MediaTypeFormatter is available to read a "TokenModel" object of "text / plain" media content.
My code is:
public class TokenModel
{
public string Token { get; set; }
}
and in AuthController I have:
var _Client = _httpClientFactory.CreateClient("MyApiClient");
var jsonBody = JsonConvert.SerializeObject(login);
var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
var response = _Client.PostAsync("/Api/Authentication", content).Result;
if (response.IsSuccessStatusCode)
{
var token = response.Content.ReadAsAsync<TokenModel>().Result;
}
The error occurs on this line:
var token = response.Content.ReadAsAsync<TokenModel>().Result;
HomeController:
public IActionResult Index()
{
var token = User.FindFirst("AccessToken").Value;
return View(_user.GetAllUsers(token));
}
UserRepository:
public List<UserViewModel> GetAllUsers(string token)
{
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var res = _client.GetStringAsync(UrlMyApi).Result;
List<UserViewModel> users = JsonConvert.DeserializeObject<List<UserViewModel>>(res);
return users;
}
Your API is returning content-type of text/plain and none of the default media type formatters(MediaTypeFormatter) which ReadAsAsync<string>() will try to use support parsing it as is. They work with JSON/XML. You can go a couple of ways but maybe the easiest is to read the content as string and deserialize it after:
var tokenJSON = response.Content.ReadAsStringAsync().Result;
var token = JsonConvert.DeserializeObject<TokenModel>(tokenJSON);
Also, as you're using the Async methods, you should be returning Task from your actions and await the result instead of using .Result as you're just creating overhead currently.
var tokenJSON = await response.Content.ReadAsStringAsync();
var token = JsonConvert.DeserializeObject<TokenModel>(tokenJSON);

Is there any in-built function/method to return, IActionResult/ActionResult instead of HttpResponseMessage in .Net Core 3.1

My Action method is returning HttpResponseMessage but, I want to get rid off Microsoft.AspNetCore.Mvc.WebApiCompatShim NuGet Package (which is basically provided to bridge the gap while porting Asp.Net Web API code into .Net Core) and use IActionResult/ActionResult instead of HttpResponseMessage.
My Action method looks like this:
[HttpGet]
[Route("GetTemplate")]
public async Task<HttpResponseMessage> GetTemplate(string id) {
var userAgent = this.Request.Headers.UserAgent;
bool IsWindows = true;
if(userAgent.ToString().ToLower().Contains("apple")) {
IsWindows = false; //false
}
var template = await _templateService.GetTemplateContent(id);
HttpResponseMessage responseMsg = new HttpResponseMessage();
if(IsWindows) {
responseMsg.Content = new StringContent(JsonConvert.SerializeObject(template));
responseMsg.RequestMessage = Request;
responseMsg.StatusCode = HttpStatusCode.OK;
responseMsg.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/json");
} else {
responseMsg.Content = new ByteArrayContent(template.ContentBytes);
responseMsg.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileNameStar = template.Name };
responseMsg.Content.Headers.Add("x-filename", template.Name);
responseMsg.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
responseMsg.Content.Headers.ContentLength = template.ContentBytes.Length;
responseMsg.RequestMessage = Request;
responseMsg.StatusCode = HttpStatusCode.OK;
}
return (responseMsg);
}
Since you aren’t doing anything fancy there, you can translate your return object directly into corresponding action results here. In your case, you want a JsonResult and a FileResult with a custom response header:
[HttpGet]
[Route("GetTemplate")]
public async Task<HttpResponseMessage> GetTemplate(string id)
{
var userAgent = this.Request.Headers.UserAgent;
bool IsWindows = !userAgent.ToString().ToLower().Contains("apple");
var template = await _templateService.GetTemplateContent(id);
if (IsWindows)
{
return Json(template);
}
else
{
Response.Headers.Add("x-filename", template.Name);
return File(template.ContentBytes, "application/octet-stream", template.Name);
}
}
There are a lot similar utility methods on the Controller and ControllerBase type that help you create a variety of different response messages. For most use cases, there should be a built-in way to produce the response.
1stly change the signature of your action to this:
public async Task<IActionResult> GetTemplate
You will then return your data in the response something like this return Ok(data). You do not have to serialize your data, you can send a POCO class. This would represent .StatusCode = HttpStatusCode.OK
If you want to add extra headers to your response, you will do so using the Response field from ControllerBase. Eg. Response.Headers.Add for adding key value pairs to your Response header.

ASP.NET Core 3.1 - PostAsync/PostAsJsonAsync method in Integration Test always returns Bad Request

This is my register method inside the AuthController.
[HttpPost(ApiRoutes.Auth.Register)]
public async Task<IActionResult> Register(UserRegistrationRequest request)
{
var authResponse = await _authService.RegisterAsync(request.Email, request.Password);
if (!authResponse.Success)
{
return BadRequest(new AuthFailedResponse
{
Errors = authResponse.Errors
});
}
return Ok(new AuthSuccessResponse
{
Token = authResponse.Token,
RefreshToken = authResponse.RefreshToken
});
}
I'm trying to call this method by using TestClient.PostAsync() method, unfortunately it always returns Bad Request. I've already tried calling the TestClient.PostAsJsonAsync(ApiRoutes.Auth.Register, user) method by importing Microsoft.AspNet.WebApi.Client package, the result is the same.
var user = new UserRegistrationRequest
{
Email = "user1#testtest.com",
Password = "P#ssw0rd1!!!!!"
};
var response = await TestClient.PostAsync(
ApiRoutes.Auth.Register,
new StringContent(JsonConvert.SerializeObject(user), Encoding.UTF8)
{
Headers = { ContentType = new MediaTypeHeaderValue("application/json") }
});
You are missing the FromBody attribute from you action parameter. When you are sending json data to a controller that will be part of the request body. You can tell to the controller how to bind the incoming data, in your case from the body. So you code should look like:
public async Task<IActionResult> Register([FromBody]UserRegistrationRequest request)
{
…
}
You could read more about bindings in the official documentation.