I have a web API application. I'm supposed to do a post to an endpoint. When l tried my API controller in postman, l get the error message "
Requested resource does not support HTTP 'POST'
I'm new to Web API so any help and suggestions are welcomed.
This is my model class:
namespace Products.Models
{
public class Prouct
{
public string ProductID { get; set; }
public string ProductName { get; set; }
public string ProductPrice { get; set; }
public string VoucherID { get; set; }
}
}
Here is my controller class
[RoutePrefix("api/products")]
public class ProductsController : ApiController
{
static HttpClient client = new HttpClient();
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
public string Get(int id)
{
return "value";
}
[Route("products")]
public async Task PostAsync(string ProductID, string ProductName, string ProductPrice,
string VoucherID)
{
Products p = new Products();
p.ProductID = ProductID;
p.ProductName = ProductName;
p.ProductPrice = ProductPrice;
p.VoucherID = VoucherID;
var client = new HttpClient { BaseAddress = new
Uri("http://localhost:51613/") };
var response = await
client.PostAsJsonAsync("api/products",
p);
if (response.IsSuccessStatusCode)
{
}
public void Put(int id, [FromBody]string value)
{
}
public void Delete(int id)
{
}
You need to specify HttpPost on PostAsync method. by default, it is [HttpGet].
[HttpPost]
[Route("products")]
public async Task PostAsync(string ProductID, string ProductName, string ProductPrice, string VoucherID)
{
// implementation
}
Looks like you're stuck in a loop. Why does the PostAsync method call itself after having been invoked? This will result in an endless request loop.
var client = new HttpClient { BaseAddress = new Uri("http://localhost:51613/") };
This is not related to the fact that the [HttpPost] attribute is required however.
Please observe that you are supposed to use [FromBody] . Also inside Postman (image attached) you have to choose "Raw" data with the product json with type as JSON(application.json).
[HttpPost]
[Route("products")]
public async Task PostAsync([FromBody] Products p)
{
var client = new HttpClient
{
BaseAddress = new
Uri("http://localhost:51613/")
};
var response = await
client.PostAsJsonAsync("api/products",
p);
if (response.IsSuccessStatusCode)
{
}
}
Related
I have looked at some forum posts and found no solution to my problem, so now I ask you for help. First i show you what the result of my HttpGet is and then i show you the not working deserialization.
Working example: I use the same code for an HttpGet to get a json result.
[HttpGet]
[Route("~/get_new_authtoken")]
public async Task<IActionResult> GetNewAuthTokenAsync()
{
try
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("https://testApi/WebService/");
string APIConnection = _configuration.GetValue<string>("APIConnection");
HttpResponseMessage response = client.PostAsync("authtoken", new StringContent(string.Format(APIConnection))).Result;
if (response.IsSuccessStatusCode)
{
var token = JsonConvert.DeserializeObject<AuthToken>(await response.Content.ReadAsStringAsync());
return Ok(response.Content.ReadAsStringAsync().Result);
}
else
{
return BadRequest(response.Content.ReadAsStringAsync().Result);
}
}
catch (Exception ex)
{
SentrySdk.CaptureException(ex);
return Ok(ex.Message);
}
}
with following result when i call it via Postman
Not working deserialization: I store the token in a database and want to update it when it expires.
Object i want to desiralize in:
public class AuthToken
{
[JsonPropertyName("access_token")] public string AccessToken { get; set; }
[JsonPropertyName("refresh_token")] public string RefreshToken { get; set; }
[JsonPropertyName("token_type")] public string TokenType { get; set; }
[JsonPropertyName("expires_in")] public int ExpiresIn { get; set; }
}
when i add the following code to the httpget above to test the deserializationthe object Authtoken is always null.
AuthToken newAccessToken = new AuthToken();
newAccessToken = JsonConvert.DeserializeObject<AuthToken>(await response.Content.ReadAsStringAsync());
Like this->
if (response.IsSuccessStatusCode)
{
//To test
AuthToken newAccessToken = new AuthToken();
newAccessToken = JsonConvert.DeserializeObject<AuthToken>(await response.Content.ReadAsStringAsync());
//To test
var token = JsonConvert.DeserializeObject<AuthToken>(await response.Content.ReadAsStringAsync());
return Ok(response.Content.ReadAsStringAsync().Result);
}
else
{
return BadRequest(response.Content.ReadAsStringAsync().Result);
}
Updated the testint to following code:
AuthToken newAccessToken = new AuthToken();
var responseString = await response.Content.ReadAsStringAsync();
newAccessToken = JsonConvert.DeserializeObject<AuthToken>(responseString);
result of the responseString:
JsonPropertyNameAttribute is from System.Text.Json while JsonConvert.DeserializeObject is part of Newtonsoft.Json either switch to former completely by using System.Text.Json.JsonSerializer.Deserialize:
AuthToken newAccessToken = JsonSerializer.Deserialize<AuthToken>(...);
// or just use ReadFromJsonAsync instead of ReadAsStringAsync
Or use attributes for latter:
public class AuthToken
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
// ...
}
I'm using .Net 5.0 as backend and .Net 5.0 for client-side.
I want to know how to handle exceptions that returned from web api in Client Side and show them to client.
The api result on exception is like :
{
"Version": "1.0",
"StatusCode": 500,
"ErrorMessage": "User not found!"
}
How to handle this type of exception globally in the client side (using .Net Core MVC)?
According to your description, I suggest you could use try catch on the server-side to capture the exception and return as a json response.
In the client side, you could use deserlize the response and create a new view named Error to show the response message.
More details, you could refer to below codes:
Error Class:
public class APIError
{
public string Version { get; set; }
public string StatusCode { get; set; }
public string ErrorMessage { get; set; }
}
API:
[HttpGet]
public IActionResult Get()
{
try
{
throw new Exception("UserNotFound");
}
catch (Exception e)
{
return Ok(new APIError { Version="1.0", ErrorMessage=e.Message, StatusCode="500" });
}
}
Application:
var request = new HttpRequestMessage(HttpMethod.Get,
"https://localhost:44371/weatherforecast");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var responseStream = await response.Content.ReadAsStringAsync();
APIError re = JsonSerializer.Deserialize<APIError>(responseStream, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
});
if (re.StatusCode == "500")
{
return View("Error", new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier, Version = re.Version, StatusCode = re.StatusCode, ErrorMessage = re.ErrorMessage });
}
}
else
{
// Hanlde if request failed issue
}
Notice: I created a new Error view, you could create it by yourself or modify the default error view.
Error Viewmodel:
public class ErrorViewModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public string Version { get; set; }
public string StatusCode { get; set; }
public string ErrorMessage { get; set; }
}
Error view:
#model ErrorViewModel
#{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
#if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>#Model.RequestId</code>
</p>
}
<h3>#Model.StatusCode</h3>
<p>
#Model.ErrorMessage
</p>
Result:
If you don't want to use exceptions in the backend, you could just send the http status code to the client. Here is an example of reaching out to an external api via service and returning that status to the backend controller. You would then just GET this result via client side. You could also just send over the full http response to the client, instead of solely the HttpStatusCode if needed.
A little more elaboration here: https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/calling-a-web-api-from-a-net-client
//Backend Service..
private const string baseUrl = "https://api/somecrazyapi/";
public async Task<HttpStatusCode> GetUserStatusAsync(string userId)
{
var httpResponse = await client.GetAsync(baseUrl + "userId");
return httpResponse.StatusCode;
}
//Backend Controller
[ApiController]
[Route("[controller]")]
public class UserController
{
private readonly IUserService service;
public UserController(IUserService service)
{
this.service = service;
}
......
[HttpGet("{userId}")]
public HttpStatusCode GetUserStatus(string userId)
{
return service.GetUserStatusAsync(userId).Result;
}
}
so, I get the Access Token and I want to create a request...
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new
AuthenticationHeaderValue("Bearer", accesToken);
HttpResponseMessage response = await httpClient.GetAsync("https://graph.microsoft.com/v1.0/users");
Also, the Problem may be on setting the scopes for the authentication...i set it to:
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
But I don't know if it gets all application permissions that I have (it is a deamon).
You can use this sample which will help you to create events with same client credential flow which you are using but you need to change some things here.
You need to first give the Calendar.ReadWrite permission in the Azure portal for your app.
You need to add the below code in the Program.cs
if (result != null)
{
var httpClient = new HttpClient();
var apiCaller = new ProtectedApiCallHelper(httpClient);
await apiCaller.CallWebAPIToPostEvent($"{config.ApiUrl}v1.0/users/{user obj id}/calendars/{calendar id}/events", result.AccessToken, Display);
}
Then you need to add the below classes in the protectedApiCallHelper.cs
public class Event
{
[JsonProperty("subject")]
public string Subject { get; set; }
[JsonProperty("body")]
public Body Body;
[JsonProperty("start")]
public TimeAndDate Start;
[JsonProperty("end")]
public TimeAndDate End;
[JsonProperty("location")]
public Location Location;
[JsonProperty("attendees")]
public List<Attendees> Attendees;
}
public class Body
{
[JsonProperty("contentType")]
public string ContentType { get; set; }
[JsonProperty("content")]
public string Content { get; set; }
}
public class TimeAndDate
{
[JsonProperty("dateTime")]
public string DateTime { get; set; }
[JsonProperty("timeZone")]
public string TimeZone { get; set; }
}
public class Location
{
[JsonProperty("displayName")]
public string DisplayName { get; set; }
}
public class Attendees
{
[JsonProperty("emailAddress")]
public EmailAddress EmailAddress;
[JsonProperty("type")]
public string Type;
}
public class EmailAddress
{
[JsonProperty("address")]
public string Address { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
}
In this same ProtectedApiCallHelper class you can create a post request and get the details by adding the below code
public async Task CallWebAPIToPostEvent(string webApiUrl, string accessToken, Action<JObject> processResult)
{
var defaultRequetHeaders = HttpClient.DefaultRequestHeaders;
if (defaultRequetHeaders.Accept == null || !defaultRequetHeaders.Accept.Any(m => m.MediaType == "application/json"))
{
HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
defaultRequetHeaders.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
var payload = new Event
{
Subject = "Let's go for lunch",
Body = new Body
{
ContentType = "HTML",
Content = "Does mid month work for you?"
},
Start = new TimeAndDate
{
DateTime = "2019-03-15T12:00:00",
TimeZone = "Pacific Standard Time"
},
End = new TimeAndDate
{
DateTime = "2019-03-15T14:00:00",
TimeZone = "Pacific Standard Time"
},
Location = new Location
{
DisplayName = "Harry's Bar"
},
Attendees = new List<Attendees>
{
new Attendees
{
EmailAddress = new EmailAddress
{
Address = "Shiva#nishantsingh.live",
Name = "Shiva"
},
Type = "required"
}
}
};
// Serialize our concrete class into a JSON String
var stringPayload = await Task.Run(() => JsonConvert.SerializeObject(payload));
// Wrap our JSON inside a StringContent which then can be used by the HttpClient class
var httpContent = new StringContent(stringPayload, Encoding.UTF8, "application/json");
HttpResponseMessage response = await HttpClient.PostAsync(webApiUrl, httpContent);
if (response.Content != null)
{
var responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine(response.Content.ReadAsStringAsync().Result);
}
}
This will help you create the event with these details.
we are writing some API which required sessionId in header and some other data in body.
Is it possible to have only one class automatically parsed partially from header and from body?
Something like:
[HttpGet("messages")]
[Produces("application/json")]
[Consumes("application/json")]
[Authorize(Policy = nameof(SessionHeaderKeyHandler))]
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(PendingMessagesData pendingMessagesRequest)
{
some body...
}
with request class like:
public class PendingMessagesData
{
[FromHeader]
public string SessionId { get; set; }
[FromBody]
public string OrderBy { get; set; }
}
I know, it is possible to do this, but it means, that I have to pass SessionId into the other methods as a parameter, instead of pass only one object. And we would have to do that in every API call.
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(
[FromHeader] string sessionId,
[FromBody] PendingMessagesData pendingMessagesRequest)
{
some body...
}
Thank you,
Jakub
we are writing some API which required sessionId in header and some other data in body. Is it possible to have only one class automatically parsed partially from header and from body
Your GetPendingClockInMessages is annotated with a [HttpGet("messages")]. However, a HTTP GET method has no body at all. Also, it can't consume application/json. Please change it to HttpPost("messages")
Typically, SessionId is not passed in header of Session: {SessionId} like other HTTP headers. Session are encrypted via IDataProtector. In other words, you can't get it by Request.Headers["SessionId"].
Apart from the above two facts, you can create a custom model binder to do that.
Since the Session doesn't come from header directly, let's create a custom [FromSession] attribute to replace your [FromHeader]
public class FromSessionAttribute : Attribute, IBindingSourceMetadata
{
public static readonly BindingSource Instance = new BindingSource("FromSession", "FromSession Binding Source", true, true);
public BindingSource BindingSource { get { return FromSessionAttribute.Instance; } }
}
And since you're consuming application/json, let's create a binder as below:
public class MyModelBinder : IModelBinder
{
private readonly JsonOptions jsonOptions;
public MyModelBinder(IOptions<JsonOptions> jsonOptions)
{
this.jsonOptions = jsonOptions.Value;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var type = bindingContext.ModelType;
var pis = type.GetProperties();
var result= Activator.CreateInstance(type);
var body= bindingContext.ActionContext.HttpContext.Request.Body;
var stream = new System.IO.StreamReader(body);
var json = await stream.ReadToEndAsync();
try{
result = JsonSerializer.Deserialize(json, type, this.jsonOptions.JsonSerializerOptions);
} catch(Exception){
// in case we want to pass string directly. if you don't need this feature, remove this branch
if(pis.Count()==2){
var prop = pis
.Where(pi => pi.PropertyType == typeof(string) )
.Where(pi => !pi.GetCustomAttributesData().Any(ca => ca.AttributeType == typeof(FromSessionAttribute)))
.FirstOrDefault();
if(prop != null){
prop.SetValue( result ,json.Trim('"'));
}
} else{
bindingContext.ModelState.AddModelError("", $"cannot deserialize from body");
return;
}
}
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError("sessionId", $"cannot get SessionId From Session");
return;
} else {
var props = pis.Where(pi => {
var attributes = pi.GetCustomAttributesData();
return attributes.Any( ca => ca.AttributeType == typeof(FromSessionAttribute));
});
foreach(var prop in props) {
prop.SetValue(result, sessionId);
}
bindingContext.Result = ModelBindingResult.Success(result);
}
}
}
How to use
Decorate the property with a FromSession to indicate that we want to get the property via HttpContext.Sessino.Id:
public class PendingMessagesData
{
[FromBody]
public string OrderBy { get; set; } // or a complex model: `public MySub Sub{ get; set; }`
[FromSession]
public string SessionId { get; set; }
}
Finally, add a modelbinder on the action method parameter:
[HttpPost("messages")]
[Produces("application/json")]
[Consumes("application/json")]
public async Task<ActionResult> GetPendingClockInMessages([ModelBinder(typeof(MyModelBinder))]PendingMessagesData pendingMessagesRequest)
{
return Json(pendingMessagesRequest);
}
Personally, I would prefer another way, i.e, creating a FromSessionBinderProvider so that I can implement this without too much effort. :
public class FromSessionDataModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError(sessionId, $"cannot get SessionId From Session");
} else {
bindingContext.Result = ModelBindingResult.Success(sessionId);
}
return Task.CompletedTask;
}
}
public class FromSessionBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
var hasFromSessionAttribute = context.BindingInfo?.BindingSource == FromSessionAttribute.Instance;
return hasFromSessionAttribute ?
new BinderTypeModelBinder(typeof(FromSessionDataModelBinder)) :
null;
}
}
(if you're able to remove the [ApiController] attribute, this way is more easier).
I have Customer and Address classes, something like this:
public class Customer
{
[PrimaryKey]
public int Id { get; set; }
public string CustomerName { get; set; }
public int ShippingAddressId { get; set; }
[ForeignKey("ShippingAddressId")]
public Address ShippingAddress { get; set; }
}
public class Address
{
[PrimaryKey]
public int Id { get; set; }
public string City { get; set; }
}
When I call a Web API to update the Customer, I pass an edited Customer object to this method. For example, I edit 2 properties: Customer.CustomerName and Customer.ShippingAddress.City
AuthenticationResult ar = await new AuthHelper().AcquireTokenSilentAsync();
if (ar.Token == null) return false;
using (var json = new StringContent(JsonConvert.SerializeObject(entity), UnicodeEncoding.UTF8, "application/json"))
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(Settings.ApiUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ar.Token);
using (HttpResponseMessage response = await client.PutAsync("api/" + type.ToString() + "/" + id, json))
{
response.EnsureSuccessStatusCode();
return await InsertOrReplaceResponse(type, response);
}
}
Here is the Web API:
[HttpPut("{id}")]
public async Task<IActionResult> PutCustomer([FromRoute] int id, [FromBody] Customer customer)
{
_context.Entry(customer).State = EntityState.Modified;
await _context.SaveChangesAsync();
return new StatusCodeResult(StatusCodes.Status204NoContent);
}
However, only Customer.CustomerName gets updated. The foreign key data (Customer.ShippingAddress.City) isn't updated in the Address table.
Any idea what I'm doing wrong?
Thanks!
Generally, I would expect the child Address entity to be update as well because you use foreign key association which also should be the default for one-to-one mappings (see here for more details). However, it seems that something is not setup right and you get independent association behavior where you have to manage child entity state yourself. Should be a simple fix in your case:
_context.Entry(customer).State = EntityState.Modified;
_context.Entry(customer.ShippingAddress).State = EntityState.Modified;