Where is the PostAsJsonAsync method in ASP.NET Core? - c#

I was looking for the PostAsJsonAsync() extension method in ASP.NET Core. Based on this article, it's available in the Microsoft.AspNet.WebApi.Client assembly.
I had thought Microsoft had changed all of the assembly names from Microsoft.AspNet to Microsoft.AspNetCore to be more specific to .NET Core, however, and yet I cannot find an Microsoft.AspNetCore.WebApi.Client assembly.
Where is the PostAsJsonAsync() extension method in ASP.NET Core?

It comes as part of the library Microsoft.AspNet.WebApi.Client
https://www.nuget.org/packages/Microsoft.AspNet.WebApi.Client/

I dont deserve any credit for this. Have a look #danroth27 answer in the following link.
https://github.com/aspnet/Docs/blob/master/aspnetcore/mvc/controllers/testing/sample/TestingControllersSample/tests/TestingControllersSample.Tests/IntegrationTests/HttpClientExtensions.cs
He uses an extension method. Code as below. (Copied from the above github link). I am using it on .Net Core 2.0.
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace TestingControllersSample.Tests.IntegrationTests
{
public static class HttpClientExtensions
{
public static Task<HttpResponseMessage> PostAsJsonAsync<T>(
this HttpClient httpClient, string url, T data)
{
var dataAsString = JsonConvert.SerializeObject(data);
var content = new StringContent(dataAsString);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return httpClient.PostAsync(url, content);
}
public static async Task<T> ReadAsJsonAsync<T>(this HttpContent content)
{
var dataAsString = await content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(dataAsString);
}
}
}

As of .NET 5.0, this has been (re)introduced as an extension method off of HttpClient, via the System.Net.Http.Json namespace. See the HttpClientJsonExtensions class for details.
Demonstration
It works something like the following:
var httpClient = new HttpClient();
var url = "https://StackOverflow.com"
var data = new MyDto();
var source = new CancellationTokenSource();
var response = await httpClient.PostAsJsonAsync<MyDto>(url, data, source.Token);
And, of course, you'll need to reference some namespaces:
using System.Net.Http; //HttpClient, HttpResponseMessage
using System.Net.Http.Json; //HttpClientJsonExtensions
using System.Threading; //CancellationToken
using System.Threading.Tasks; //Task
Alternatively, if you're using the .NET 6 SDK's implicit using directives, three of these will be included for you, so you'll just need:
using System.Net.Http.Json; //HttpClientJsonExtensions
Background
This is based on the design document, which was previously referenced by #erandac—though the design has since changed, particularly for the PostAsJsonAsync() method.
Obviously, this doesn't solve the problem for anyone still using .NET Core, but with .NET 5.0 released, this is now the best option.

That is not part of the ASP.NET Core project. However you can proceed with:
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "http://myurl/api");
string json = JsonConvert.SerializeObject(myObj);
request.Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
HttpClient http = new HttpClient();
HttpResponseMessage response = await http.SendAsync(request);
if (response.IsSuccessStatusCode)
{
}
else
{
}

You Can Use This Extension for use PostAsJsonAsync method in ASP.NET core
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Newtonsoft.Json;
public static class HttpClientExtensions
{
public static Task<HttpResponseMessage> PostAsJsonAsync<T>(this HttpClient httpClient, string url, T data)
{
var dataAsString = JsonConvert.SerializeObject(data);
var content = new StringContent(dataAsString);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return httpClient.PostAsync(url, content);
}
public static Task<HttpResponseMessage> PutAsJsonAsync<T>(this HttpClient httpClient, string url, T data)
{
var dataAsString = JsonConvert.SerializeObject(data);
var content = new StringContent(dataAsString);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return httpClient.PutAsync(url, content);
}
public static async Task<T> ReadAsJsonAsync<T>(this HttpContent content)
{
var dataAsString = await content.ReadAsStringAsync().ConfigureAwait(false);
return JsonConvert.DeserializeObject<T>(dataAsString);
}
}
see: httpclient-extensions

this is coming late but I think it may help someone down the line. So the *AsJsonAsync() methods are not part of the ASP.NET Core project. I created a package that gives you the functionality. You can get it on Nuget.
https://www.nuget.org/packages/AspNetCore.Http.Extensions
using AspNetCore.Http.Extensions;
...
HttpClient client = new HttpClient();
client.PostAsJsonAsync('url', payload);

