How to use ReadAsStringAsync in asp.net core MVC controller? - asp.net-core

How to use ReadAsStringAsync in asp.net core MVC controller?
The Microsoft.AspNetCore.Mvc.Request does not have Content property. Is there an alternative to this? Thank you!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using AuthLibrary;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web;
using System.Web.Http;
using System.Threading.Tasks;
[Microsoft.AspNetCore.Mvc.Route("TestAPI")]
public class TestController : Controller
{
[Microsoft.AspNetCore.Mvc.HttpPost]
[AllowAnonymous]
[Microsoft.AspNetCore.Mvc.Route("Start")]
public async Task<HttpResponseMessage> Start()
{
string req = await this.Request.Content.ReadAsStringAsync();
////
}
}

For Asp.Net Core MVC, you could access the request content with request.Body.
Here is an extension:
public static class HttpRequestExtensions
{
/// <summary>
/// Retrieve the raw body as a string from the Request.Body stream
/// </summary>
/// <param name="request">Request instance to apply to</param>
/// <param name="encoding">Optional - Encoding, defaults to UTF8</param>
/// <returns></returns>
public static async Task<string> GetRawBodyStringAsync(this HttpRequest request, Encoding encoding = null)
{
if (encoding == null)
encoding = Encoding.UTF8;
using (StreamReader reader = new StreamReader(request.Body, encoding))
return await reader.ReadToEndAsync();
}
/// <summary>
/// Retrieves the raw body as a byte array from the Request.Body stream
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static async Task<byte[]> GetRawBodyBytesAsync(this HttpRequest request)
{
using (var ms = new MemoryStream(2048))
{
await request.Body.CopyToAsync(ms);
return ms.ToArray();
}
}
}
Use:
public async Task<string> ReadStringDataManual()
{
return await Request.GetRawBodyStringAsync();
}
Reference:Accepting Raw Request Body Content in ASP.NET Core API Controllers

You hope you can use .ReadAsStringAsync() on the current MVC request because perhaps you've seen something like this?
using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using System.Threading.Tasks;
namespace DL.SO.UI.Web.Controllers
{
public class DashboardController : Controller
{
// In order to be able to inject the factory, you need to register in Startup.cs
// services.AddHttpClient()
// .AddRouting(...)
// .AddMvc(...);
private readonly IHttpClientFactory _httpClientFactory;
public DashboardController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<IActionResult> Index()
{
var client = _httpClientFactory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://www.google.com");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
string bodyContent = await response.Content.ReadAsStringAsync();
}
return View();
}
}
}
This is how you use HttpClient to fetch external data/resources in an app. .ReadAsStringAsync() is off an HttpContent, which is the property of either HttpRequestMessage or HttpResponseMessage. Both HttpRequestMessage and HttpResponseMessage are in System.Net.Http namespace.
But now you're in the app itself! Things work a little bit differently. We don't have a response for the request yet (because we haven't done return View();). Hence I assume the content you want to look at is the content of the request coming in?
GET request's content
When a GET request comes in, MVC will automatically bind request's query strings to action method parameters in the controller. They're also available in the Query property off the current Request object:
public IActionResult Index(int page = 1, int size = 15)
{
foreach (var param in Request.Query)
{
...
}
return View();
}
POST request's content
When a POST request comes in, Request.Body might not always have the data you're looking for. It depends on the content type of the POST request.
By default when you're submitting a form, the content type of the request is form-data. MVC then will bind the inputs to your view model as the action parameter:
[HttpPost]
public async Task<IActionResult> Close(CloseReservationViewModel model)
{
Request.Form // contains all the inputs, name/value pairs
Request.Body // will be empty!
...
}
If you use jQuery to fire POST requests without specifying the contentType, it defaults to x-www-form-urlencoded:
#section scripts {
<script type="text/javascript">
$(function() {
$.ajax({
url: '#Url.Action("test", "dashboard", new { area = "" })',
data: {
name: 'David Liang',
location: 'Portland Oregon'
},
method: 'POST'
}).done(function (response) {
console.info(response);
});
});
</script>
}
[HttpPost]
public async Task<IActionResult> Test()
{
string body;
using (var reader = new StreamReader(Request.Body))
{
body = await reader.ReadToEndAsync();
}
return Json(body);
}
Conclusion
If you want to use HttpClient to call external services inside your MVC app, you can utilize IHttpClientFactory, HttpClient from System.Net.Http and get a HttpContent from either the request or response without too much trouble. Then you can do ReadAsStringAsync() off it.
If you want to peek on the request data sent from the client to your MVC app, MVC has already done so much to help you bind the data using model binding. You can also read request's body for POST requests with a StreamReader. Just pay attention that depends on the content type, Request.Body might not have what you expect.

