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.
Related
I'm trying to pass a query string into a BaseAddress but it doesn't recognize the quotation mark "?".
The quotation breaks the URI
First I create my BaseAddress
httpClient.BaseAddress = new Uri($"https://api.openweathermap.org/data/2.5/weather?appid={Key}/");
Then I call the GetAsync method, trying to add another parameter
using (var response = await ApiHelper.httpClient.GetAsync("&q=mexico"))....
This is the URI the code is calling
https://api.openweathermap.org/data/2.5/&q=mexico
I'd be tempted to use a DelegatingHandler if you need to apply an API key to every single request:
private class KeyHandler : DelegatingHandler
{
private readonly string _escapedKey;
public KeyHandler(string key) : this(new HttpClientHandler(), key)
{
}
public KeyHandler(HttpMessageHandler innerHandler, string key) : base(innerHandler)
{
// escape the key since it might contain invalid characters
_escapedKey = Uri.EscapeDataString(key);
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// we'll use the UriBuilder to parse and modify the url
var uriBuilder = new UriBuilder(request.RequestUri);
// when the query string is empty, we simply want to set the appid query parameter
if (string.IsNullOrEmpty(uriBuilder.Query))
{
uriBuilder.Query = $"appid={_escapedKey}";
}
// otherwise we want to append it
else
{
uriBuilder.Query = $"{uriBuilder.Query}&appid={_escapedKey}";
}
// replace the uri in the request object
request.RequestUri = uriBuilder.Uri;
// make the request as normal
return base.SendAsync(request, cancellationToken);
}
}
Usage:
httpClient = new HttpClient(new KeyHandler(Key));
httpClient.BaseAddress = new Uri($"https://api.openweathermap.org/data/2.5/weather");
// since the logic of adding/appending the appid is done based on what's in
// the query string, you can simply write `?q=mexico` here, instead of `&q=mexico`
using (var response = await ApiHelper.httpClient.GetAsync("?q=mexico"))
** Note: If you're using ASP.NET Core, you should call services.AddHttpClient() and then use IHttpHandlerFactory to generate the inner handler for KeyHandler.
This is how I work around it:
Http client impl:
namespace StocksApi2.httpClients
{
public interface IAlphavantageClient
{
Task<string> GetSymboleDetailes(string queryToAppend);
}
public class AlphavantageClient : IAlphavantageClient
{
private readonly HttpClient _client;
public AlphavantageClient(HttpClient httpClient)
{
httpClient.BaseAddress = new Uri("https://www.alphavantage.co/query?apikey=<REPLACE WITH YOUR TOKEN>&");
httpClient.DefaultRequestHeaders.Add("Accept", "application/json; charset=utf-8");
httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
_client = httpClient;
}
public async Task<string> GetSymboleDetailes(string queryToAppend)
{
_client.BaseAddress = new Uri(_client.BaseAddress + queryToAppend);
return await _client.GetStringAsync("");
}
}
}
Controller:
namespace StocksApi2.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class SymbolDetailsController : ControllerBase
{
private readonly IAlphavantageClient _client;
public SymbolDetailsController(IAlphavantageClient client)
{
_client = client;
}
[HttpGet]
public async Task<ActionResult> Get([FromQuery]string function = "TIME_SERIES_INTRADAY",
[FromQuery]string symbol = "MSFT", [FromQuery]string interval = "5min")
{
try {
string query = $"function={function}&symbol={symbol}&interval={interval}";
string result = await _client.GetSymboleDetailes(query);
return Ok(result);
}catch(Exception e)
{
return NotFound("Error: " + e);
}
}
}
}
And in Startup.cs inside ConfigureServices:
services.AddHttpClient();
services.AddHttpClient<IAlphavantageClient, AlphavantageClient>();
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'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;
}
Is it possible to return { } instead of null when webApi returns a null object?
This, to prevent my user from getting errors while parsing the response. And to make the response a valid Json Response?
I know that i could be setting it everywhere manually. That when null is the response, an empty Json object should be returned. But, is there a way to do it automaticly for every response?
If you are building a RESTful service, and have nothing to return from the resource, I believe that it would be more correct to return 404 (Not Found) than a 200 (OK) response with an empty body.
You can use a HttpMessageHandler to perform behaviour on all requests. The example below is one way to do it. Be warned though, I whipped this up very quickly and it probably has a bunch of edge case bugs, but it should give you the idea of how it can be done.
public class NullJsonHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (response.Content == null)
{
response.Content = new StringContent("{}");
} else if (response.Content is ObjectContent)
{
var objectContent = (ObjectContent) response.Content;
if (objectContent.Value == null)
{
response.Content = new StringContent("{}");
}
}
return response;
}
}
You can enable this handler by doing,
config.MessageHandlers.Add(new NullJsonHandler());
Thanks to Darrel Miller, I for now use this solution.
WebApi messes with StringContent "{}" again in some environment, so serialize through HttpContent.
/// <summary>
/// Sends HTTP content as JSON
/// </summary>
/// <remarks>Thanks to Darrel Miller</remarks>
/// <seealso cref="http://www.bizcoder.com/returning-raw-json-content-from-asp-net-web-api"/>
public class JsonContent : HttpContent
{
private readonly JToken jToken;
public JsonContent(String json) { jToken = JObject.Parse(json); }
public JsonContent(JToken value)
{
jToken = value;
Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
var jw = new JsonTextWriter(new StreamWriter(stream))
{
Formatting = Formatting.Indented
};
jToken.WriteTo(jw);
jw.Flush();
return Task.FromResult<object>(null);
}
protected override bool TryComputeLength(out long length)
{
length = -1;
return false;
}
}
Derived from OkResult to take advantage Ok() in ApiController
public class OkJsonPatchResult : OkResult
{
readonly MediaTypeWithQualityHeaderValue acceptJson = new MediaTypeWithQualityHeaderValue("application/json");
public OkJsonPatchResult(HttpRequestMessage request) : base(request) { }
public OkJsonPatchResult(ApiController controller) : base(controller) { }
public override Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var accept = Request.Headers.Accept;
var jsonFormat = accept.Any(h => h.Equals(acceptJson));
if (jsonFormat)
{
return Task.FromResult(ExecuteResult());
}
else
{
return base.ExecuteAsync(cancellationToken);
}
}
public HttpResponseMessage ExecuteResult()
{
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new JsonContent("{}"),
RequestMessage = Request
};
}
}
Override Ok() in ApiController
public class BaseApiController : ApiController
{
protected override OkResult Ok()
{
return new OkJsonPatchResult(this);
}
}
Maybe better solution is using Custom Message Handler.
A delegating handler can also skip the inner handler and directly
create the response.
Custom Message Handler:
public class NullJsonHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var updatedResponse = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = null
};
var response = await base.SendAsync(request, cancellationToken);
if (response.Content == null)
{
response.Content = new StringContent("{}");
}
else if (response.Content is ObjectContent)
{
var contents = await response.Content.ReadAsStringAsync();
if (contents.Contains("null"))
{
contents = contents.Replace("null", "{}");
}
updatedResponse.Content = new StringContent(contents,Encoding.UTF8,"application/json");
}
var tsc = new TaskCompletionSource<HttpResponseMessage>();
tsc.SetResult(updatedResponse);
return await tsc.Task;
}
}
Register the Handler:
In Global.asax file inside Application_Start() method register your Handler by adding below code.
GlobalConfiguration.Configuration.MessageHandlers.Add(new NullJsonHandler());
Now all the Asp.NET Web API Response which contains null will be replaced with empty Json body {}.
References:
- https://stackoverflow.com/a/22764608/2218697
- https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/http-message-handlers
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