You need to add Nuget package System.Net.Http.Formatting.Extension to your project.
Or you can use
client.PostAsync(uri, new StringContent(data, Encoding.UTF8, "application/json"));

To follow on from the answers above, I have a small addition that was required for me to get it to work.
Previously I was using a .NET Core 2.1 web app using the PostAsJsonAsync() method, and when I upgraded to .NET Core 3.1 it no longer worked.
I could not get the above answers to work, and it turned out to be because the text to be posted had to be surrounded by quotes, and any quotes within it had to be escaped. I made the following extension method, which solved my problem:
public static async Task<HttpResponseMessage> PostJsonAsync(this HttpClient client, string uri, string json)
{
//For some reason, not doing this will cause it to fail:
json = $"\"{json.Replace("\"", "\\\"")}\"";
return await client.PostAsync(uri, new StringContent(json, Encoding.UTF8, "application/json"));
}
Note that I am using the System.Text.Json.JsonSerializer as opposed to the Newtonsoft version.

The methodPostAsJsonAsync (along with other *Async methods of the
HttpClient class) is indeed available out of the box – without using
directives.
Your .csproj file should start with
<Project Sdk="Microsoft.NET.Sdk.Web">, and contain the lines
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
I have given a more elaborated answer to a similar question.
(The PackageReference is no longer needed in .NET Core 3.0.)

I used this in a standard 2.0 library
System.Net.Http.Json

make the extension method truly async:
public static async Task<HttpResponseMessage> PostAsJsonAsync<T>(
this HttpClient httpClient, string url, T data)
{
var dataAsString = JsonConvert.SerializeObject(data);
var content = new StringContent(dataAsString);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return await httpClient.PostAsync(url, content);
}

If you are trying to use PostJsonAsync, PutJsonAsync or any other json extension methods in Blazor you need to add a following statement
using Microsoft.AspNetCore.Components;

Dotnet core 3.x runtime itself going to have set of extension methods for HttpClient which uses System.Text.Json Serializer
https://github.com/dotnet/designs/blob/main/accepted/2020/json-http-extensions/json-http-extensions.md

If you are in 2021 and having .Net Core 3.1, make sure in your project file csproj, Microsoft.AspNetCore.App is upto date, the latest is 2.2.8. You can check and update the package as below:
<ItemGroup>
...
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.8" />
...
</ItemGroup>
then restore your project from cli like this:
dotnet restore

using Microsoft.AspNetCore.Blazor.HttpClient

Related

Content-Type negation doesn't work upgrading from netcoreapp3.1 to net6 ASP.NET Core

