Using JsonPatchDocument With PatchAsync In Blazor Client - asp.net-core

In my Blazor Client project, I have the following code:
#using Microsoft.AspNetCore.JsonPatch
...
var doc = new JsonPatchDocument<Movie>()
.Replace(o => o.Title, "New Title");
await Http.PatchAsync("api/patch/" + MovieId, doc);
This won't compile with the following error:
Error CS1503 Argument 2: cannot convert from
'Microsoft.AspNetCore.JsonPatch.JsonPatchDocument'
to 'System.Net.Http.HttpContent'
After some research, I've installed Newtonsoft.Json but I'm unsure how to configure the project to use it, or if indeed this is the correct solution for getting JsonPatchDocument working in a Blazor Project?
If JsonPatchDocument is not supported by Blazor, how can I implement a HTTP Patch request?

I just had a different but related issue. You are correct that you need to be using Newtonsoft.Json instead of System.Text.Json on the client application. Here is an extension method that will turn your JsonPatchDocument into an HttpContent.
public static class HttpClientExtensions
{
public static async Task<HttpResponseMessage> PatchAsync<T>(this HttpClient client,
string requestUri,
JsonPatchDocument<T> patchDocument)
where T : class
{
var writer = new StringWriter();
var serializer = new JsonSerializer();
serializer.Serialize(writer, patchDocument);
var json = writer.ToString();
var content = new StringContent(json, Encoding.UTF8, "application/json-patch+json");
return await client.PatchAsync(requestUri, content);
}
I know it's late but I hope it's helpful.

Related

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);

Sending data to PowerFlow from ASP.Net Core App

I am trying to post data to Power Automate HTTP Request trigger, but i just get all properties with Null values. I dont know what i am missing?
It is requeried to set "Content-Type":"application/json".
(blog post referecne: https://flow.microsoft.com/en-us/blog/call-flow-restapi/ )
My .Net corre app code is:
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Content-Type", "application/json");
var response = await client.PostAsJsonAsync(url, order);
response.EnsureSuccessStatusCode();
var data = await response.Content.ReadAsAsync<String>();
http post data
I test it in my side, you can refer to my power-automate flow and my .net code below:
My flow shown as:
And my code shown as below:
using System;
using System.Net.Http;
using System.Text;
namespace ConsoleApp7
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var postData = "{\"name\": \"Hury\",\"email\": \"test#xxx.com\"}";
HttpContent httpContent = new StringContent(postData, Encoding.UTF8, "application/json");
HttpClient client = new HttpClient();
var response = client.PostAsync("your http request trigger url", httpContent);
Console.WriteLine(response.Result);
}
}
}
After running this code, we can see the properties are post success from the request in the running history.

RestRequest Body not received in .net core web api

I am trying to build a service client to simplify calling my microservices in .net core.
Here is a service client sample:
public ProductServiceClient(SystemEnvironment.MachineEnvironment? environment = null)
{
this.url = ServiceEnvironment.Urls.GetUrl(ServiceEnvironment.Service.Product, environment);
}
private RestClient GetClient(string method)
{
return new RestClient(url + "/api/" + method);
}
private RestRequest GetRestRequest(Method method)
{
var restRequest = new RestRequest(method);
restRequest.RequestFormat = DataFormat.Json;
restRequest.AddHeader("Content-Type", "application/json");
return restRequest;
}
public FindProductsResponse FindProducts(FindProductsRequest request)
{
var restRequest = GetRestRequest(Method.GET);
restRequest.AddJsonBody(request);
var client = this.GetClient("Products");
var restResponse = client.Get(restRequest);
return new JsonDeserializer().Deserialize<FindProductsResponse>(restResponse);
}
public void Dispose()
{
}
And here is how I am trying to read it in my .net core api:
[HttpGet]
public ActionResult<FindProductsResponse> Get()
{
var request = "";
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
request = reader.ReadToEnd();
}
var buildRequest = JsonConvert.DeserializeObject<FindProductsRequest>(request);
var products = _service.FindProducts(buildRequest);
if (products != null && products.Any())
{
return new FindProductsResponse()
{
Products = products
};
}
return BadRequest("Not found");
}
However the request variable is always empty after Request.Body has been processed by the StreamReader.
If I make the same request from Postman (also using GET), I get the body just fine.
What am I doing wrong here?
EDIT: This is the unit test calling the api:
[Test]
public void Test1()
{
using (var productServiceClient = new ProductServiceClient())
{
var products = productServiceClient.FindProducts(new FindProductsRequest()
{
Id = 50
}).Products;
}
}
It can be your Request.Body has been already consumed.
Try to call Request.EnableRewind() before to open the StreamReader.
I'm not sure why you are manually doing it. It looks like you are reinventing the wheel. ASP.NET Core already does that for you.
This is what your service should look like:
[HttpGet] // oops, GET requests will not allow Bodies, this won't work
public ActionResult<FindProductsResponse> Get([FromBody]FindProductsRequest buildRequest)
{
// skip all the serialization stuff, the framework does that for you
var products = _service.FindProducts(buildRequest);
if (products != null && products.Any())
{
return new FindProductsResponse()
{
Products = products
};
}
return BadRequest("Not found");
}
And if you don't want to redo all the busy work that is retyping all the code on the client side, I suggest you read up on swagger (probably in the form of Swashbuckle). Client code can be generated. Even from within Visual Studio, if you right-click on the project and in the context menu pick "Add REST API Client...". Please don't erroneously hand-code what can be generated flawlessly by a machine instead. I don't really know what went wrong in your specific case, but searching bugs that could be avoided altogether is just busywork, that time should be spent on other parts of the program.
I just realized this is a GET request. ASP.NET will not recognize bodies for GET-Requests. You will need to make it a PUT or POST request or put your parameters in the query string.
If you happen to make that mistake as often as I did, you might want to write some unit tests that cover this. Because .NET is not helping you there. Been there, done that..

