I'm getting a 404 error when trying to use an HttpClient to connect to a WebApi service using GET. However, POST works without any problem. In the code below, I have a CreditCard class that I use throughout.
Here's my routing configuration:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Here's my code that calls the async operation:
Task task1 = RegisterCard(card, false);
Task task2 = FetchCard(cardid, false);
Here's my code that contains the the async operations:
private async Task RegisterCard(CreditCard card, bool runAsync)
{
try
{
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:63801/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = runAsync
? await client.PostAsJsonAsync("api/card", card)
: client.PostAsJsonAsync("api/card", card).Result;
response.EnsureSuccessStatusCode();
}
}
catch (HttpRequestException ex)
{
throw new HttpRequestException(ex.Message, ex.InnerException);
}
}
private async Task FetchCard(int cardid, bool runAsync)
{
CreditCard card = new CreditCard();
try
{
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:63801/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = runAsync
? await client.GetAsync("api/card/" + cardid)
: client.GetAsync("api/card/" + cardid).Result;
response.EnsureSuccessStatusCode();
}
}
}
catch (HttpRequestException ex)
{
throw new HttpRequestException(ex.Message, ex.InnerException);
}
}
Here's my code for the apiController:
[HttpPost]
public HttpResponseMessage Register(CreditCard card)
{
HttpResponseMessage result;
try
{
RegisterResponse response = _cardRepository.Register(card);
result = Request.CreateResponse(HttpStatusCode.Created, response);
}
catch (Exception ex)
{
// TODO: add logging
result = Request.CreateErrorResponse(HttpStatusCode.BadRequest, "failed to register card");
}
finally
{
// TODO: add audit logging of what attempted and who attempted it
}
return result;
}
[HttpGet]
public CreditCard Fetch(int cardid)
{
CreditCard card = new CreditCard();
try
{
card = _cardRepository.Fetch(cardid);
}
catch (Exception ex)
{
// TODO: add logging
}
finally
{
// TODO: add audit logging of what attempted and who attempted it
}
return card;
}
And my code for the CardRepository:
public RegisterResponse Register(Models.CreditCard card)
{
using (CreditCardContext ccContext = new CreditCardContext())
{
card.MaskedNumber = "XXXXXXXXXXXX" + card.Number.Substring(card.Number.Length - 4, 4);
card.Number = Crypto.EncryptData_Aes(card.Number, KeyType.CardNumberKey);
card.CardGuid = Guid.NewGuid().ToString();
ccContext.CreditCards.Add(card);
ccContext.SaveChanges();
}
card.ResetSensitive();
RegisterResponse response = new RegisterResponse
{
IsSuccess = true,
Message = "successfully registered card",
CreditCard = card
};
return response;
}
public CreditCard Fetch(int cardid) // , bool masked
{
CreditCard card;
using (CreditCardContext ccContext = new CreditCardContext())
{
card = ccContext.CreditCards.SingleOrDefault(x => x.Card_ID == cardid);
}
return card;
}
QUESTION: Why am I getting a 404 error when using an HttpClient object to connect to my WebApi service using HttpGet, but when I use HttpPost, it works correctly?
The problem is the parameter naming in your Fetch method.
If you change it to id as per the route specified it should work:
[HttpGet]
public CreditCard Fetch(int id) // , bool masked
{
...
}
Or, alternatively, you could call the api with the named param (e.g. api/card/?cardid=2)
In Web API 2 you can use Attribute Routing
[Route("api/card/{cardid:int}")]
[HttpGet]
public CreditCard Fetch(int cardid)
{
...
}
Related
I am trying something similar posted [here][1]
[1]: .Net Core Receive FileContentResult from one API to Another This is what I am trying and I think I am very close,just that not sure how to receive the in the receiver Api a response which is being sent by the Producer Api as FileContentResult, any help will be appreciated.
My effort is listed below:
//Source API-working well
[HttpPost("download")]
public async Task<FileContentResult> ExportApplicationUsers2(ExportApplicationUsersQuery command)
{
try
{
var filePath = await Mediator.Send(command);
return File(System.IO.File.ReadAllBytes(filePath), "application/octet-stream");
}
catch (Exception ex)
{
throw ex.InnerException;
}
}
//Controller in Consumer Api
public async Task<Result<FileContentResult>> ExportApplicationUsers(ExportUsersRequest model)
{
try
{
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.PostFileAsync("/users/download", JsonConvert.SerializeObject(model));
return response;
//return File(System.IO.File.ReadAllBytes(response), "application/octet-stream");
}
catch (Exception ex)
{
_logger.Error(ex.Message, ex);
return new Result<FileContentResult>(new List<string> { ex.Message });
}
}
The Actual PostFileAsync() which I need to fix first:
public async Task<FileContentResult> PostFileAsync(string uri, object data)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri);
if (data != null)
{
request.Content = new StringContent(data.ToString(), Encoding.UTF8, "application/json");
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
var response = default(FileContentResult);
var actionTask = _httpClient.SendAsync(request)
.ContinueWith(responseTask =>
{
FileStreamResult resposneMessage = responseTask.Result;
response = (FileContentResult)resposneMessage.ReadAsStreamAsync().Result;//This is where I need help as its not able to convert into FileContentResult!
});
actionTask.Wait();
return response;
}
When I try to debug the application by calling like
http://localhost:5/api/GetEmployeesDEV
It returns error like
No HTTP resource was found that matches the request URI 'http://localhost:57764/api/GetEmployeesDEV'. No action was found on the controller 'GetEmployeesDEV' that matches the request.
The ASP.NET Web API which makes a Call to the Odata endpoint and returns the response received by the call. And I have the below code for the Controller
public class GetEmployeesDEVController : ApiController
{
[HttpGet]
private async Task<EmployeeDTO.RootObject> Get()
{
string userName_Core = ConfigurationManager.AppSettings["core_Username"];
string password_Core = ConfigurationManager.AppSettings["core_Password"];
string BaseURL_Core = ConfigurationManager.AppSettings["BaseURL_Core"];
var byteArray_Core = Encoding.ASCII.GetBytes(userName_Core + ":" + password_Core);
EmployeeDTO.RootObject returnObj = new EmployeeDTO.RootObject();
try
{
// GET
using (var client_Core = new HttpClient())
{
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
Uri uri = new Uri(BaseURL_Core);
client_Core.BaseAddress = uri;
client_Core.DefaultRequestHeaders.Accept.Clear();
client_Core.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client_Core.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray_Core));
string core_URL = BaseURL_Core;
var response = client_Core.GetAsync(core_URL).Result;
var responsedata = await response.Content.ReadAsStringAsync();
returnObj = JsonConvert.DeserializeObject<EmployeeDTO.RootObject>(responsedata);
}
}
catch (Exception ex)
{
throw ex;
}
return returnObj;
}
Not sure if I am missing something here. I didnot change anything with RouteConfig file
Please change the scope of the function
From private
private async Task<EmployeeDTO.RootObject> Get()
To public
public async Task<EmployeeDTO.RootObject> Get()
Action need to be public in order to be available for routing.
I want to post data form my code but no data changed . when I debug the code it got me 200 which mean code is good . I tried to test in fiddler and data is changed successfully . I want to know where is the wrong ?
this is my service including url
public static async Task<string> PostOrderAsync(MachinePostModel Order)
{
try
{
var client = new HttpClient();
string data = JsonConvert.SerializeObject(Order);
var content = new StringContent(data, Encoding.UTF8, "application/json");
var response = await client.PostAsync("http://94.205.253.150:2016/api/JobOrder", content);
return data;
}
catch
{
return null;
}
}
and this is the step where i send order object
private async Task<bool> AcceptPostOrder()
{
MachinePostModel OrderDetails = new MachinePostModel();
try
{
OrderDetails.ID = 1163;
OrderDetails.joStatus = "should to wait";
OrderDetails.remarks = "hello remarks";
var Client = await Services.MachineTestPostService.PostOrderAsync(OrderDetails);
return true;
}
catch (Exception exc)
{
await MachineTestOrderView.machineobj.DisplayAlert("Alert", exc.Message, "OK");
return false;
}
}
finally my model
public class MachinePostModel
{
public int ID { get; set; }
public string remarks { get; set; }
public string joStatus { get; set; }
}
Get link to check if data is changed
http://94.205.253.150:2016/api/JobOrder/Get?regId=1&driverID=35&status=All%20Status
There are some mistakes in your code. Firstly, decorate your action with HttpPost attribute and add a parameter for sent complex object.
[HttpPost]
private async Task<bool> AcceptPostOrder(MachinePostModel Order)
{
MachinePostModel OrderDetails = new MachinePostModel();
try
{
OrderDetails.ID = 1163;
OrderDetails.joStatus = "should to wait";
OrderDetails.remarks = "hello remarks";
var Client = await Services.MachineTestPostService.PostOrderAsync(OrderDetails);
return true;
}
catch (Exception exc)
{
await MachineTestOrderView.machineobj.DisplayAlert("Alert", exc.Message, "OK");
return false;
}
}
And, there is no routing definition as api/JobOrder, so I assume that you are trying to post the data AcceptPostOrder method. Change the request endpoint;
var response = await client.PostAsync("http://94.205.253.150:2016/api/AcceptPostOrder", content);
Code I have constructed so for:
public class RestService : IRestService
{
public async Task<StellaData> GetStellConfigData()
{
try
{
//Declare a Http client
HttpClient client = new HttpClient();
//Add a Base URl
//client.BaseAddress = new Uri(Constants.MUrl);
//Add the response type
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Add the API
var response =await client.GetStringAsync(new Uri(Constants.mUrl));
var myItems = Newtonsoft.Json.JsonConvert.DeserializeObject<StellaData>(response);
return myItems;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
return null;
}
}
What I am trying to do:
Set a Timeout for the request
Catch Related Exception for the timeout for the request
Get the response code for the request
This question really has nothing to do with MvvmCross, Xamarin or Android, since you're using the same HTTP client you would in any .NET application. Nevertheless, HttpClient has a Timeout property which you can set to ensure your requests time out after a certain interval. I've changed GetStringAsync to GetAsync, since GetAsync will throw a TaskCanceledException if the request times out, which you can catch and handle. GetStringAsync would handle the timeout internally, and you wouldn't be able to catch it. I've rewritten your method to achieve that (this example has a 30-second timeout), as well as assign the status code to a variable for you to use:
public async Task<StellaData> GetStellConfigData()
{
try
{
using (var client = new HttpClient
{
Timeout = TimeSpan.FromMilliseconds(30000)
})
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.GetAsync(new Uri(Constants.mUrl));
HttpStatusCode statusCode = response.StatusCode;
var myItems = Newtonsoft.Json.JsonConvert.DeserializeObject<StellaData>(await response.Content.ReadAsStringAsync());
return myItems;
}
}
catch (TaskCanceledException tcex)
{
// The request timed out
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
return null;
}
This class can be refactored to reuse the client instead of creating a new instance for each request. Set the time-out on the client when initialized.
public class RestService : IRestService {
private static HttpClient client = new HttpClient();
static RestService() {
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.Timeout = TimeSpan.FromMilliseconds(Constants.DefaultTimeOut);
}
public async Task<StellaData> GetStellConfigData() {
try {
//Add the API
using(var response = await client.GetAsync(new Uri(Constants.mUrl))) {
if (response.IsSuccessStatusCode) {
return await response.Content.ReadAsAsync<StellaData>();
}
}
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine(ex.Message);
}
return null;
}
}
I want to add property value in message handler of asp.net webapi.
scenario is I implement webapi in which authenticationToken is part of different requests but client ask me to accept token in request header. but some applications are already using this api is sending token in request body.
so i don't want to change implementation. I want to read request header and if it contains header then assign that value to property of request.
is it possible?
Yes, it's possible to modify your request uri & formdata in message handler. Here is an example to do that, you can try this. I've added a class APIKeyHandler to handle this, and added handler in my WebApiConfig. I've uploaded a solution in my Git you can check it from Example WebAPI with Modify Request Body in MessageHandler
Code of WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
System.Web.Routing.RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//add the handler class in WebApiConfig
config.MessageHandlers.Add(new APIKeyHandler());
}
}
Code of APIKeyHandler:
public class APIKeyHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var formData = await request.Content.ReadAsFormDataAsync();
if (request.Method.Method.Equals("POST"))
{
request.Content = new FormUrlEncodedContent(ModifyYourFormData(formData, request));
}
request.RequestUri = new Uri(ModifyYourURI(request.RequestUri.ToString(), request), UriKind.Absolute);
return await base.SendAsync(request, cancellationToken);
}
public IEnumerable<KeyValuePair<string, string>> ModifyYourFormData(NameValueCollection source, HttpRequestMessage request)
{
//Add your logic here
string Authorized = "";
try
{
Authorized = request.Headers.GetValues("AuthorizedKey").FirstOrDefault();
}
catch (Exception ex)
{
}
List<KeyValuePair<string, string>> formData;
formData = source.AllKeys.SelectMany(
source.GetValues,
(k, v) => new KeyValuePair<string, string>(k, v)).ToList();
if (!string.IsNullOrEmpty(Authorized))
{
formData.Insert(0, new KeyValuePair<string, string>("AuthorizedKey", Authorized));
}
return formData;
}
public string ModifyYourURI(string uri, HttpRequestMessage request)
{
//Add your logic here
string Authorized = "";
try
{
Authorized = request.Headers.GetValues("AuthorizedKey").FirstOrDefault();
}
catch (Exception ex)
{
}
if (!string.IsNullOrEmpty(Authorized))
{
return uri + "?AuthorizedKey="+ Authorized;
}
else
{
return uri;
}
}
}
Code of ExampleAPIController:
public class ExampleAPIController : ApiController
{
public async Task<string> Post()
{
string returnValue = "OK";
var formKeyValueData = await Request.Content.ReadAsFormDataAsync();
string AuthorizedKey = formKeyValueData["AuthorizedKey"];
return returnValue;
}
public string Get(string AuthorizedKey)
{
string returnValue = "OK";
return returnValue;
}
}
POST Request Calling Example:
GET Request Calling Example:
one more solution I figure out to add dynamic property in request
if (request.Headers.Contains(HeaderName) && request.Method.Method.Equals("POST"))
{
var formData = await request.Content.ReadAsStringAsync();
dynamic data = JsonConvert.DeserializeObject(formData);
data.authenticationToken = request.Headers.GetValues(HeaderName).FirstOrDefault();
request.Content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");
}