I'm (trying) to upgrade ASP.NET Core application from .NET Core App 3.1 to .NET 6 but one test fails that deserialize a Problem result. Reason for failing is that in .NET 6 the content type is application/problem+json whilst in .NET Core App 3.1 application/xml.
Have searched for any notes regarding this in migration document but can't find anything.
A repro is available in my GitHub and the controller is very simple
using System.Net.Mime;
using Microsoft.AspNetCore.Mvc;
namespace ProblemDetailsXMLSerialization
{
[ApiController]
[Route("[controller]")]
public class XmlController : ControllerBase
{
[HttpPost]
[Produces(MediaTypeNames.Application.Xml)]
[Consumes(MediaTypeNames.Application.Xml)]
public IActionResult Xml()
{
return Problem();
}
}
}
// Test file
using Microsoft.AspNetCore.Mvc.Testing;
using ProblemDetailsXMLSerialization;
using System.Net.Http;
using System.Net.Mime;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace TestProject1
{
public class UnitTest1
{
[Fact]
public async Task Test1()
{
// Arrange
var application = new WebApplicationFactory<Startup>();
var client = application.CreateClient();
// Act
const string xml = #"<?xml version=""1.0"" encoding=""UTF-8""?>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>";
var content = new StringContent(xml, Encoding.UTF8, MediaTypeNames.Application.Xml);
var response = await client.PostAsync("xml", content);
// Assert
Assert.Equal(MediaTypeNames.Application.Xml, response.Content.Headers.ContentType.MediaType);
var responseString = await response.Content.ReadAsStringAsync();
}
}
}
Thanks
To get an XML response - matching your assert statement - you'll need to add an Accept HTTP header with value application/xml.
From the documentation:
Content negotiation occurs when the client specifies an Accept header. The default format used by ASP.NET Core is JSON.
var content = new StringContent(xml, Encoding.UTF8, MediaTypeNames.Application.Xml);
client.DefaultRequestHeaders.Add(
"Accept", "application/xml"
);
var response = await client.PostAsync("xml", content);
There are built-in strings for both Accept and application/xml.
client.DefaultRequestHeaders.Add(
Microsoft.Net.Http.Headers.HeaderNames.Accept,
System.Net.Mime.MediaTypeNames.Application.Xml
);
Setting that header to the DefaultRequestHeaders makes it being sent with every request made by that HttpClient instance.
In case you only want/need it for a single request, then use a HttpRequestMessage instance.
using (var request = new HttpRequestMessage(HttpMethod.Post, "xml"))
{
request.Headers.Add("accept", "application/xml");
request.Content = new StringContent(xml, Encoding.UTF8, MediaTypeNames.Application.Xml);
var response = await client.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
}
In either case, the responseString variable will contain an xml payload similar to below one.
<problem xmlns="urn:ietf:rfc:7807">
<status>500</status>
<title>An error occurred while processing your request.</title>
<type>https://tools.ietf.org/html/rfc7231#section-6.6.1</type>
<traceId>00-26c29d0830bd0a5a417e9bab9746bd23-3cfbc9589ffd8182-00</traceId>
</problem>
TLDR
To fix this change the order that the XmlOutput formatter is registered. Set it to first position. After services.AddControllers().AddXmlSerializerFormatters() in the startup set the XmlFormatter in first position. Haven't tried this code out but something like this should work:
services.AddControllers().AddXmlSerializerFormatters();
services.Configure<MvcOptions>(options => {
var xmlFormatterIdx = options.OutputFormatters.length - 1;
options.OutputFormatters.Insert(0,
options.OutputFormatters[xmlFormatterIdx]);
options.OutputFormatters.RemoveAt(xmlFormatterIdx + 1);
});
Details
When using the "Produces" attribute then this should be the response type. In this case it seems to be a problem in how dotnet handles object results that contain a ProblemDetail response. So this is what I've seen happens when decompiling and checking the source code:
The request starts and since we have the Produces attribute with application/xml then the content types for the result is application.xml only.
When the return type of the ObjectResult is ProblemDetails is detected 2 new content types are appended to the end of the content types list:
https://github.com/dotnet/dotnet/blob/b8bc661a7429baa89070f3bee636b7fbc1309489/src/aspnetcore/src/Mvc/Mvc.Core/src/Infrastructure/ObjectResultExecutor.cs#L152
Now we have 3 possible content types for the response. The output format selector now picks the wrong formatter:
https://github.com/dotnet/dotnet/blob/b8bc661a7429baa89070f3bee636b7fbc1309489/src/aspnetcore/src/Mvc/Mvc.Core/src/Infrastructure/DefaultOutputFormatterSelector.cs#L192
Conclusion
The produces attribute should override it all but it doesn't. The current implementation in dotnet gives the order of how output formatters are registered a higher priority.
Could be that the DefaultOutputformatter implementation should be done differently and use the content types of the object result as order of response types rather than how the output handlers are registered. Not sure on what the side effects could be but this could potentially a something for the dotnet team to look into

Targeting net461, HttpClient throws ex when GET request has content, but works with netstandard2.0

