How to capture response after calling REST API? - asp.net

I am using RestSharp to call REST API as
public async Task<IActionResult> getoutput()
{
var client = new RestClient("....");
var request = new RestRequest(".....");
var response = await client.ExecuteAsync(request);
return Ok(response.Content);
}
My response is like
{
"number" : 567889
}
I want to get that number 567889 and save it into variable in my controller. But I am not able to do so.
I tried with
var answer = response.Content;
But it is showing the JSON.
Can somebody please help me out?

you can create a class for your data
public class Answer
{
public int number {get;set;}
}
and action
var response = await client.ExecuteAsync(request);
Answer answer= JsonConvert.DeserializeObject<Answer>(response.Content);
int number=answer.number;
return Ok(answer);
or if for some reason you don't want to create a class, you can parse a json string
var response = await client.ExecuteAsync(request);
int _number= (int) JObject.Parse(response.Content)["number"]
return Ok( new { number= _number} );

Related

Web API is not able to serialize the DateTime sent as "LOGINDATE": "2020-04-05T01:00:21.45+04:00". it only accepts Date(milliseconds)

Maybe I am confused or due to working from home I am not able to think.
I have Web API 2.0 project which is receiving models in end points. I have android client which is sending Date in milliseconds format and for same end point i have MVC client which is sending date in "LOGINDATE": "2020-04-05T01:00:21.45+04:00". the date from MVC is not accepted by API and gives an error on model state like
System.FormatException: DateTime content
'2020-04-05T10:52:42.333+04:00' does not start with '/Date(' and end
with ')/' as required for JSON.
I have forced the WebApi to use the newtonsoft json formatter but when i debug the exception i found
System.Runtime.Serialization.Json.JsonReaderDelegator.ParseJsonDateInDefaultFormat(String originalDateTimeValue)
Following is the WEBAPICONFIG code for forcing use the newtonsoft mediatype.
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
jsonFormatter.UseDataContractJsonSerializer = true;
JsonSerializerSettings CustomJsonSetting = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
DateTimeZoneHandling = DateTimeZoneHandling.Local,
DateFormatHandling = DateFormatHandling.IsoDateFormat,
//DateFormatString = "yyyy-mm-dd HH:MM:ss"
};
CustomJsonSetting.Converters.Clear();
CustomJsonSetting.Converters.Add(new IsoDateTimeConverter());
JsonConvert.DefaultSettings = () => CustomJsonSetting;
jsonFormatter.SerializerSettings = CustomJsonSetting;
config.Formatters.Clear();
config.Formatters.Insert(0,jsonFormatter);
Then from MVC client i am compressing GZIP and sending the call like following:
var frmt = UniversalFormatters.GetJsonFormatter();
if (IsCompressionEnabled)
{
var json = JsonConvert.SerializeObject(request, frmt.SerializerSettings);
//new JavaScriptSerializer().Serialize(request);
//
client.DefaultRequestHeaders.Add("pHubCompression", "1");
var content = new CompressedContent(
new StringContent(json, Encoding.UTF8, "application/json"),
CompressionMethod.GZip);
return await client.PostAsync(EndPoint, content);
}
else
return await client.PostAsync(EndPoint, request, frmt);
And Universal Formatter is defined as
public static JsonMediaTypeFormatter GetJsonFormatter()
{
if (JsonFormatter==null)
{
JsonFormatter = new JsonMediaTypeFormatter();
JsonFormatter.UseDataContractJsonSerializer = true;
JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
JsonFormatter.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
JsonFormatter.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
JsonFormatter.SerializerSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
JsonFormatter.SerializerSettings.DateFormatString = "yyyy-MM-ddTHH:mm:ss";
}
return JsonFormatter;
}
I have done a bit R&D and people mentioned to create my own date formater which is a work around but i wanted to know if there is something wrong with my setting.
My Model is defined like following:
[System.Runtime.Serialization.DataContractAttribute(Namespace = "http://MyProj.Core.Entities", Name = "RequestObject{0}")]
public class RequestObject<T> : IDisposable//, IEntity
//where T : IEntity, IEntityCollection, ICompositeEntity
{
private UserInfo m_userInfo;
private T m_obj;}
and UserInfo is defined like
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Namespace = "http://MyProj.Business.Contracts", Name = "UserInfo")]
public partial class UserInfo : BaseEntity, IEntity, IDisposable
{
[System.Runtime.Serialization.DataMember(EmitDefaultValue = true)]
public DateTime? LOGINDATE { set; get; }}
Sorry for my bad English.
according to above comment by #dbc I removed JsonFormatter.UseDataContractJsonSerializer = true; this line from my startups and everything starts working. Thanks for the help.

