How do I force my controller to return a 404?
[HttpGet]
[Route("account({accountid})/printgroup", Name = "Get")]
public HttpResponseMessage Get(int accountid)
{
var query = Request.RequestUri.Query;
var uri = new Uri(Client.Instance.BaseAddress.ToString().Replace("[accountid]", accountid.ToString()) + query);
var request = new HttpRequestMessage {RequestUri = uri, Method = HttpMethod.Get};
var clientResponse = Client.Instance.SendAsync(request).Result;
return clientResponse;
}
When the clientResponse is 404, the method does not return a response. It just times out.
What am I doing wrong? How do I force it to return a 404 if the client response is 404?
EDIT:
Per macceturra's helpful comments, I've created my own object:
Yet the behavior is the same! The method will go out of scope, yet the client does not see anything. It just times out.
My latest update is:
[HttpGet]
[Route("account({accountid})/bill({billingRunId})", Name = "GetInvoiceSummary")]
public async Task<IHttpActionResult> GetInvoiceSummary(int accountid, int billingRunId)
{
var query = Request.RequestUri.Query;
var uri = new Uri(Client.Instance.BaseAddress.ToString() + accountid + "/" + billingRunId + query);
var request = new HttpRequestMessage { RequestUri = uri, Method = HttpMethod.Get };
var response = await Client.Instance.SendAsync(request);
if (response.StatusCode == HttpStatusCode.NotFound)
return NotFound();
return ResponseMessage(response);
}
The behavior is once again the same.
Update
Based on discussion in comments it was advised that you review the response pipeline as it was determined that the code in the action was handling/executing the request as intended. The issue was being encountered by the response on its way out the pipeline which cause the client to time out. Check your message handlers and middle-ware in the pipeline that may be causing the response to block on its way out the pipeline based on how it was handled by your controllers.
Original answer
Consider the following, using the preferred Web API 2.* syntax along with proper use of async/await.
[HttpGet]
[Route("account({accountid})/printgroup", Name = "Get")]
public async Task<IHttpActionResult> Get(int accountid) {
var query = Request.RequestUri.Query;
var uri = new Uri(Client.Instance.BaseAddress.ToString().Replace("[accountid]", accountid.ToString()) + query);
var request = new HttpRequestMessage { RequestUri = uri, Method = HttpMethod.Get };
var clientResponse = await Client.Instance.SendAsync(request);
if (clientResponse.StatusCode == System.Net.HttpStatusCode.NotFound)
return NotFound();
return ResponseMessage(clientResponse);
}
After adding this piece of middleware, the problem has been resolved:
public class NotFoundMiddleware : OwinMiddleware
{
public NotFoundMiddleware(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
await Next.Invoke(context);
if (context.Response.StatusCode == (int)HttpStatusCode.NotFound
&& !context.Response.Headers.ContainsKey("X-ServiceFabric")
)
context.Response.Headers.Add("X-ServiceFabric", new[] { "ResourceNotFound" });
}
}
Related
I have a simple API gateway controller which returns an IActionResult. The issue is I am not able to read the body of the response.
If I comment out the using block in ExecuteResultAsync it seems to work fine but there is not content/body.
Not sure how to get this working with the httpbody being returned. RouteRequest returning HttpResponseMessage is not an option as it puts the response from the microservice as the body of the response from the Gateway.
So I need to use the HttpResponseMessageResult middleware, which works as expected for headers but not for the body.
public async Task<IActionResult> RouteRequest()
{
// Calls a method which send a request and gets a response and constructs a HttpResponseMessage
_contextAccessor.HttpContext.Response.RegisterForDispose(response);
return new HttpResponseMessageResult(response);
}
public class HttpResponseMessageResult : IActionResult
{
private readonly HttpResponseMessage _responseMessage;
public HttpResponseMessageResult(HttpResponseMessage responseMessage)
{
_responseMessage = responseMessage;
}
public async Task ExecuteResultAsync(ActionContext context)
{
context.HttpContext.Response.StatusCode = (int)_responseMessage.StatusCode;
var responseMessageHeadersArray = _responseMessage.Headers.ToArray();
for (int i = 0; i < responseMessageHeadersArray.Length; i++)
{
var header = responseMessageHeadersArray[i];
context.HttpContext.Response.Headers.TryAdd(header.Key, new StringValues(header.Value.ToArray()));
}
using (var stream = await _responseMessage.Content.ReadAsStreamAsync())
{
await stream.CopyToAsync(context.HttpContext.Response.Body);
await context.HttpContext.Response.Body.FlushAsync();
}
}
}
Try this out, based on this good answer to a similar question, I used the ObjectResult class instead of manually manipulating the streams. When I run it with response from one of our API's (JSON), I get the same amount of data in the body of objectResult when it calls ExecuteAsync as were in the initial response.
public class HttpResponseMessageResult : IActionResult
{
private readonly HttpResponseMessage _responseMessage;
public HttpResponseMessageResult(HttpResponseMessage responseMessage)
{
_responseMessage = responseMessage;
}
public async Task ExecuteResultAsync(ActionContext context)
{
var objectResult = new ObjectResult(await _responseMessage.Content.ReadAsStreamAsync())
{StatusCode = (int)_responseMessage.StatusCode};
foreach (KeyValuePair<string, IEnumerable<string>> h in _responseMessage.Headers)
{
context.HttpContext.Response.Headers.TryAdd(h.Key, string.Join("", h.Value));
}
await objectResult.ExecuteResultAsync(context);
}
}
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 am confused on where my deserialization logic should go.
I have a controller that returns data to the client specifically for a GET operation:
public accountscontroller:apicontroller
{
[HttpGet]
[Route("", Name = "GetAccount")]
public async Task<IHttpActionResult> GetAccount()
{
var query = Request.RequestUri.PathAndQuery.Split('/')[2];
var response = await _accountService.GetAccount(query);
if (response == null)
{
return NotFound();
}
return Ok(response);
}
//morestuff
}
and the AccountService.GetAccount code is the following:
public class AccountService
{
public async Task<Account> GetAccount(string query)
{
var task = await Client.HTTPCLIENT.GetAsync(Client.HTTPCLIENT.BaseAddress + query);
var jsonString = await task.Content.ReadAsStringAsync();
var value = JsonConvert.DeserializeObject<RootObject>(jsonString);
return value.value.FirstOrDefault();
}
//morestuff
}
as you can see, the deserialization is handled in the AccountService, not the AccountsController
however, if we look at the POST operation:
public class AccountController
{
[HttpPost]
[Route("", Name = "CreateAccount")]
public async Task<IHttpActionResult> CreateAccount([FromBody] JObject account)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var response = await _accountService.Create(account);
var newAccount = await response.Content.ReadAsAsync<object>();
return Ok(newAccount);
}
and the underlying Create method within the AccountService:
public async Task<HttpResponseMessage> Create(JObject account)
{
var request = new HttpRequestMessage(HttpMethod.Post, Client.HTTPCLIENT.BaseAddress + "accounts")
{
Content = new StringContent(account.ToString(), Encoding.UTF8, "application/json")
};
var response = await Client.HTTPCLIENT.SendAsync(request);
var uri = new Uri(response.Headers.GetValues("OData-EntityId").FirstOrDefault());
return await Client.HTTPCLIENT.GetAsync(uri);
}
you will see that in fact the deserialization happens on the controller level.
How can I encapsulate the deserialization logic for CRUD operations, such as GET/PUT/POST for consistency?
The aim is to make controller that uses async method in my custom service.
Controller:
[Route("api/data/summary")]
[HttpGet]
public async Task<IHttpActionResult> Get()
{
var result = await DataService.GetDataObjects();
return Ok(result);
}
Service:
public static async Task<IEnumerable<DataObject>> GetDataObjects()
{
var apiKey = "some-api-key";
var path = "path-to-external-service";
using (var client = new HttpClient())
{
var dataToProcess = // some data object
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
client.BaseAddress = new Uri(path);
HttpResponseMessage response = await client.PostAsJsonAsync("", dataToProcess);
var content = await response.Content.ReadAsStringAsync();
var result = MakeEntities(content); // some logic
return result;
}
}
But I came across with the problem that controller's action returns empty result before service actually finished processing data.
Could you please advice how to implement it correctly?
Your code is OK and controller doesn't seem to return a value before GetDataObjects returns value.
Except for the situations below:
MakeEntities uses some asynchronous operation and you don't await it inside MakeEntities. So MakeEntities return task.
Exception rises while your code is running. Make sure GetDataObjects and MakeEntities code works fine.
The aim is to make controller that uses async method in my custom service.
Controller:
[HttpGet]
[Route("api/data/summary")]
public async Task<IHttpActionResult> Get()
{
var result = await DataService.GetDataObjects().ConfigureAwait(false);
return Ok(result);
}
Service:
public static async Task<ResponseEntity> GetDataObjects()
{
ResponseEntity response = new ResponseEntity();
var apiKey = "some-api-key";
var path = "path-to-external-service";
using (var client = new HttpClient())
{
var dataToProcess = // some data object
client.BaseAddress = new Uri(path);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.PostAsJsonAsync("", dataToProcess).ConfigureAwait(false);
string responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var result = JsonConvert.DeserializeObject<ResponseEntity>(responseString);
return response;
}
}
I'm trying to come up with a way to post to a Web API controller with one object but have a different, processed, object return. None of the methods I've been able to find have solved the issue.
Here's my method in my MVC project that posts to my Web API project
public dynamic PostStuff<X>(string action, X request)
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var task = client.PostAsJsonAsync(new Uri("host/api/somecontroller/post, request);
task.Wait();
var response = task.Result;
return response;
}
}
This is my Web API controller code
public HttpResponseMessage Post([FromBody] FooObject foo)
{
var res = Request.CreateResponse(HttpStatusCode.OK,
new ValidationResponse<FooObject>
{
ID = new Random().Next(1000, 1000000),
Content =
new List<IContent>()
{
{
new Content()
{
Name = "TheContent",
Type = "SomeType",
Value = "This is some content for the page : " + foo.Bar
}
}
},
Product = new ProductFoo(),
Validated = true
});
return res;
}
}
When I put a break in my WebAPI controller code, the res variable is correctly created. Once the processing goes back to the PostStuff method, all I get is a StreamResponse with no trace of the ValidationResponse object created in the Web API controller. There are no errors but I can't use anything in the response beyond that the post succeeded. How can I extract the ValidationResponse from my posting method?
I have built myself this generic function as a helper for POSTing to Web API
Usage
var result = PostAsync<MyDataType, MyResultType>("http://...", MyData)
Function
public async Task<U> PostAsync<T, U>(string url, T model)
{
using (HttpClient httpClient = new HttpClient(httpHandler))
{
var result = await httpClient.PostAsJsonAsync<T>(url, model);
if (result.IsSuccessStatusCode == false)
{
string message = await result.Content.ReadAsStringAsync();
throw new Exception(message);
}
else
{
return await result.Content.ReadAsAsync<U>();
}
}
}
And HttpClientHandler configured for Windows Auth
protected HttpClientHandler httpHandler = new HttpClientHandler() { PreAuthenticate = true, UseDefaultCredentials = true };