I need to use 3rd party web api.
On this specific endpoint I need to make GET request with content body (json).
var jsonPayload = JsonConvert.SerializeObject(myObject);
var request = new HttpRequestMessage(HttpMethod.Get, endpoint)
{
Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
};
var client = new HttpClient();
var response = await client.SendAsync(request); //<-- explodes with net461,
//but works with netstandard2.0
var responseContent = await response.Content.ReadAsStringAsync();
I targeted that code to netstandard2.0 and it worked.
However I now need to use in project where I target net461 and it throws exception saying "Cannot send a content-body with this verb-type"
I understand that it is not usual to set content to GET request, but that api is out of my reach.
How come HttpClient.Send(request) failed when I target net461, but works well when I target netstandard2.0?
What options do I have when I target net461?
Update
A way to reproduce.
ConsoleApp.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework> <!-- toggle this to net461 -->
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Net.Http" />
</ItemGroup>
</Project>
Program.cs
using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Text;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
var jsonPayload = JsonConvert.SerializeObject(new { });
var request = new HttpRequestMessage(HttpMethod.Get, "http://www.stackoverflow.com")
{
Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json")
};
var client = new HttpClient();
var response = client.SendAsync(request).Result;
var responseContent = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(responseContent);
}
}
}
dotnet --version shows 2.1.104
You didn't mention on which .net version you run it when it works (because you can target net standard but you cannot run on net standard). So I assume that when it works you run it on .NET Core, and when it does not you run on full .NET 4.6.1 as you said.
Then it's just because it was implemented differently in .NET Core and full .NET Framework, as described in this issue. There is nothing wrong with either implementation, because RFC says:
A payload within a GET request message has no defined semantics;
sending a payload body on a GET request might cause some existing
implementations to reject the request.
So, having body in GET (while almost never a good practice) does not by itself contradict that RFC. One implementation (older) was designed to reject it, but another (newer) was designed to allow it, there is nothing more to that.
As for your second question "What options do I have when I target net461". I don't think there are easy workarounds, because that's the behavior of HttpWebRequest, not specifically of HttpClient. And almost anything that makes web requests uses HttpWebRequest internally.
There are ugly hacks using reflections though. Your example can be modified like this (works only when running on full .NET):
static void Main(string[] args) {
// UGLY HACK!
var verbType = typeof(HttpWebRequest).Assembly.GetType("System.Net.KnownHttpVerb");
var getMethodField = verbType.GetField("Get", BindingFlags.Static | BindingFlags.NonPublic);
var getMethod = getMethodField.GetValue(null);
verbType.GetField("ContentBodyNotAllowed", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(getMethod, false);
var jsonPayload = JsonConvert.SerializeObject(new { });
var request = new HttpRequestMessage(HttpMethod.Get, "http://www.stackoverflow.com")
{
Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json")
};
var client = new HttpClient();
var response = client.SendAsync(request).Result;
var responseContent = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(responseContent);
}
Basically we use reflection to modify internal field named ContentBodyNotAllowed of KnownHttpVerb.Get to false. Use at your own risk.

Alternative to ReadAsAsync method in HttpContent

I am using Microsoft.AspNet.WebApi.Client to consume rest services in my ASP.MVC 5 project. I am following the this tutorial to make use of HttpClient. The code is not compiling since ReadAsAsync method is no longer available in HttpContent. After digging a bit I came to know that it is an extension method defined in System.Net.Http.Formatting.dll. I found a nuget package for the same dll here but the package is deprecated and I am not able to install it. I also trid to search that dll in Program Files folder according to this post but I could not get it. Any ideas how to make ReadAsAsync work? Any help greatly appreiciated. Thanks.
What do you need to do is to add new reference System.Net.HttpClient; and System.Net.HttpClient.Formating;.
This is my sample codes in HttpClient:
The following codes is use to get a certificate from saba using HttpClient.
using System.Net.Http;
using System.Net.Http.Headers;
using GoSaba.Models.Saba;
namespace GoSaba.Controllers.Saba
{
class LoginController
{
//HTTP GET: Saba/api/login
public async Task<string> GetCertificate(string host, string user, string password, string site)
{
StringBuilder getCertificate = new StringBuilder();
if(!string.IsNullOrEmpty(host))
{
using(var httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri(string.Format("http://{0}/", host));
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpClient.DefaultRequestHeaders.Add("user", user);
httpClient.DefaultRequestHeaders.Add("password", password);
httpClient.DefaultRequestHeaders.Add("site", site);
HttpResponseMessage httpResponse = await httpClient.GetAsync("Saba/api/login");
if(httpResponse.IsSuccessStatusCode)
{
LoginModel.GetCertificate saba = await httpResponse.Content.ReadAsAsync<LoginModel.GetCertificate>();//LoginModel.GetCertificate is model.
getCertificate.Append(saba.certificate);
}
}
}
return getCertificate.ToString();
}
}
}
You can use this a reference in how to use a HttpClient.
Here is an alternate way using the same Microsoft.AspNet.WebApi.Client. The number of lines of code increase but you wont find any issue like Newtonsoft version issue, which prompted me to look at alternatives for ReadAsAsync.
Here is a link that explains the code - https://www.newtonsoft.com/json/help/html/Performance.htm
HttpClient client = new HttpClient();
using (Stream s = client.GetStreamAsync("http://www.test.com/large.json").Result)
using (StreamReader sr = new StreamReader(s))
using (JsonReader reader = new JsonTextReader(sr))
{
JsonSerializer serializer = new JsonSerializer();
// read the json from a stream
// json size doesn't matter because only a small piece is read at a time from the HTTP request
T p = serializer.Deserialize<T>(reader); // Where T is any type
}

Downgrade code of HttpClient .NET 4.5 to .NET 4.0

I have this code that is working fine in .NET 4.5.
var handler = new HttpClientHandler();
handler.UseDefaultCredentials = true;
handler.PreAuthenticate = true;
handler.ClientCertificateOptions = ClientCertificateOption.Automatic;
var client = new HttpClient(handler);
client.BaseAddress = new Uri("http://localhost:22678/");
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
var loginBindingModel = new LoginBindingModel { Password = "test01", UserName = "test01" };
var response = await client.PostAsJsonAsync("api/Account/Login", loginBindingModel);
response.EnsureSuccessStatusCode(); // Throw on error code.
tokenModel = await response.Content.ReadAsAsync<TokenModel>();
Now I have to do the same thing in .NET 4.0.
But I am facing two problems I do not know how to resolve them.
In .NET 4.0. method client.PostAsJsonAsync does not exist.
The existing method is client.PostAsync and it needs HttpContext.
I do request within WPF client... Guys, I have no clue what I can do to archive the same functionality...
Please, help!
Suggest using the BCL / async / "Microsoft HTTP Client Libraries" helper projects to "supplement" .Net 4.0 with equivalent functionality to .Net 4.5 (Can find the latest versions in the NuGet package manager.)
See the following link for more info: http://www.nuget.org/packages/microsoft.bcl.async
(Note: you can get support for http client via same basic mechanism)
Just for someone who is looking for the same .NET 4.0 pure solution I found that code working fine. But It does not have ASYNC/AWAIT thing that should be implemented as well. Anyway it is working exactly the same way as my code in the question.
var webAddr = "http://localhost:22678/api/Account/Login";
var httpWebRequest = (HttpWebRequest)WebRequest.Create(webAddr);
httpWebRequest.ContentType = "application/json; charset=utf-8";
httpWebRequest.Method = "POST";
using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
{
var loginBindingModel = new WebAPILoginBindingModel { Password = "test01", UserName = "test01" };
var myJsonString = Newtonsoft.Json.JsonConvert.SerializeObject(loginBindingModel);
streamWriter.Write(myJsonString);
streamWriter.Flush();
}
var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
}
If you want to use use ASYNC/AWAIT for .NET 4.0 you have to install Microsoft.BCL dll and use async versions of HttpClient as well.
Even after installing the following packages i got other errors.
Install-Package Microsoft.Net.Http
Install-Package System.Net.Http.Formatting.Extension
Turns out Install-Package System.Net.Http.Formatting.Extension installs version of system.net that does not support dot net 4. So I has to stick to PostAsync and create an httpContent object from my custom object.
So
PostAsJsonAsync("api/Account/Login", loginBindingModel)
Becomes (Note i used json.net here )
public HttpContent CreateHttpContent(object data)
{
return new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");
}
PostAsync("api/Account/Login",CreateHttpContent(loginBindingModel))

