How to override HttpGet/HttpPost in Asp.net MVC 5 ApiController - c#

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

Related

Attribute to apply to non-OK responses

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

API File Upload using HTTP content not exposed in swagger

I am implementing a swagger interface into an existing web API. The current API controller exposes an async upload function which uses the Request.Content to transport an image asynchronously. The code that has been used is explained in this article.
My api controller:
[HttpPost]
[Route("foo/bar/upload")]
public async Task<HttpResponseMessage> Upload()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
var provider = await Request.Content.ReadAsMultipartAsync(new InMemoryMultipartFormDataStreamProvider());
NameValueCollection formData = provider.FormData;
HttpResponseMessage response;
//access files
IList<HttpContent> files = provider.Files;
if (files.Count > 0)
{
HttpContent file1 = files[0];
using (Stream input = await file1.ReadAsStreamAsync())
{
object responseObj = ExternalProcessInputStream(input)
response = Request.CreateResponse(HttpStatusCode.OK, responseObj);
}
}
else
{
response = Request.CreateResponse(HttpStatusCode.BadRequest);
}
return response;
}
This works dandy, but when i expose this through swagger i have a parameterless function, which returns an error when used.
My question is how can supply a proper value to test this method with?
You'll need to add a custom IOperationFilter to handle this.
Given you have a controller like so:
[ValidateMimeMultipartContentFilter]
[HttpPost, Route("softwarepackage")]
public Task<SoftwarePackageModel> UploadSingleFile()
{
var streamProvider = new MultipartFormDataStreamProvider(ServerUploadFolder);
var task = Request.Content.ReadAsMultipartAsync(streamProvider).ContinueWith<SoftwarePackageModel>(t =>
{
var firstFile = streamProvider.FileData.FirstOrDefault();
if (firstFile != null)
{
// Do something with firstFile.LocalFileName
}
return new SoftwarePackageModel
{
};
});
return task;
}
You then need to create an Swashbuckle.Swagger.IOperationFilter to add a file upload parameter to your function like:
public class FileOperationFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.operationId.ToLower() == "softwarepackage_uploadsinglefile")
{
if (operation.parameters == null)
operation.parameters = new List<Parameter>(1);
else
operation.parameters.Clear();
operation.parameters.Add(new Parameter
{
name = "File",
#in = "formData",
description = "Upload software package",
required = true,
type = "file"
});
operation.consumes.Add("application/form-data");
}
}
}
And in your Swagger config you'll need to register the filter:
config.EnableSwagger(c => {... c.OperationFilter<FileOperationFilter>(); ... });
To top this up, I also added a FilterAttribute to filter out Multipart content:
public class ValidateMimeMultipartContentFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
}
}

Parameter Binding in ASP.NET Web API

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.

Return empty json on null in WebAPI

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

How to use Api key in Web Api for service authentication using forms authentication