Web API translate JSON object into simple parameters

If I am sending JSON data (via POST) to a .Net Core Web API like this
{ a: "a", b: "b" }
What do I need to do to have a controller method like this?
[HttpPost]
public async Task SometMethod(string a, string b)
{
return Ok();
}
Normally, all tutorials and docs say that you need to define a class and use [FromBody] attribute. But how can I make do without extra classes that I don't really need?
Firstly,your json should be:
{
"a":"a",
"b":"b"
}
You could receive data as JObject instead of a class like below:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[HttpPost]
public void Post(JObject data)
{
//get the property value like below
var data1 = data["a"].ToString();
var data2 = data["b"].ToString();
}
}
Result (For easily distinguish value and property name,I change a to aaa and b to bbb):
If you want to post the data to the method like this, you will have to serialize your data before you can send it to the server. Assuming you are using JQuery, you can do like the following.
var postData = $.param({ a: "a", b: "b" });
//Then you can send this postData obejct to the server. This should perfectly bound to the parameters.
You can also use the same in an angular app.
After some research I came up with ModelBinder to do just this. It is not performant since it re-parses the whole request body for every parameter. I will improve it in the future.
https://github.com/egorpavlikhin/JsonParametersModelBinder
public class JsonBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
var actionDescriptor = bindingContext.ActionContext.ActionDescriptor as ControllerActionDescriptor;
if (actionDescriptor.MethodInfo.GetCustomAttributes(typeof(JsonParametersAttribute), false).Length > 0)
{
var context = bindingContext.HttpContext;
if (context.Request.ContentType != "application/json")
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
#if (NETSTANDARD2_1 || NETCOREAPP3_0)
context?.Request.EnableBuffering();
#else
context?.Request.EnableRewind();
#endif
using var reader = new StreamReader(context.Request.Body, Encoding.UTF8,
false,
1024,
true); // so body can be re-read next time
var body = await reader.ReadToEndAsync();
var json = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(body);
if (json.TryGetValue(bindingContext.FieldName, out var value))
{
if (bindingContext.ModelType == typeof(string))
{
bindingContext.Result = ModelBindingResult.Success(value.GetString());
}
else if (bindingContext.ModelType == typeof(object))
{
var serializerOptions = new JsonSerializerOptions
{
Converters = {new DynamicJsonConverter()}
};
var val = JsonSerializer.Deserialize<dynamic>(value.ToString(), serializerOptions);
bindingContext.Result = ModelBindingResult.Success(val);
}
}
context.Request.Body.Position = 0; // rewind
}
}
}

Read Asp.Net Core Response body in ActionFilterAttribute