Download string from URL using a portable class library (PCL)

I'm trying to download a string from ANY webpage within my portable class library. I've created the most basic setup:
created a new PCL project
compatible with WP8 and WinRT as well as the compulsory components such as Silverlight
As WebClient is not compatible across these systems, it is not possible to use:
string data = new WebClient().DownloadString();
I've tried using this as well (uses this):
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = HttpMethod.Get;
HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync();
string data = ""
using (var sr = new StreamReader(response.GetResponseStream()))
{
data = sr.ReadToEnd();
}
However, when I call the second set of code from an external C# application referencing the PCL, the debugger simply fails with NO warning or error message on:
request.GetResponseAsync();
Is there an easy way to download a string that I'm missing?
*also, why would the debugger simply exit with no explanation?
Edit:
Here is another method I have attempted - based on an answer already provided. Again, this method simply exits and force closes the debugger.
PCL Method:
public static async Task<string> DownloadString()
{
var url = "http://google.com";
var client = new HttpClient();
var data = await client.GetStringAsync(url);
return data;
}
Calling method:
private static async void Method()
{
string data = await PCLProject.Class1.DownloadString();
return data;
}
Install the NuGet packages:
Microsoft.Bcl.Async, which adds async/await support to PCLs.
Microsoft.Net.Http, which adds HttpClient support to PCLs.
Then you can do it the easy way:
var client = new HttpClient();
var data = await client.GetStringAsync(url);
This method worked for me, it returned the HTML source code from google.com:
public async void GetStringFromWebpage()
{
using (HttpClient wc = new HttpClient())
{
var data = await wc.GetStringAsync("http://google.com/");
Debug.WriteLine("string:" + data);
}
}

Categories