Receive IFileForm in Net Core Controller and forward to another (independent) API

I have an Vue.JS application, where I upload an image to a NetCore Controller.
I'm receiving the IFileForm in the following controller:
[HttpPost("UpdateContactPhoto")]
public async Task<string> UpdateContactPhoto(IFormFile file){ //Forward the original IFileForm to another NetCore API. }
At this point everything is working correctly. IFileForm arrives perfect.
My problem is that I need to forward this IFileForm to another API (independent of this) whose input is an IFileForm with HttpClient PutAsync, but not works.
Can someone help me?
Thanks for help.
You can use this example. Note that the argument name is the same as the item added to the form-data:
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:57985");
byte[] data;
using (var br = new BinaryReader(file.OpenReadStream()))
{
data = br.ReadBytes((int) file.OpenReadStream().Length);
}
ByteArrayContent bytes = new ByteArrayContent(data);
MultipartFormDataContent multiContent = new MultipartFormDataContent();
multiContent.Add(bytes, "file", file.FileName);
var result = client.PutAsync("api/v1/FileManager", multiContent).Result;
if (result.StatusCode == HttpStatusCode.OK)
{
//do some things
}
}
You can also use this code to get the file from the HttpContext :
IFormFile file = HttpContext.Request.Form.Files[0];
Replace "Target url here" with your destination URL:
HttpClient httpClient = new HttpClient();
var streamcontent = new StreamContent(file.OpenReadStream());
var response = await httpClient.PutAsync("target url here", streamcontent);
Reference:
HttpClient PutAsync
StreamContent class
IFormFile interface

google oauth 2 authorization when using their indexing api

I'm trying to make sense of the google indexing api but their documentation is horrible. I've gone through setting up the service account and downloading the json file along with the remaining prerequisites. The next step is to get an access token to authenticate.
I'm in a .net environment but they don't give an example for that. I did find some example of using a .net library to do it here, but after the following code I'm not sure what service would be created to then make the call to the indexing api. I don't see a google.apis.indexing library in the nuget package manager.
UserCredential credential;
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] { "https://www.googleapis.com/auth/indexing" },
"user", CancellationToken.None, new FileDataStore("IndexingStore"));
}
In their example code it looks like just a simple json post. I tried that but of course it doesn't work because I'm not authenticated. I'm just not sure how to tie all of this together in a .net environment.
You're right, Google's documentation for this is either not there or is terrible. Even their own docs have broken or unfinished pages in them and in one of them you're pointed to a nuget package that doesn't exist. It is possible to get this to work though by cobbling together other Auth examples on SA and then following the Java indexing documentation.
First, you'll need to use nuget package manager to add the main api package and the auth package:
Google.Apis
Google.Apis.Auth
Then try the following:
using System;
using System.Configuration;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Http;
using Newtonsoft.Json;
namespace MyProject.Common.GoogleForJobs
{
public class GoogleJobsClient
{
public async Task<HttpResponseMessage> AddOrUpdateJob(string jobUrl)
{
return await PostJobToGoogle(jobUrl, "URL_UPDATED");
}
public async Task<HttpResponseMessage> CloseJob(string jobUrl)
{
return await PostJobToGoogle(jobUrl, "URL_DELETED");
}
private static GoogleCredential GetGoogleCredential()
{
var path = ConfigurationManager.AppSettings["GoogleForJobsJsonFile"];
GoogleCredential credential;
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream)
.CreateScoped(new[] { "https://www.googleapis.com/auth/indexing" });
}
return credential;
}
private async Task<HttpResponseMessage> PostJobToGoogle(string jobUrl, string action)
{
var googleCredential = GetGoogleCredential();
var serviceAccountCredential = (ServiceAccountCredential) googleCredential.UnderlyingCredential;
const string googleApiUrl = "https://indexing.googleapis.com/v3/urlNotifications:publish";
var requestBody = new
{
url = jobUrl,
type = action
};
var httpClientHandler = new HttpClientHandler();
var configurableMessageHandler = new ConfigurableMessageHandler(httpClientHandler);
var configurableHttpClient = new ConfigurableHttpClient(configurableMessageHandler);
serviceAccountCredential.Initialize(configurableHttpClient);
HttpContent content = new StringContent(JsonConvert.SerializeObject(requestBody), Encoding.UTF8, "application/json");
var response = await configurableHttpClient.PostAsync(new Uri(googleApiUrl), content);
return response;
}
}
}
You can then just call it like this
var googleJobsClient = new GoogleJobsClient();
var result = await googleJobsClient.AddOrUpdateJob(url_of_vacancy);
Or if you're not inside an async method
var googleJobsClient = new GoogleJobsClient();
var result = googleJobsClient.AddOrUpdateJob(url_of_vacancy).Result;