I am using MVC 4 Web Api and I want the users to be authenticated, before using my service.
I have implemented an authorization message handler, that works just fine.
public class AuthorizationHandler : DelegatingHandler
{
private readonly AuthenticationService _authenticationService = new AuthenticationService();
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
IEnumerable<string> apiKeyHeaderValues = null;
if (request.Headers.TryGetValues("X-ApiKey", out apiKeyHeaderValues))
{
var apiKeyHeaderValue = apiKeyHeaderValues.First();
// ... your authentication logic here ...
var user = _authenticationService.GetUserByKey(new Guid(apiKeyHeaderValue));
if (user != null)
{
var userId = user.Id;
var userIdClaim = new Claim(ClaimTypes.SerialNumber, userId.ToString());
var identity = new ClaimsIdentity(new[] { userIdClaim }, "ApiKey");
var principal = new ClaimsPrincipal(identity);
Thread.CurrentPrincipal = principal;
}
}
return base.SendAsync(request, cancellationToken);
}
}
The problem is, that I use forms authentication.
[HttpPost]
public ActionResult Login(UserModel model)
{
if (ModelState.IsValid)
{
var user = _authenticationService.Login(model);
if (user != null)
{
// Add the api key to the HttpResponse???
}
return View(model);
}
return View(model);
}
When I call my api:
[Authorize]
public class TestController : ApiController
{
public string GetLists()
{
return "Weee";
}
}
The handler can not find the X-ApiKey header.
Is there a way to add the user's api key to the http response header and to keep the key there, as long as the user is logged in?
Is there another way to implement this functionality?
I found the following article http://www.asp.net/web-api/overview/working-with-http/http-cookies
Using it I configured my AuthorizationHandler to use cookies:
public class AuthorizationHandler : DelegatingHandler
{
private readonly IAuthenticationService _authenticationService = new AuthenticationService();
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var cookie = request.Headers.GetCookies(Constants.ApiKey).FirstOrDefault();
if (cookie != null)
{
var apiKey = cookie[Constants.ApiKey].Value;
try
{
var guidKey = Guid.Parse(apiKey);
var user = _authenticationService.GetUserByKey(guidKey);
if (user != null)
{
var userIdClaim = new Claim(ClaimTypes.Name, apiKey);
var identity = new ClaimsIdentity(new[] { userIdClaim }, "ApiKey");
var principal = new ClaimsPrincipal(identity);
Thread.CurrentPrincipal = principal;
}
}
catch (FormatException)
{
}
}
return base.SendAsync(request, cancellationToken);
}
}
I configured my Login action result:
[HttpPost]
public ActionResult Login(LoginModel model)
{
if (ModelState.IsValid)
{
var user = _authenticationService.Login(model);
if (user != null)
{
_cookieHelper.SetCookie(user);
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("", "Incorrect username or password");
return View(model);
}
return View(model);
}
Inside it I am using the CookieHelper, that I created. It consists of an interface:
public interface ICookieHelper
{
void SetCookie(User user);
void RemoveCookie();
Guid GetUserId();
}
And a class that implements the interface:
public class CookieHelper : ICookieHelper
{
private readonly HttpContextBase _httpContext;
public CookieHelper(HttpContextBase httpContext)
{
_httpContext = httpContext;
}
public void SetCookie(User user)
{
var cookie = new HttpCookie(Constants.ApiKey, user.UserId.ToString())
{
Expires = DateTime.UtcNow.AddDays(1)
};
_httpContext.Response.Cookies.Add(cookie);
}
public void RemoveCookie()
{
var cookie = _httpContext.Response.Cookies[Constants.ApiKey];
if (cookie != null)
{
cookie.Expires = DateTime.UtcNow.AddDays(-1);
_httpContext.Response.Cookies.Add(cookie);
}
}
public Guid GetUserId()
{
var cookie = _httpContext.Request.Cookies[Constants.ApiKey];
if (cookie != null && cookie.Value != null)
{
return Guid.Parse(cookie.Value);
}
return Guid.Empty;
}
}
By having this configuration, now I can use the Authorize attribute for my ApiControllers:
[Authorize]
public class TestController : ApiController
{
public string Get()
{
return String.Empty;
}
}
This means, that if the user is not logged in. He can not access my api and recieves a 401 error. Also I can retrieve the api key, which I use as a user ID, anywhere in my code, which makes it very clean and readable.
I do not think that using cookies is the best solution, as some user may have disabled them in their browser, but at the moment I have not found a better way to do the authorization.
From your code samples it doesn't seem like you're using Web Forms. Might you be using Forms Authentication? Are you using the Membership Provider inside your service to validate user credentials?
You can use the HttpClient class and maybe its property DefaultRequestHeaders or an HttpRequestMessage from the code that will be calling the API to set the headers.
Here there are some examples of HttpClient:
http://www.asp.net/web-api/overview/web-api-clients/calling-a-web-api-from-a-net-client

Categories

Resources