Related

HttpClient.GetAsync return HttpResponseMessage with null header

net 5.0 lover.
I am new in blazor and .net 5.0, I develop the application with blazor WebAssembly and WebApi.
There are two major Projects: Client, Server.
Client is Blazor WebAssembly and Server is WebApi Controller Project.
In server side, in controller, HttpGet Method, i add a value to Response header:
[HttpGet]
public async Task<ActionResult<IList<Country>>> GetAsync([FromQuery] Pagination paginationDto)
{
/...
httpContext.Response.Headers.Add("TotalPages", totalPages.ToString());
//...
IList<Country> = ...
return result;
}
In Client project razor page, call the api with following method from generic calss:
protected virtual async Task<PaginatedResponse<O>> GetAsync<O>(Pagination pagination)
{
HttpResponseMessage response = null;
try
{
response = await httpClient.GetAsync(RequestUri);
if (response.IsSuccessStatusCode)
{
try
{
//This response Header always is null!
System.Console.WriteLine("response.Headers: " + response.Headers.ToString());
O result = await response.Content.ReadFromJsonAsync<O>();
var paginatedResponse = new PaginatedResponse<O>
{
Response = result,
TotalPages = totalPages
};
return paginatedResponse;
}
//...
return default;
}
When Api call from postman the result and Header is fine and TotalPages is there.
In Client App, the result is ok, but the Header is null.
Any information will save me ;-)
Thanks in Advance.
I think you're overcomplicating this by trying to use headers to pass back a result that can be passed more easily as part of the content. You even sort of realise this you're trying to use a PaginatedResponse in the Blazor client.
So instead of the API returning just a list, have a PaginatedResponse class in a shared library somewhere.. e.g.
/// <summary>
/// Paged result class
/// </summary>
/// <typeparam name="T"></typeparam>
public class PaginatedResponse<T>
{
public int TotalPages { get; set; }
public int Page { get; set; }
public List<T> Data { get; set; }
}
Your API then returns this
[HttpGet]
public async Task<ActionResult<PaginatedResponse<Country>>> GetAsync([FromQuery] Pagination paginationDto)
{
// ... query results here
var result = new PaginatedResponse<Country>()
{
Page = x,
TotalPages = totalPages,
Data = countrylist // from query
};
return result;
}
Your Blazor client can then use the same PaginatedResponse class and just use the standard GetFromJsonAsync method:
var result = await Http.GetFromJsonAsync<PaginatedResponse<Country>>("yourApiUri");
This is why I love Blazor!
This is the exactly answer for how search for answer:
in Server project, in startup.cs, in ConfigureServices method, add following code for CORS or update your CORS rule:
services.AddCors(options => options.AddPolicy(name: "WebApiProjectName or somthing", builder =>
{
builder.WithOrigins("http://localhost:xxxx") //xxxxx is server port
.AllowAnyMethod()
.AllowAnyHeader()
//.AllowCredentials() // its optional for this answer
.WithExposedHeaders("*"); // this is the code you need!
}));

415 Unsupported Media Type in ASP.NET core web api

I am trying to experiment with asp.net core web api so I made some simple api with a controller like this:
[ApiController]
[Route("MyController")]
public class MyController : ControllerBase
{
[HttpGet]
[Route("GetResult")]
public IActionResult GetResult(string param1, string param2= null, SomeClassObj obj = null)
{ .... }
}
I ran the api locally and sent this postman GET request:
https://localhost:5001/MyController/GetResult?param1=someString
I got the error: 415 Unsupported Media Type
What am I missing here so it could work?
I was getting the same error after invoking the WEB API from .NET MVC.
As suggested by #zhulien, I have changed from [FromBody] to [FromForm] in WebAPI, it works fine for me.
.NET Core WebAPI method.
public async Task<IActionResult> Login([FromForm] LoginModel loginInfo)
{ // JWT code here }
.Net Core MVC Action Method.
public async void InvokeLoginAPIAsync(string endPoint, string userName, string pwd)
{
configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
baseUrl = configuration["Application:BaseAPI"] ?? throw new Exception("Unable to get the configuration with key Application:BaseAPI");
string targetUrl = string.Format("{0}/{1}", baseUrl, endPoint);
using (HttpClient deviceClient = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Post, targetUrl);
var data = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("userName", userName),
new KeyValuePair<string, string>("password", pwd)
};
request.Content = new FormUrlEncodedContent(data);
using (var response = await deviceClient.SendAsync(request))
{
if (response.StatusCode == HttpStatusCode.OK)
{
TempData["Response"] = JsonConvert.SerializeObject(response.Content);
}
}
}
}
Which version of .NET Core are you using?
Try doing the request from the browser and see if you have the same result.
Also, are you sure you're doing a GET and not a POST request in Postman? You shouldn't get 415 errors for GET requests, especially when you're not sending any body.
This error mainly occurs when you try to send a body and you haven't specified the media-type through the Content-Type header.
Ensure that the request is GET and your body is empty.
Solution after post edit:
As you're trying to parse a DTO object(SomeClassObj), you should specify where the values should come from. In order to fix your specific case, add the [FromQuery] attribute before SomeClassObj.
Your code should look like this:
[ApiController]
[Route("MyController")]
public class MyController : ControllerBase
{
[HttpGet]
[Route("GetResult")]
public IActionResult GetResult(string param1, string param2= null, [FromQuery]SomeClassObj obj = null)
{ .... }
}
This tells the parser to fetch the data from the query string. This will fix the 415 issue. However, if you want to bind to complex types, especially on get, checkout those topics: ASP.NET CORE 3.1 Model Binding and this issue as you will most probably encounter issues with parsing your DTO object.
Use [FromForm] attribute before each argument in the controller function.