I'm using Asp.Net Core as a Rest Api Service.
I need access to request and response in ActionFilter. Actually, I found the request in OnActionExcecuted but I can't read the response result.
I'm trying to return value as follow:
[HttpGet]
[ProducesResponseType(typeof(ResponseType), (int)HttpStatusCode.OK)]
[Route("[action]")]
public async Task<IActionResult> Get(CancellationToken cancellationToken)
{
var model = await _responseServices.Get(cancellationToken);
return Ok(model);
}
And in ActionFilter OnExcecuted method as follow:
_request = context.HttpContext.Request.ReadAsString().Result;
_response = context.HttpContext.Response.ReadAsString().Result; //?
I'm trying to get the response in ReadAsString as an Extension method as follow:
public static async Task<string> ReadAsString(this HttpResponse response)
{
var initialBody = response.Body;
var buffer = new byte[Convert.ToInt32(response.ContentLength)];
await response.Body.ReadAsync(buffer, 0, buffer.Length);
var body = Encoding.UTF8.GetString(buffer);
response.Body = initialBody;
return body;
}
But, there is no result!
How I can get the response in OnActionExcecuted?
Thanks, everyone for taking the time to try and help explain
If you're logging for json result/ view result , you don't need to read the whole response stream. Simply serialize the context.Result:
public class MyFilterAttribute : ActionFilterAttribute
{
private ILogger<MyFilterAttribute> logger;
public MyFilterAttribute(ILogger<MyFilterAttribute> logger){
this.logger = logger;
}
public override void OnActionExecuted(ActionExecutedContext context)
{
var result = context.Result;
if (result is JsonResult json)
{
var x = json.Value;
var status = json.StatusCode;
this.logger.LogInformation(JsonConvert.SerializeObject(x));
}
if(result is ViewResult view){
// I think it's better to log ViewData instead of the finally rendered template string
var status = view.StatusCode;
var x = view.ViewData;
var name = view.ViewName;
this.logger.LogInformation(JsonConvert.SerializeObject(x));
}
else{
this.logger.LogInformation("...");
}
}
I know there is already an answer but I want to also add that the problem is the MVC pipeline has not populated the Response.Body when running an ActionFilter so you cannot access it. The Response.Body is populated by the MVC middleware.
If you want to read Response.Body then you need to create your own custom middleware to intercept the call when the Response object has been populated. There are numerous websites that can show you how to do this. One example is here.
As discussed in the other answer, if you want to do it in an ActionFilter you can use the context.Result to access the information.
For logging whole request and response in the ASP.NET Core filter pipeline you can use Result filter attribute
public class LogRequestResponseAttribute : TypeFilterAttribute
{
public LogRequestResponseAttribute() : base(typeof(LogRequestResponseImplementation)) { }
private class LogRequestResponseImplementation : IAsyncResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
var requestHeadersText = CommonLoggingTools.SerializeHeaders(context.HttpContext.Request.Headers);
Log.Information("requestHeaders: " + requestHeadersText);
var requestBodyText = await CommonLoggingTools.FormatRequestBody(context.HttpContext.Request);
Log.Information("requestBody: " + requestBodyText);
await next();
var responseHeadersText = CommonLoggingTools.SerializeHeaders(context.HttpContext.Response.Headers);
Log.Information("responseHeaders: " + responseHeadersText);
var responseBodyText = await CommonLoggingTools.FormatResponseBody(context.HttpContext.Response);
Log.Information("responseBody: " + responseBodyText);
}
}
}
In Startup.cs add
app.UseMiddleware<ResponseRewindMiddleware>();
services.AddScoped<LogRequestResponseAttribute>();
Somewhere add static class
public static class CommonLoggingTools
{
public static async Task<string> FormatRequestBody(HttpRequest request)
{
//This line allows us to set the reader for the request back at the beginning of its stream.
request.EnableRewind();
//We now need to read the request stream. First, we create a new byte[] with the same length as the request stream...
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
//...Then we copy the entire request stream into the new buffer.
await request.Body.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
//We convert the byte[] into a string using UTF8 encoding...
var bodyAsText = Encoding.UTF8.GetString(buffer);
//..and finally, assign the read body back to the request body, which is allowed because of EnableRewind()
request.Body.Position = 0;
return $"{request.Scheme} {request.Host}{request.Path} {request.QueryString} {bodyAsText}";
}
public static async Task<string> FormatResponseBody(HttpResponse response)
{
//We need to read the response stream from the beginning...
response.Body.Seek(0, SeekOrigin.Begin);
//...and copy it into a string
string text = await new StreamReader(response.Body).ReadToEndAsync();
//We need to reset the reader for the response so that the client can read it.
response.Body.Seek(0, SeekOrigin.Begin);
response.Body.Position = 0;
//Return the string for the response, including the status code (e.g. 200, 404, 401, etc.)
return $"{response.StatusCode}: {text}";
}
public static string SerializeHeaders(IHeaderDictionary headers)
{
var dict = new Dictionary<string, string>();
foreach (var item in headers.ToList())
{
//if (item.Value != null)
//{
var header = string.Empty;
foreach (var value in item.Value)
{
header += value + " ";
}
// Trim the trailing space and add item to the dictionary
header = header.TrimEnd(" ".ToCharArray());
dict.Add(item.Key, header);
//}
}
return JsonConvert.SerializeObject(dict, Formatting.Indented);
}
}
public class ResponseRewindMiddleware {
private readonly RequestDelegate next;
public ResponseRewindMiddleware(RequestDelegate next) {
this.next = next;
}
public async Task Invoke(HttpContext context) {
Stream originalBody = context.Response.Body;
try {
using (var memStream = new MemoryStream()) {
context.Response.Body = memStream;
await next(context);
//memStream.Position = 0;
//string responseBody = new StreamReader(memStream).ReadToEnd();
memStream.Position = 0;
await memStream.CopyToAsync(originalBody);
}
} finally {
context.Response.Body = originalBody;
}
}
You can also do...
string response = "Hello";
if (result is ObjectResult objectResult)
{
var status = objectResult.StatusCode;
var value = objectResult.Value;
var stringResult = objectResult.ToString();
responce = (JsonConvert.SerializeObject(value));
}
I used this in a .net core app.
Hope it helps.

Get value from IActionResult

I have Controller whit some endpoints Task<IActionResult> MyCustomEndpoint which is returning return Ok(CustomDataType). Returned datas are in JSON fromat.
Now I want to call that Endpoint from other Controller like var resp = myController.MyCustomEndpoint, where resp becomes IActionResult. The problem is that resp now doesn't return only datas anymore, but all of those fields as seen on image.
My question is, how to access and return only Value field, because resp.Value is not working.
Thanks for help.
You have to convert to as ObjectResult
public async Task GetFormSummary_ItShouldReturnSingleForm_NotNull()
{
var formId = new Guid("5EF685E7-1167-4226-7F5E-08D9009544A3");
var mockFormService = new Mock<IFormService>();
mockFormService.Setup(x => x.GetAsync(formId).Result)
.Returns(GetTestForm());
// Inject
var formController = new FormController(_logger, mockFormService.Object);
// Act
var result = (await formController.GetAsync(formId)) as ObjectResult;
// Assert
Assert.IsAssignableFrom<FormViewModel>(result.Value);
}

Response on created context keeps giving me NullStream

I'm trying to write a middleware for batch requests i .net core 2.0.
So far the I have splitted the request, pipe each request on to the controllers.
The controllers return value, but for some reason the response on the created context that I parse to the controllers keeps giving me a NullStream in the body, so I think that there is something that I miss in my setup.
The code looks like this:
var json = await streamHelper.StreamToJson(context.Request.Body);
var requests = JsonConvert.DeserializeObject<IEnumerable<RequestModel>>(json);
var responseBody = new List<ResponseModel>();
foreach (var request in requests)
{
var newRequest = new HttpRequestFeature
{
Body = request.Body != null ? new MemoryStream(Encoding.ASCII.GetBytes(request.Body)) : null,
Headers = context.Request.Headers,
Method = request.Method,
Path = request.RelativeUrl,
PathBase = string.Empty,
Protocol = context.Request.Protocol,
Scheme = context.Request.Scheme,
QueryString = context.Request.QueryString.Value
};
var newRespone = new HttpResponseFeature();
var requestLifetimeFeature = new HttpRequestLifetimeFeature();
var features = CreateDefaultFeatures(context.Features);
features.Set<IHttpRequestFeature>(newRequest);
features.Set<IHttpResponseFeature>(newRespone);
features.Set<IHttpRequestLifetimeFeature>(requestLifetimeFeature);
var innerContext = _factory.Create(features);
await _next(innerContext);
var responseJson = await streamHelper.StreamToJson(innerContext.Response.Body);
I'm not sure what it is I'm missing in the setup, since innerContext.Response.Body isn't set.
One of the endpoints that I use for testing and that gets hit looks like this
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
I found the error, or two errors for it to work.
First I had to change my newResponse to
var newRespone = new HttpResponseFeature{ Body = new MemoryStream() };
Since HttpResponseFeature sets Body to Stream.Null in the constructor.
When that was done, then Body kept giving an empty string when trying to read it. That was fixed by setting the Position to Zero like
innerContext.Response.Body.Position = 0;

Resources