If I have controller GET methods, such as:
[HttpGet]
[Route("accountrecv({accountid})/promisepay", Name = "GetPromisePay")]
public HttpResponseMessage GetPromisePay(int accountid)
{
var query = Request.RequestUri.Query;
var uri = new Uri(Client.Instance.BaseAddress.ToString() + accountid + "/promisepay" + query);
var request = new HttpRequestMessage { RequestUri = uri, Method = HttpMethod.Get };
var response = Client.Instance.SendAsync(request);
return response.Result;
}
How can I impose behavior on all responses where the HttpStatusCode is NOT ok?
I imagine I would create an attribute at the method level, such as:
[NonOKResponse]
[HttpGet]
[Route.......]
public HttpResponseeMessage GetPromisePay(int accountid)
{
//my code
return response.Result //but force it here to return 404 for EVERY response other than 200
}
How can I define an attribute that I can use on all GETs to force a specific response based on some criteria?
I believe this article answers your question. You can use the ExceptionFilterAttribute to handle and manage exceptions. For example:
public class GeneralExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
context.Response = new HttpResponseMessage(HttpStatusCode.NotFound);
}
}
It sounds like you want a Result Filter.
https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/controllers-and-routing/understanding-action-filters-cs
Related
I have the following method in my BaseApiController class:
public virtual HttpResponseMessage GetById(int id)
{
var entity = repository.GetById(id);
if (entity == null)
{
var message = string.Format("No {0} with ID = {1}", GenericTypeName, id);
return ErrorMsg(HttpStatusCode.NotFound, message);
}
return Request.CreateResponse(HttpStatusCode.OK, SingleResult.Create(repository.Table.Where(t => t.ID == id)));
}
I'm using SingleResult for OData request (because $expand for single entity not works if I not create SingleResult).
But now I have problem with UnitTests of this method on concrete controller (e.g. AddressApiController). I always get NULL in result:
[TestMethod]
public void Get_By_Id()
{
//Arrange
var moq = CreateMockRepository();
var controller = new AddressApiController(moq);
controller.Request = new HttpRequestMessage()
controller.Request.SetConfiguration(new HttpConfiguration())
// Action
HttpResponseMessage response = controller.GetById(1);
var result = response.Content.ReadAsAsync<T>().Result;
// Accert
Assert.IsNotNull(result);
}
I checked and debug GetById() and find out that repository.Table.Where(t => t.ID == id)) return proper value, but after SingleResult.Create I'm getting NULL.
How can I solve this problem? How can I read content from SingleResult or using something else?
I have not had a chance to mock up an api but from the docs here:
Here are some rules for the method signatures: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-routing-conventions
Try change id to key and attribute, then you probably won't need to use SingleResult.
If the path contains a key, the action should have a parameter named key.
If the path contains a key into a navigation property, the action should have a parameter named relatedKey.
Decorate key and relatedKey parameters with the [FromODataUri] parameter.
POST and PUT requests take a parameter of the entity type.
PATCH requests take a parameter of type Delta, where T is the entity type.
I would be interested to see if that changes the test outcome.
I created extensions:
public static class HttpResponseMessageExtensions
{
public static IQueryable<T> ContentToQueryable<T>(this HttpResponseMessage response) where T : BaseEntity
{
var objContent = response.Content as ObjectContent;
return objContent?.Value as IQueryable<T>;
}
public static T ContentToEntity<T>(this HttpResponseMessage response) where T : BaseEntity
{
var objContent = response.Content as ObjectContent;
return objContent?.Value as T;
}
}
and then:
var result = response.ContentToEntity<T>();
Until now, I had a GET method that looked like the following:
protected override async Task<IHttpActionResult> GetAll(QueryData query)
{
// ... Some operations
//LINQ Expression based on the query parameters
Expression<Func<Entity, bool>> queryExpression = BuildQueryExpression(query);
//Begin to count all the entities in the repository
Task<int> countingEntities = repo.CountAsync(queryExpression);
//Reads an entity that will be the page start
Entity start = await repo.ReadAsync(query.Start);
//Reads all the entities starting from the start entity
IEnumerable<Entity> found = await repo.BrowseAllAsync(start, queryExpression);
//Truncates to page size
found = found.Take(query.Size);
//Number of entities returned in response
int count = found.Count();
//Number of total entities (without pagination)
int total = await countingEntities;
return Ok(new {
Total = total,
Count = count,
Last = count > 0 ? GetEntityKey(found.Last()) : default(Key),
Data = found.Select(e => IsResourceOwner(e) ? MapToOwnerDTO(e) : MapToDTO(e)).ToList()
});
}
This worked like a charm and it was good. However, I was told recently to send the response metadata (that is, Total, Count and Last properties) as response custom headers instead of the response body.
I cannot manage to access the Response from the ApiController. I thought of a filter or attribute, but how would I get the metadata values?
I can keep all this information on the response and then have a filter that will deserialize the response before being sent to the client, and create a new one with the headers, but that seems troublesome and bad.
Is there a way to add custom headers directly from this method on an ApiController?
You can explicitly add custom headers in a method like so:
[HttpGet]
[Route("home/students")]
public HttpResponseMessage GetStudents()
{
// Get students from Database
// Create the response
var response = Request.CreateResponse(HttpStatusCode.OK, students);
// Set headers for paging
response.Headers.Add("X-Students-Total-Count", students.Count());
return response;
}
For more information read this article: http://www.jerriepelser.com/blog/paging-in-aspnet-webapi-http-headers/
I have entered comments, here is my complete answer.
You will need to create a custom filter and apply that to your controller .
public class CustomHeaderFilter : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var count = actionExecutedContext.Request.Properties["Count"];
actionExecutedContext.Response.Content.Headers.Add("totalHeader", count);
}
}
In your Controller
public class AddressController : ApiController
{
public async Task<Address> Get()
{
Request.Properties["Count"] = "123";
}
}
Simple solution is to write just this:
HttpContext.Current.Response.Headers.Add("MaxRecords", "1000");
What you need is:
public async Task<IHttpActionResult> Get()
{
var response = Request.CreateResponse();
response.Headers.Add("Lorem", "ipsum");
return base.ResponseMessage(response);
}
I hope this answers your question.
Alternatively, it’s better to leverage DelegatingHandler if it is something you need to perform on every response. Because it will work on the request/response pipeline and not on the controller/action level. In my case I must add some headers with every response, so I did what I described. See code snippet below
public class Interceptor : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
response.Headers.Add("Access-Control-Allow-Origin", "*");
response.Headers.Add("Access-Control-Allow-Methods", "GET,POST,PATCH,DELETE,PUT,OPTIONS");
response.Headers.Add("Access-Control-Allow-Headers", "Origin, Content-Type, X-Auth-Token, content-type");
return response;
}
}
And you would be requiring to add this handler in WebApiConfig
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MessageHandlers.Add(new Interceptor());
}
}
You can use a custom ActionFilter that will allow you to send custom headers and access the HttpContext:
public class AddCustomHeaderFilter : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
actionExecutedContext.Response.Content.Headers.Add("name", "value");
}
}
I'm looking at the documentation of WebAPI 2, and i'm severely disappointed with the way the action results are architected. I really hope there is a better way.
So documentation says I can return these:
**void** Return empty 204 (No Content)
**HttpResponseMessage** Convert directly to an HTTP response message.
**IHttpActionResult** Call ExecuteAsync to create an HttpResponseMessage, then convert to an HTTP response message.
**Other type** Write the serialized return value into the response body; return 200 (OK).
I don't see a clean way to return an array of items with custom HTTP status code, custom headers and with auto negotiated content though.
What I would like to see is something like
public HttpResult<Item> Post()
{
var item = new Item();
var result = new HttpResult<Item>(item, HttpStatusCode.Created);
result.Headers.Add("header", "header value");
return result;
}
This way I can glance over a method and immediately see whats being returned, and modify status code and headers.
The closest thing I found is NegotiatedContentResult<T>, with weird signature (why does it need an instance of controller?), but there's no way to set custom headers?
Is there a better way ?
The following code should give you everything you want:
[ResponseType(typeof(Item))]
public IHttpActionResult Post()
{
var item = new Item();
HttpContext.Current.Response.AddHeader("Header-Name", "Header Value");
return Content(HttpStatusCode.Created, item);
}
... if you really need to return an array of items ...
[ResponseType(typeof(List<Item>))]
public IHttpActionResult Post()
{
var items = new List<Item>();
// Do something to fill items here...
HttpContext.Current.Response.AddHeader("Item-Count", items.Count.ToString());
return Content(HttpStatusCode.Created, items);
}
I don't think the designers of the web-api intended for controller methods to be fiddling with the headers.
The design pattern seems to be to use DelegatingHandler, ActionFilterAttribute and the ExecuteAsync overridable method of ApiController to handle authentication and response formatting.
So perhaps your logic for message content negotiation should be handled there ?
However if you definitely need to control headers from within your controller method you can do a little set-up to make it work.
To do so you can create your own DelegationHandler that forwards selected headers from your "Inner" response headers:
public class MessageHandlerBranding : DelegatingHandler {
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
//If we want to forward headers from inner content we can do this:
if (response.Content != null && response.Content.Headers.Any())
{
foreach (var hdr in response.Content.Headers)
{
var keyUpr = hdr.Key.ToUpper(); //Response will not tolerate setting of some header values
if ( keyUpr != "CONTENT-TYPE" && keyUpr != "CONTENT-LENGTH")
{
string val = hdr.Value.Any() ? hdr.Value.FirstOrDefault() : "";
response.Headers.Add(hdr.Key, val);
}
}
}
//Add our branding header to each response
response.Headers.Add("X-Powered-By", "My product");
return response;
}
}
Then you register this handler in your web-api configuration, this is usually in the GlobalConfig.cs file.
config.MessageHandlers.Add(new MessageHandlerBranding());
You could also write your own custom class for the response object like this:
public class ApiQueryResult<T> : IHttpActionResult where T : class
{
public ApiQueryResult(HttpRequestMessage request)
{
this.StatusCode = HttpStatusCode.OK; ;
this.HeadersToAdd = new List<MyStringPair>();
this.Request = request;
}
public HttpStatusCode StatusCode { get; set; }
private List<MyStringPair> HeadersToAdd { get; set; }
public T Content { get; set; }
private HttpRequestMessage Request { get; set; }
public void AddHeaders(string headerKey, string headerValue)
{
this.HeadersToAdd.Add(new MyStringPair(headerKey, headerValue));
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = this.Request.CreateResponse<T>(this.StatusCode, this.Content);
foreach (var hdr in this.HeadersToAdd)
{
response.Content.Headers.Add(hdr.key, hdr.value);
}
return Task.FromResult(response);
}
private class MyStringPair
{
public MyStringPair(string key, string value)
{
this.key = key;
this.value = value;
}
public string key;
public string value;
}
}
And use it like this in your controller:
[HttpGet]
public ApiQueryResult<CustomersView> CustomersViewsRow(int id)
{
var ret = new ApiQueryResult<CustomersView>(this.Request);
ret.Content = this.BLL.GetOneCustomer(id);
ret.AddHeaders("myCustomHkey","myCustomValue");
return ret;
}
I have a DelegatingHandler in my web API for authentification (HMAC).
I would like to add a GET parameter to the request to return the user's id to my controller.
In my handler, I tried adding it like so:
public class SecurityHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string apikey = request.Headers.GetValues(AuthConfig.ApiKeyHeader).First();
UserDTO user = UserRepo.GetUser(apikey);
if (user == null)
{
return SendResponseText("Invalid API key");
}
// Validate signature ...
// Add user Id to the URI
request.RequestUri = new Uri(request.RequestUri.OriginalString + "&UserId=" + user.Id);
return base.SendAsync(request, cancellationToken);
}
}
In my controller, I'm able to get the newly added parameter from the request uri, however the parameter binding is not working
public class MyModel
{
public int UserId { get; set; }
...
}
public string Get([FromUri] MyModel model)
{
// model.UserId has not been set (= 0)
// Request.RequestUri contains "&UserId=5"
}
Update
I'm guessing the binding is being done from the Params in the HttpContext. I tried something like this, but Params collection is readonly.
var newContext = request.Properties["MS_HttpContext"] as HttpContextWrapper;
newContext.Request.Params.Add("UserId", "8");
request.Properties.Remove("MS_HttpContext");
request.Properties.Add("MS_HttpContext", newContext);
I have tried at my side and it working.
Here is my sample url.
http://localhost:50382/api/values?UserId=10
Here is controller action.
public class ValueController : ApiController
{
public IEnumerable<string> Get([FromUri]Model my)
{
return new string[] { "value1", "value2" , my.UserId.ToString() };
}
}
As per your comment here I created delegate handler.
public class MyMessageHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var myTempObj = new { id = 20 };
/* You have to check this and remove this key. If key present that FromUri use cached properties*/
if (request.Properties.ContainsKey("MS_QueryNameValuePairs"))
{
request.Properties.Remove("MS_QueryNameValuePairs");
}
// Now prepare or update uri over here. It will surely work now.
if (!string.IsNullOrEmpty(request.RequestUri.Query) && request.RequestUri.Query.Contains('?'))
{
request.RequestUri = new Uri(request.RequestUri + "&UserID=" + myTempObj.id);
}
else
{
request.RequestUri = new Uri(request.RequestUri + "?UserID=" + myTempObj.id);
}
return base.SendAsync(request, cancellationToken);
}
}
It is work as expected.
If your original request contain userid then it get duplicated and it will not work. It return 0.
I also have doubt that you are using request.RequestUri.OriginalString. Just try to use request.RequestUri. It might possible that OriginalString has encoded value.
I found the following solution to my problem
request.RequestUri = new Uri(request.RequestUri + "&UserId=8");
var newFormDataCollection = new FormDataCollection(request.RequestUri);
request.Properties.Remove("MS_QueryNameValuePairs");
request.Properties.Add("MS_QueryNameValuePairs", newFormDataCollection);
return base.SendAsync(request, cancellationToken);
[FromURI] seems to use the values in the FormDataCollection, so you simply need to update the "MS_QueryNameValuePairs" property.
I would like to override the API Controller to check for certain values in the header on all HttpGet and HttpPost calls as they're made without including the code for the check in every single call. Currently my method looks like:
public class MyApiController : ApiController
{
[HttpGet]
public HttpResponseMessage GetAccountById()
{
var accountId = (Request.Headers.Where(t => t.Key == "accountid").Count() == 0) ? null : Request.Headers.GetValues("accountid").First();
var apiKey = (Request.Headers.Where(t => t.Key == "apikey").Count() == 0) ? null : Request.Headers.GetValues("apikey").First();
if (String.IsNullOrEmpty(accountId)) {
return Request.CreateResponse(HttpStatusCode.Forbidden, "Please provide an Account Id.");
}
if (String.IsNullOrEmpty(apiKey)) {
return Request.CreateResponse(HttpStatusCode.Forbidden, "Please provide an Account Api Key.");
}
// Get Account
// return Account;
}
}
How can I do that apikey/accountid check in every call without having to write the check into every call?
SOLUTION: Overriding the DelegatingHandler worked perfectly.
ApiSecurityHandler.cs
public class ApiSecurityHandler : DelegatingHandler
{
public ApiSecurityHandler(HttpConfiguration httpConfiguration)
{
InnerHandler = new HttpControllerDispatcher(httpConfiguration);
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var accountId = (request.Headers.Where(t => t.Key == "accountid").Count() == 0) ? null : request.Headers.GetValues("accountid").First();
var apiKey = (request.Headers.Where(t => t.Key == "apikey").Count() == 0) ? null : request.Headers.GetValues("apikey").First();
if (String.IsNullOrEmpty(accountId)) {
var response = new HttpResponseMessage(HttpStatusCode.Forbidden);
response.Content = new StringContent("Please provide an Account Id.");
var tsc = new TaskCompletionSource<HttpResponseMessage>();
tsc.SetResult(response);
return tsc.Task;
}
if (String.IsNullOrEmpty(apiKey)) {
var response = new HttpResponseMessage(HttpStatusCode.Forbidden);
response.Content = new StringContent("Please provide an Account Api Key.");
var tsc = new TaskCompletionSource<HttpResponseMessage>();
tsc.SetResult(response);
return tsc.Task;
}
// Authorize the Account Id and Api Key here
using (var accountManager = new AccountManager()) {
if (!accountManager.AuthorizeAccountApiKey(accountId, apiKey)) {
var response = new HttpResponseMessage(HttpStatusCode.Forbidden);
response.Content = new StringContent("Api authorization denied.");
var tsc = new TaskCompletionSource<HttpResponseMessage>();
tsc.SetResult(response);
return tsc.Task;
}
}
return base.SendAsync(request, cancellationToken);
}
}
And the in your routing config just add this parameter to the map route:
handler: new ApiSecurityHandler(GlobalConfiguration.Configuration)
I would recommend the use of DelegatingHandler.
This example from msdn show how to override your Header
This code should work, enjoy:
public class Myhandler: DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessagerequest, CancellationToken cancellationToken)
{
if(request.Headers.Contains("accountid") && request.Headers.Contains("apikey"))
{
string accountid = request.Headers.GetValues("accountid").FirstOrDefault();
string apikey = request.Headers.GetValues("apikey").FirstOrDefault();
//HERE you can get your account and do what you want
}else{
return SendError("please provide account id and api key", HttpStatusCode.Forbidden);
}
return base.SendAsync(request, cancellationToken);
}
private Task<HttpResponseMessage> SendError(string error, HttpStatusCode code)
{
var response = new HttpResponseMessage();
response.Content = new StringContent(error);
response.StatusCode = code;
return Task<HttpResponseMessage>.Factory.StartNew(() => response);
}
}
one more example of DelegatingHandler
More examples
Write yourself a subclass of System.Web.Http.Filters.AuthorizationFilterAttribute, with your own implementation of OnAuthorization then you can use this attribute on individual controller methods or on the controller itself.
Here's an example of someone doing exactly this.
What I would do, instead of overriding ApiController is, create a base class that inherits ApiController, and do your coding there. Like this:
public class APIBaseController : ApiController
{
[HttpGet]
public void APIBaseController() {
//Request.Headers.Count()
}
[HttpPost]
public void APIBaseController() {
//Request.Headers.Count()
}
}
And then do this:
public class MyApiController : APIBaseController