Return an image from asp.net web api core as IActionResult

What is the best way to return an image file as IActionResult while using asp.net web api core?
I tried returning a base64 string and it works fine. But not considered as efficient.
Is there a way using which we can return an image file object itself as IActionResult.
You can use the various overloads of the File() function in controllers that inherit from Controller or ControllerBase.
For example, you can do:
return File("~/Images/photo.jpg", "image/jpeg");
This uses a virtual path, other options include giving it a byte array or a Stream. You can also give a download file name as a third argument if that is needed.
[Route("getProductImage/v1")]
[HttpGet]
public async Task<IActionResult> getProductImage(GetProductImageQueryParam parammodel)
{
using (HttpClient client = new HttpClient())
{
MNimg_URL = MNimg_URL + parammodel.modelname;
HttpResponseMessage response = await client.GetAsync(MNimg_URL);
byte[] content = await response.Content.ReadAsByteArrayAsync();
//return "data:image/png;base64," + Convert.ToBase64String(content);
return File(content, "image/png", parammodel.modelname);
}
}
In .net core web api you can use the above code
here GetProductImageQueryParam is a class with input parameters
A File result is called FileContentResult in NET Core 3.x.
You can return image using return file with stream or bytes format or using its image path.
There are few overloaded methods for return File(//parameters); which you can use it in mvc controller's action method.
API Controller
[Route("api/[controller]")]
public class FileController : Controller {
//GET api/file/id
[HttpGet("{id}"]
public async Task<IActionResult> GetFile(string id) {
var stream = await {{//__get_stream_here__//}};
var response = File(stream, "application/octet-stream"); // FileStreamResult
return response;
}
}
or
var imageFileStream = System.IO.File.OpenRead("// image path");
return File(imageFileStream, "image/jpeg");
Hope this will help you.

ASP.NET Core Integration Test for controller action

Microsoft documentation (https://learn.microsoft.com/en-us/aspnet/core/testing/integration-testing) explain how to implement an integration test using the TestServer class. It is easy in case we are using WEB API because we get the serialized model as response from the action.
But in case I want to test a Controller action returning an HTML View containing some data, how can I evaluate that the page content is what I expect (avoiding to scan the HTML page contents) ?
One option is to use Automated UI Testing using something like Selenium
In order to append this JSON serialized view model to your page, I implemented the following filter:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Newtonsoft.Json;
using Ticketino.Web.Components.Extensions.Request;
using Ticketino.Web.OnlineShop.Serializations;
using Ticketino.Web.OnlineShop.ViewModels.Base;
namespace Ticketino.Web.OnlineShop.Filters
{
/// <summary>
/// This is a filter used only for integration tests.
/// It format the ViewModel as jSon and appends it to the end of HMTL page, so that it can be deserialized from the test in order to check its values.
/// </summary>
/// <seealso cref="Microsoft.AspNetCore.Mvc.Filters.ResultFilterAttribute" />
[AttributeUsage(AttributeTargets.Method)]
public class IntegrationTestFilterAttribute : ResultFilterAttribute
{
public const string StartViewModelContainer = "<script type=\"model/json\">";
public const string EndViewModelContainer = "</script>";
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
if (!filterContext.ModelState.IsValid)
{
var viewResult = filterContext.Result as ViewResult;
if (viewResult?.Model is BaseViewModel)
{
var errors = IntegrationTestFilterAttribute.GetModelErrors(filterContext.ModelState);
((BaseViewModel)viewResult.Model).ValidationErrors = errors;
}
}
base.OnResultExecuting(filterContext);
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (!filterContext.HttpContext.Request.IsAjaxRequest())
{
var viewResult = filterContext.Result as ViewResult;
if (viewResult?.Model != null)
{
var jsonViewModel = string.Concat(
IntegrationTestFilterAttribute.StartViewModelContainer,
JsonConvert.SerializeObject(viewResult.Model, Formatting.None, CommonJsonSerializerSettings.Settings()),
IntegrationTestFilterAttribute.EndViewModelContainer);
filterContext.HttpContext.Response.WriteAsync(jsonViewModel);
}
}
base.OnResultExecuted(filterContext);
}
#region Private methods
private static IDictionary<string, string> GetModelErrors(ModelStateDictionary errDictionary)
{
var errors = new Dictionary<string, string>();
//get all entries from the ModelStateDictionary that have any errors and add them to our Dictionary
errDictionary.Where(k => k.Value.Errors.Count > 0).ForEach(i =>
{
foreach (var errorMessage in i.Value.Errors.Select(e => e.ErrorMessage))
{
errors.Add(i.Key, errorMessage);
}
});
return errors;
}
#endregion
}
}
Then, in ConfigureServices(IServiceCollection serviceCollection) method inject it when you run integration test as show:
// Filter to append json serialized view model to buttom html response page, in order to eveluate from integration test class
if (_hostingEnvironment.IsIntegrationTest())
{
mvcBuilder.AddMvcOptions(opt => { opt.Filters.Add(new IntegrationTestFilterAttribute()); });
}

Sniff request in ActionFilter

One parameter in a Web API method is unexpectedly null, so I want to inspect the request. In support of this I wrote an ActionFilterAttribute and implemented the OnActionExecuting method. Attempting to retrieve Content as per the code below returns an empty string, but ContentLength says content is 345 bytes and content type is JSON (as expected).
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace Website.ActionFilters
{
public class ActionFilterSniffAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
Task<string> task = actionContext.Request.Content.ReadAsStringAsync();
while (task.Status != TaskStatus.RanToCompletion)
Thread.Sleep(10);
Debug.WriteLine(task.Result);
}
}
}
What is the correct way to get hold of the HTTP request string? Installing Fiddler on the server is not something I'm keen to do.
This mechanism worked for me and is a good explanation of what is occurring.
Web API action filter content can't be read
public override async void OnActionExecuting(HttpActionContext actionContext)
{
System.Net.Http.HttpRequestMessage request = actionContext.Request;
Stream reqStream = await request.Content.ReadAsStreamAsync();
if (reqStream.CanSeek)
{
reqStream.Position = 0;
}
//now try to read the content as string
string data = await request.Content.ReadAsStringAsync();
Debugger.Break();
}