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");
}
}
Related
I have a ASP.NET Core Web API and I'm having problems receiving my parameter in my controller method. I do receive the request parameter in the RetrieveMultipleEmployees method, but the Where property is null.
The sequence is as follows:
Create the StandardRequest<Employee> with the Where property defined.
Call the RetrieveMultipleEmployeesAsync method and pass the created StandardRequest<Employee>.
The RetrieveMultipleEmployeesAsync calls the RetrieveMultipleEmployeesRoute method and passes the request along.
The RetrieveMultipleEmployees controller method gets hit, the parameter is not null but the Where property is null.
Here is what I have:
Base controller:
[ApiController]
[Route("data/v{version:apiVersion}/[controller]/{action}")]
public class BaseController<TController> : ControllerBase
{
private IMediator _mediatorInstance;
protected IMediator _mediator => _mediatorInstance ??= HttpContext.RequestServices.GetService<IMediator>();
private ILogger<TController> _loggerInstance;
protected ILogger<TController> _logger => _loggerInstance ??= HttpContext.RequestServices.GetService<ILogger<TController>>();
}
EmployeesController:
public class EmployeesController : BaseController<EmployeesController>
{
[HttpGet]
[ActionName("retrievemultipleemployees")]
public async Task<IActionResult> RetrieveMultipleEmployees([FromQuery] StandardRequest<Employee> request)
{
var response = await _mediator.Send(new EmployeeQueries.RetrieveMultipleQuery() { Request = request });
return Ok(response);
}
}
StandardRequest:
public class StandardRequest<TEntity>
{
public Expression<Func<TEntity, bool>> Where { get; set; }
}
Url:
public static string RetrieveMultipleEmployeesRoute(StandardRequest<Employee> request)
{
var url = $"data/v1/employees/retrievemultipleemployees?{request}";
return url;
}
Request:
public async Task<StandardResult<List<EmployeeModel>>> RetrieveMultipleEmployeesAsync(StandardRequest<Employee> request)
{
var response = await _httpClient.GetAsync(EmployeeRoutes.RetrieveMultipleEmployeesRoute(request));
return await response.ToStandardResultAsync<List<EmployeeModel>>();
}
Where am I going wrong? Might it be something in my API setup?
Some advise on this would be greatly appreciated.
This bit of code looks suspect:
public static string RetrieveMultipleEmployeesRoute(StandardRequest<Employee> request)
{
var url = $"data/v1/employees/retrievemultipleemployees?{request}";
return url;
}
That is simply going to call ToString() on request, resulting in something like this being returned (assuming you haven't overridden it to create an actual query string):
data/v1/employees/retrievemultipleemployees?StandardRequest`[Employee]
Which is clearly bogus. You're going to need to convert that incoming request into a proper query string using something like QueryString.Create for example.
Is recommended to put your ComplexObject into a Class Library common to both projects the Client and the API
REQUESTOR
using Newtonsoft.Json;
using System.Net.Http.Headers;
private readonly string ExtractEpridDbcisinfo = "http://localhost:5281/domething";
public void consumeAPI(){
Uri uri = new Uri(*yourBaseURI*);
client.BaseAddress = uri;
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
string sParam1= JsonConvert.SerializeObject(complexobject,typeof(ComplexObject) ,null);
HttpResponseMessage response = client.GetAsync($"?paramComplexObject={sParam1}").Result;
.
.
.
GET REST API
[HttpGet]
[Route("domething")]
public IActionResult Index(string paramComplexObject){
ComplexObject complexObject = JsonConvert.DeserializeObject<ComplexObject>(paramComplexObject);
.
.
.
I am using the following attribute [ResponseCache(Duration = 60)] to cache a specific GET Request which is called a lot on my backend in .NET Core.
Everything is working fine except the cache isn't reloaded when some data in database has changed within the 60 seconds.
Is there a specific directive I have to set to reload/update the cache? link
Example Code Snippet from my Controller:
[HttpGet]
[ResponseCache(Duration = 60)]
public ActionResult<SomeTyp[]> SendDtos()
{
var dtos = _repository.QueryAll();
return Ok(dtos);
}
There is a solution with a usage of "ETag", "If-None-Match" HTTP headers. The idea is using a code which can give us an answer to the question: "Did action response changed?".
This can be done if a controller completely owns particular data lifetime.
Create ITagProvider:
public interface ITagProvider
{
string GetETag(string tagKey);
void InvalidateETag(string tagKey);
}
Create an action filter:
public class ETagActionFilter : IActionFilter
{
private readonly ITagProvider _tagProvider;
public ETagActionFilter(ITagProvider tagProvider)
{
_tagProvider = tagProvider ?? throw new ArgumentNullException(nameof(tagProvider));
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception != null)
{
return;
}
var uri = GetActionName(context.ActionDescriptor);
var currentEtag = _tagProvider.GetETag(uri);
if (!string.IsNullOrEmpty(currentEtag))
{
context.HttpContext.Response.Headers.Add("ETag", currentEtag);
}
}
public void OnActionExecuting(ActionExecutingContext context)
{
var uri = GetActionName(context.ActionDescriptor);
var requestedEtag = context.HttpContext.Request.Headers["If-None-Match"];
var currentEtag = _tagProvider.GetETag(uri);
if (requestedEtag.Contains(currentEtag))
{
context.HttpContext.Response.Headers.Add("ETag", currentEtag);
context.Result = new StatusCodeResult(StatusCodes.Status304NotModified);
}
}
private string GetActionName(ActionDescriptor actionDescriptor)
{
return $"{actionDescriptor.RouteValues["controller"]}.{actionDescriptor.RouteValues["action"]}";
}
}
Initialize filter in Startup class:
public void ConfigureServices(IServiceCollection services)
{
// code above
services.AddMvc(options =>
{
options.Filters.Add(typeof(ETagActionFilter));
});
services.AddScoped<ETagActionFilter>();
services.AddSingleton<ITagProvider, TagProvider>();
// code below
}
Use InvalidateETag method somewhere in controllers (in the place where you modifing data):
[HttpPost]
public async Task<ActionResult> Post([FromBody] SomeType data)
{
// TODO: Modify data
// Invalidate tag
var tag = $"{controllerName}.{methodName}"
_tagProvider.InvalidateETag(tag);
return NoContent();
}
This solution may require a change of a client side. If you are using fetch, you can use, for example, the following library: https://github.com/export-mike/f-etag.
P.S. I didn't specify an implementation of the ITagProvider interface, you will need to write your own.
P.P.S. Articles about ETag and caching: https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
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
I have a code
[HttpGet]
public IHttpActionResult Search()
{
return Ok(Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(db.view_users.Take(1)))));
}
I want send to client in base64, but it hard if i must convert manually in every return.
if there a way so i can automatically convert every return to base 64, how to do that?
so i can just use natural code like this :
[HttpGet]
public IHttpActionResult Search()
{
return Ok(db.view_users.Take(1));
}
but still return the same result in base64
thanks
You can create ActionFilter like this:
public class Base64FilterAttribute : ActionFilterAttribute
{
public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
if (actionExecutedContext.Exception == null)
{
var bytes = await actionExecutedContext.Response.Content.ReadAsByteArrayAsync();
var base64 = Convert.ToBase64String(bytes);
actionExecutedContext.Response.Content = new StringContent(base64);
}
await base.OnActionExecutedAsync(actionExecutedContext, cancellationToken);
}
}
and then mark you controllers with this attribute or register as global filter.
If you are using OWIN You can also create OWIN midelware for that https://learn.microsoft.com/en-us/aspnet/aspnet/overview/owin-and-katana/.
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;
}