I'm quite new to .Net Core Web API and have spent a few days looking for an answer but couldn't find exactly what I am looking for. What I want to know is how to retrieve the custom object that is pass from an API action back to the client via an ActionResult (BadRequest(), NotFound()...etc.)
So I created a new Web API project in VS2019 and updated the default Get method of the WeatherForecastController like this:
[HttpGet]
public ActionResult<IEnumerable<WeatherForecast>> Get()
{
return NotFound(new { Message = "Could not find data", Suggestion = "Refine your search" });
}
When testing in Postman, I can get the expected output of Status = 404 and body is
{
"message": "Could not find data",
"suggestion": "Refine your search"
}
But in the client project, I just don't know how I can retrieve that custom error object.
My client code is like this:
public async Task OnGet()
{
try
{
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://localhost:44377/");
WeatherForcasts = await httpClient.GetFromJsonAsync<WeatherForcast[]>("weatherforecast");
}
catch (HttpRequestException ex)
{
hasError = true;
}
}
I understand that if the API action does not return a success status code (such as 200) then this will raise an HttpRequestException. But I can't find a away to get that custom error object out from the HttpRequestException.
Any help will be very much appreciated!
Change your code to this:
public async Task OnGet()
{
using var client = new HttpClient();
var baseAddress ="https://localhost:44377");
client.BaseAddress = new Uri(baseAddress);
var response= await client.GetAsync(baseAddress);
var statusCode = response.StatusCode.ToString(); // shoud be "NotFound"
var stringData = await response.Content.ReadAsStringAsync();
var data= JsonConvert.DeserializeObject<object>(stringData);// should be
// "{{"message":"Could not find data","suggestion": "Refine your search"}}"
....
}
Related
I have a HttpPut API method that edits an object it is passed and saves into the database successfully. This works fine, however from my MVC application, the httpClient.PutAsync which I use to call my API Put method returns internal server error, even though the API Put method does not.
I am not sure what is going wrong, the API method works fine, but somehow the MVC HttpClient still gets an internal server error.
API PUT METHOD
[HttpPut]
public IActionResult Put([FromBody] School school)
{
try
{
var schoolExists = _schoolRepository.SchoolExists(school.Id);
if (!schoolExists) return NotFound();
if (!ModelState.IsValid) return BadRequest();
var schoolData = Mapper.Map<School, Data.School>(school);
var updatedClass = _schoolRepository.UpdateSchool(schoolData);
if (!updatedClass) return Json(GetHttpResponseMessage(HttpStatusCode.InternalServerError));
var route = CreatedAtRoute("GetSchool", school);
return route;
}
catch (Exception e)
{
return LogException(e);
}
}
The method above works fine and my changes are saved into the database and CreatedAtRouteResult object is returned from the API method.
MVC HTTPCLIENT
public async Task<T> PutObject(string path, T content, string accessToken)
{
using (var httpClient = new HttpClient())
{
try
{
SetBaseUri(httpClient, accessToken);
var serialisezContent = CreateHttpContent(content);
var httpResponse = await httpClient.PutAsync(path, serialisezContent);
if (httpResponse.StatusCode == HttpStatusCode.InternalServerError) throw new Exception("Problem accessing the api");
return JsonConvert.DeserializeObject<T>(GetResult(httpResponse));
}
catch (Exception ex)
{
throw ex;
}
}
}
The method above is where is issue is, this line var httpResponse = await httpClient.PutAsync(path, serialisezContent); returns internal server error still. I have the same implementation for my POST and that works just fine.
SETBASEURI()
private void SetBaseUri(HttpClient httpClient, string accessToken)
{
httpClient.BaseAddress = new Uri(BaseUri);
httpClient.DefaultRequestHeaders.Authorization =
_authenticationHeaderValueCreator.CreateAuthenticationHeaderValue("bearer", accessToken);
}
CreateHttpContent()
public ByteArrayContent CreateHttpContent<TParam>(TParam httpObject)
{
var content = JsonConvert.SerializeObject(httpObject);
var buffer = System.Text.Encoding.UTF8.GetBytes(content);
var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return byteContent;
}
I bet your API Put method indeed returns HTTP 500 error with message No route matches the supplied values. You could check it in Fiddler.
And the problem is with following line:
var route = CreatedAtRoute("GetSchool", school);
CreatedAtRoute method takes a route name as first argument. I doubt that you have a route named GetSchool. It's rather an action name in the same controller. And CreatedAtRoute will not throw exception for unknown route, it will just return 500 error code.
To fix this problem, use CreatedAtAction method instead of CreatedAtRoute:
var route = CreatedAtAction("GetSchool", school);
I think the problem is that the result from the API is failing to serialize. Try to manually serialize the result yourself in a unit-test and see where it fails.
When testing my web API with Postman my API get executes fine!
When it comes to running the code with HttpClient in my client application the code executes without error but without the expected result on the server.
What could be happening?
From my client application:
private string GetResponseFromURI(Uri u)
{
var response = "";
HttpResponseMessage result;
using (var client = new HttpClient())
{
Task task = Task.Run(async () =>
{
result = await client.GetAsync(u);
if (result.IsSuccessStatusCode)
{
response = await result.Content.ReadAsStringAsync();
}
});
task.Wait();
}
return response;
}
Here is the API controller:
[Route("api/[controller]")]
public class CartsController : Controller
{
private readonly ICartRepository _cartRepo;
public CartsController(ICartRepository cartRepo)
{
_cartRepo = cartRepo;
}
[HttpGet]
public string GetTodays()
{
return _cartRepo.GetTodaysCarts();
}
[HttpGet]
[Route("Add")]
public string GetIncrement()
{
var cart = new CountedCarts();
_cartRepo.Add(cart);
return _cartRepo.GetTodaysCarts();
}
[HttpGet]
[Route("Remove")]
public string GetDecrement()
{
_cartRepo.RemoveLast();
return _cartRepo.GetTodaysCarts();
}
}
Note these API calls work as expected when called from Postman.
You shouldn't use await with client.GetAsync, It's managed by .Net platform, because you can only send one request at the time.
just use it like this
var response = client.GetAsync("URL").Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
// Parse the response body. Blocking!
var dataObjects = response.Content.ReadAsAsync<object>().Result;
}
else
{
var result = $"{(int)response.StatusCode} ({response.ReasonPhrase})";
// logger.WriteEntry(result, EventLogEntryType.Error, 40);
}
You are doing fire-and-forget approach. In your case, you need to wait for the result.
For example,
static async Task<string> GetResponseFromURI(Uri u)
{
var response = "";
using (var client = new HttpClient())
{
HttpResponseMessage result = await client.GetAsync(u);
if (result.IsSuccessStatusCode)
{
response = await result.Content.ReadAsStringAsync();
}
}
return response;
}
static void Main(string[] args)
{
var t = Task.Run(() => GetResponseFromURI(new Uri("http://www.google.com")));
t.Wait();
Console.WriteLine(t.Result);
Console.ReadLine();
}
Simple sample used to get page data.
public string GetPage(string url)
{
HttpResponseMessage response = client.GetAsync(url).Result;
if (response.IsSuccessStatusCode)
{
string page = response.Content.ReadAsStringAsync().Result;
return "Successfully load page";
}
else
{
return "Invalid Page url requested";
}
}
I've had a problem with chace control when using httpclient.
HttpBaseProtocalFilter^ filter = ref new HttpBaseProtocolFilter();
filter->CacheControl->ReadBehavior = Windows::Web::Http::Filters::HttpCacheReadBehavior::MostRecent;
HttpClient^ httpClient = ref new HttpClient(filter);
I'm not really sure what the expected results are or what results your getting at all so this is really just a guessing game right now.
When I POST something using HttpClient I found adding headers by hand seemed to work more often than using default headers.
auto httpClient = ref new HttpClient();
Windows::Web::Http::Headers::HttpMediaTypeHeaderValue^ type = ref new Windows::Web::http::Headers::HttpMediaTypeHeaderValue("application/json");
content->Headers->ContentType = type;
If I don't do these 2 things I found, for me anyways, that half the time my web requests were either not actually being sent or the headers were all messed up and the other half of the time it worked perfectly.
I just read a comment where you said it would only fire once, that makes me think it is the cachecontrol. I think what happens is something (Windows?) sees 2 requests being sent that are the exact same, so to speed things up it just assumes the same answer and never actually sends the request a 2nd time
Currently working with the outlook api, even tough I usually work with the outlook library acquired via Nuget; I have reached a limitation where I am not able to accept event invitations. So I proceeded in making a a restful call out to the the outlook api. However, when I am making the call I am getting the following message {"error":{"code":"InvalidMethod","message":"An action can only be invoked as a 'POST' request."}} when executing the call.
Bad Code
class Program
{
static void Main(string[] args)
{
var testAccept = ExecuteClientCall.AcceptEvent().Result;
}
public static async Task<bool> AcceptEvent()
{
AuthenticationContext authenticationContext = new AuthenticationContext(CrmPrototype.Helpers.AuthHelper.devTenant);
try
{
var token = await GetTokenHelperAsync(authenticationContext, CrmPrototype.Helpers.AuthHelper.OutlookAuthenticationEndpoint);
string requestUrl = "https://outlook.office.com/api/v2.0/Users/***#nowwhere.com/events('AAQkAGZiNDQxZTVkLWQzZjEtNDdjNy04OTc4LTM4NmNjM2JiOTRjNAAQAFpV0CnWR0FIpWFYRtszPHU=')/accept";
HttpClient hc = new HttpClient();
hc.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var method = new HttpMethod("POST");
var request = new HttpRequestMessage(method, requestUrl)
{
Content = new StringContent("{SendResponse: true}", Encoding.UTF8, "application/json")
};
HttpResponseMessage hrm = await hc.GetAsync(requestUrl);
if (hrm.IsSuccessStatusCode)
{
string jsonresult = await hrm.Content.ReadAsStringAsync();
var stophere = 0;
}
else
{
return false;
}
return true;
}
catch (Exception ex)
{
throw;
}
}
}
Maybe the reason is that you called
hc.GetAsync(requestUrl);
The doc said that this method:
Sends a GET request to the specified Uri as an asynchronous operation.
Try:
PostAsync(Uri, HttpContent)
https://msdn.microsoft.com/en-us/library/system.net.http.httpclient(v=vs.118).aspx
Hope this help you.
Your variable request contains an HttpRequestMessage object that you have created, but your code presently doesn't do anything with it.
Try replacing the line
HttpResponseMessage hrm = await hc.GetAsync(requestUrl);
(which, as pointed out by the other answer, makes a GET request), with
HttpResponseMessage hrm = await hc.SendAsync(request);
I'm using HttpClient to connect to a server (see simplified code below). I cant figure out how I would respond to HTML error codes (e.g. 403) and timeouts so I can report what the result is.
When I encounter a 403 error code an error pop-up occurs in Visual Studio. But I can figure out how I convert this into try in the code. i.e. is the name of the exception present in the error pop-up?
using System.Net.Http;
HttpClient client = new HttpClient();
var response = client.PostAsync(dutMacUrl, null).Result;
var result = response.Content.ReadAsStringAsync().Result;
you can use async/await feature to simplify your code and avoid using Result.
for example
public async Task<string> Foo(string uri)
{
var client = new HttpClient();
try
{
var response = await client.PostAsync(uri, null);
}
catch (Exception ex)
{
//here you handle exceptions
}
// use this if (response.StatusCode != HttpStatusCode.OK) { do what you want }
// or this if (response.IsSuccessStatusCode) { do what you want }
var result = await response.Content.ReadAsStringAsync();
return result;
}
If you are using webAPI another option is to use IHttpActionResult
public object IHttpActionResult mymethod()
{
// instantiate your class or object
IEnumerable<yourClass> myobject = new IEnumerable<yourClass>();
// assuming you want to return a collection
try
{
// do stuff
// handle dto or map result back to object
return Ok(myobject)
}
catch(Exception e)
{
// return a bad request if the action fails
return BadRequest(e.Message)
}
}
This would allow you to make a call to your api endpoint and either return a successful response with the updated object or return a bad request if the endpoint fails.
I've create WebAPI in .net (my first). Using this api to get object from db, query db etc is easy for me. Nothing new
But I'm wondering how to save an object using this webapi ?
I have a clinet application (tablet, phone, PC) that communicates with my webapi. From my application there is an possibility to save a user news. Now I need to save it in db. I use Azure SQL. Now how can I pass this object to API so I can save it ?
For my application I use C#/XAML
For my WebAPI I use .NET
I'm tring with this code:
HttpClient httpClient = new HttpClient();
String u = this.apiUrl + "sd/Localization/insert";
Uri uri = new Uri(u);
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, uri);
But I don't know how to send object ? Should I serialize it ? If yes how to send it via post.
// UPDATE
I've constructed this
HttpClient httpClient = new HttpClient();
String u = this.apiUrl + "sd/Localization/insert";
Uri uri = new Uri(u);
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, uri);
httpRequestMessage.Content = new StringContent("{'Name':'Foo', 'Surname':'Bar'}");
await httpClient.PostAsync(uri, httpRequestMessage.Content);
But in my API the variable is null
This is code from my api
// POST sd/Localization/insert
public void Post(string test)
{
Console.WriteLine(test);
}
The "test" variable is null.
What am I doing wrong ?
// UPDATE 2
using (HttpClient httpClient = new HttpClient())
{
String u = this.apiUrl + "sd/Localization/insert";
Uri uri = new Uri(u);
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri)
{
Method = HttpMethod.Post,
Content = new StringContent("my own test string")
};
await httpClient.PostAsync(uri, request.Content);
}
Routing config
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "sd/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
after all your answers I've created this but still I get null on param in my api. Where is the mistake ?
WebAPI is really good at parsing data sent to it and converting it to .NET objects.
I am not used to using a C# client with WebAPI, but I'd try the following:
var client = new HttpClient();
client.PostAsJsonAsync<YourObjectType>("uri", yourObject);
Note: You need to use System.Net.Http (from assembly with the same name) as well as System.Net.Http.Formatting (also from assembly with the same name) for this.
The HttpRequestMessage class has a property named Content which is type of HttpContent (an abstract class). You can set the request body there. For example, you can set the JSON content there and then send it to the API:
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri) {
Content = new StringContent("{'Name':'Foo', 'Surname':'Bar'}")
};
You can also use the formatting feature and supply your CLR object to ObjectContent and delegate the serialization to the Formatter.
There are lots of samples on HttpClient and Web API here: http://blogs.msdn.com/b/henrikn/archive/2012/07/20/asp-net-web-api-sample-on-codeplex.aspx
Assuming you have an action method on your web API controller that supports a POST operation that is similiar to:
[HttpPost()]
public HttpResponseMessage Post(YourObjectType value)
{
try
{
var result = this.Repository.Add(value);
var response = this.Request.CreateResponse<YourObjectType>(HttpStatusCode.Created, result);
if (result != null)
{
var uriString = this.Url.Route(null, new { id = result.Id });
response.Headers.Location = new Uri(this.Request.RequestUri, new Uri(uriString, UriKind.Relative));
}
return response;
}
catch (ArgumentNullException argumentNullException)
{
throw new HttpResponseException(
new HttpResponseMessage(HttpStatusCode.BadRequest)
{
ReasonPhrase = argumentNullException.Message.Replace(Environment.NewLine, String.Empty)
}
);
}
}
You can use the HttpClient to serialize your object to JSON and POST the content to you controller method:
using (var client = new HttpClient())
{
client.BaseAddress = baseAddress;
client.Timeout = timeout;
using (var response = client.PostAsJsonAsync<YourObjectType>("controller_name", yourObject).Result)
{
if (!response.IsSuccessStatusCode)
{
// throw an appropriate exception
}
result = response.Content.ReadAsAsync<YourObjectType>().Result;
}
}
The NuGet package for ASP.NET MVC 4 (which provides you with the web
API framework) is available at http://nuget.org/packages/AspNetMvc.
The Web API reference at
http://msdn.microsoft.com/en-us/library/hh849329(v=vs.108).aspx.
I would also recommend taking a look at Creating a Web API that Supports CRUD Operations, which covers the scenarios you are describing, specifically the Creating a Resource section.
I think I found the solution thats why I'm posting this as answer not comment so any later discussion could be grouped.
If I send request like this
using(HttpClient client = new HttpClient()) {
await client.PostAsync(uri, new StringContent("my own string");
}
Than I can get it in my webapi from
await Request.Content.ReadAsStringAsync();
IMO this is not perfect solution but at least I'm on trace. I see that params from function definitione I can get only if they are in URL even when I send a POST request.
Probably this solution also will work (i didn't check it yet) when I use more complex objects then String.
ANy thoughts from someone. Do you think that this is good solution ?
I hope this would be what you are looking for.
I created a generic Post that will accept any object and post it
Client Side
public async Task<HttpResponseMessage> Post<T>(string requestUri, T newObject) where T : class
{
using (var client = new HttpClient())
{
client.BaseAddress = this.HttpClientAddress;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = JsonConvert.SerializeObject(newObject, this.JsonSerializerSettings);
var clientAsync = await client.PostAsync(requestUri, new StringContent(content, Encoding.UTF8, "application/json"));
clientAsync.EnsureSuccessStatusCode();
return clientAsync;
}
}
the call to this will be as simple as
public async Task<int> PostPerson(Models.Person person)
{
//call to the generic post
var response = await this.Post("People", person);
//get the new id from Uri api/People/6 <-- this is generated in the response after successful post
var st = response.Headers.Location.Segments[3];
//do whatever you want with the id
return response.IsSuccessStatusCode ? JsonConvert.DeserializeObject<int>(st) : 0;
}
Also, you can read the object after the post using ReadAsStringAsync() if your usecase requires so.
Server Side
// POST: api/People
public IHttpActionResult Post(Models.Person personDto)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var person = new Entities.Person
{
FirstName = personDto.FirstName,
LastName = personDto.LastName,
DateOfBirth = personDto.DateOfBirth,
PreferedLanguage = personDto.PreferedLanguage
};
_db.Persons.Add(person);
_db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = person.Id }, personDto);
}
I'm not familiar with the HttpClient (I believe it's .NET 4.5), but the concepts behind WebAPI are using standard RESTful constructs. If you want to insert an object via WebAPI, you will need to send a POST request to the service. You should put the contents of the object into the BODY of the request.
Add empty constructors to your webapi model people. This will save you all the time I just wasted trying to figure out why my object was null.
Serialization (and de-serialization i suppose) need default constructors.
This is my way.it's successfully.i hope it's helpful
Fisrt:is all library you must have.you can download from nuget
using Newtonsoft.Json; and
using Newtonsoft.Json.Linq;
Client :
HttpClient client = new HttpClient();
//this is url to your API server.in local.You must change when u pushlish on real host
Uri uri = new Uri("http://localhost/");
client.BaseAddress = uri;
//declared a JArray to save object
JArray listvideoFromUser = new JArray();
//sample is video object
VideoModels newvideo = new VideoModels();
//set info to new object..id/name...etc.
newvideo._videoId = txtID.Text.Trim();
//add to jArray
listvideoFromUser.Add(JsonConvert.SerializeObject(newvideo));
//Request to server
//"api/Video/AddNewVideo" is router of API .you must change with your router
HttpResponseMessage response =client.PostAsJsonAsync("api/Video/AddNewVideo", listvideoFromUser).Result;
if (response.IsSuccessStatusCode){
//show status process
txtstatus.Text=response.StatusCode.ToString();
}
else{
//show status process
txtstatus.Text=response.StatusCode.ToString();
}
Server side:
[Route("api/Video/AddNewVideo")]
[System.Web.Http.HttpPost]
public HttpResponseMessage AddNewVideo(JArray listvideoFromUser){
if (listvideoFromUser.Count > 0){
//DeserializeObject: that object you sent from client to server side.
//Note:VideoModels is class object same as model of client side
VideoModels video = JsonConvert.DeserializeObject<VideoModels>(listvideoFromUser[0].ToString());
//that is just method to save database
Datacommons.AddNewVideo(video);
//show status for client
HttpResponseMessage response = new HttpResponseMessage { StatusCode = HttpStatusCode.Created };
return response;
}
else{
HttpResponseMessage response = new HttpResponseMessage { StatusCode = HttpStatusCode.InternalServerError };
return response;
}
}
All done !