I have ported an existing web api project - which has been run using IIS - to OWIN (selfhosted). I´m using dependency injection (unity) and have implemented a service which needs some information from the current request´s header (i.e. var x = HttpContext.Current.Request.Headers["xxx"]).
Since HttpContext is not available in OWIN (which makes sense) - how can I get the current request? Please keep in mind that I do need this information inside an injected service (not inside a controller and an OWIN middleware module - owincontext).
Your controller should inherit from ApiController, which has a Request property that will be populated for each request. So from within your controller action just use Request.Headers[...].
Create a sample class like below
public class HeaderParser
{
IDictionary<string, object> _requestContext;
IDictionary<string, string[]> _headers;
public HeaderParser(IDictionary<string, object> requestContext)
{
_requestContext = requestContext;
_headers = requestContext["owin.RequestHeaders"] as IDictionary<string, string[]>;
}
public string GetEmployeeNoFromHeader()
{
if (_headers != null && _headers.ContainsKey("X-EmployeeNo") && _headers["X-EmployeeNo"] != null && _headers["X-EmployeeNo"].Length > 0)
{
return _headers["X-EmployeeNo"][0];
}
else
{
var response = new HttpResponseMessage(HttpStatusCode.BadRequest);
response.Content = new StringContent("EMPLOYEE NO NOT AVAILABLE IN REQUEST");
throw new HttpResponseException(response);
}
}
}
In the controller something like below should work
var owincontext = request.GetOwinContext().Environment;
var headerParser= new HeaderParser(owincontext);
headerParser.GetEmployeeNoFromHeader()
What we have done is we have implemented interface IHttpControllerActivator.Create like below, so it runs for all controller class, The controller is generated by dependency injection windsor castle
public IHttpController Create(
HttpRequestMessage request,
HttpControllerDescriptor controllerDescriptor,
Type controllerType)
{
var owincontext = request.GetOwinContext().Environment;
var headerParser= new HeaderParser(owincontext);
var logger = _dpendencyManager.Resolve(typeof(IPOSLogger)) as IPOSLogger;
var executionContext = new ExecutionContext(logger, owincontext,headerParser.GetEmployeeNoFromHeader());
var controller =
(IHttpController)_dpendencyManager.Resolve(controllerType, new { context = executionContext });
//var controller =
// (IHttpController)_dpendencyManager.Resolve(controllerType);
request.RegisterForDispose(
new Release(
() => _dpendencyManager.Release(controller)));
return controller;
}
Related
I have a ASP.NET Core MVC API with controllers that need to be unit tested.
Controller:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace TransitApi.Api.Controllers
{
[Route("api/foo")]
public class FooController : Controller
{
private IFooRepository FooRepository { get; }
public FooController(IFooRepository fooRepository)
{
FooRepository = fooRepository;
}
[HttpGet]
[Authorize("scopes:getfoos")]
public async Task<IActionResult> GetAsync()
{
var foos = await FooRepository.GetAsync();
return Json(foos);
}
}
}
It is essential that I am able to unit test the effectiveness of the AuthorizeAttribute. We have had issues in our code base with missing attributes and incorrect scopes. This answer is exactly what I am looking for, but not having a ActionInvoker method in Microsoft.AspNetCore.Mvc.Controller means I am not able to do it this way.
Unit Test:
[Fact]
public void GetAsync_InvalidScope_ReturnsUnauthorizedResult()
{
// Arrange
var fooRepository = new StubFooRepository();
var controller = new FooController(fooRepository)
{
ControllerContext = new ControllerContext
{
HttpContext = new FakeHttpContext()
// User unfortunately not available in HttpContext
//,User = new User() { Scopes = "none" }
}
};
// Act
var result = controller.GetAsync().Result;
// Assert
Assert.IsType<UnauthorizedResult>(result);
}
How can I unit test that users without the correct scopes are denied access to my controller method?
Currently I have settled for testing merely the presence of an AuthorizeAttribute as follows, but this is really not good enough:
[Fact]
public void GetAsync_Analysis_HasAuthorizeAttribute()
{
// Arrange
var fooRepository = new StubFooRepository();
var controller = new FooController(fooRepository)
{
ControllerContext = new ControllerContext
{
HttpContext = new FakeHttpContext()
}
};
// Act
var type = controller.GetType();
var methodInfo = type.GetMethod("GetAsync", new Type[] { });
var attributes = methodInfo.GetCustomAttributes(typeof(AuthorizeAttribute), true);
// Assert
Assert.True(attributes.Any());
}
This would need integration testing with an in-memory test server because the attribute is evaluated by the framework as it processes the request pipeline.
Integration testing in ASP.NET Core
Integration testing ensures that an application's components function correctly when assembled together. ASP.NET Core supports integration testing using unit test frameworks and a built-in test web host that can be used to handle requests without network overhead.
[Fact]
public async Task GetAsync_InvalidScope_ReturnsUnauthorizedResult() {
// Arrange
var server = new TestServer(new WebHostBuilder().UseStartup<Startup>());
var client = server.CreateClient();
var url = "api/foo";
var expected = HttpStatusCode.Unauthorized;
// Act
var response = await client.GetAsync(url);
// Assert
Assert.AreEqual(expected, response.StatusCode);
}
You can also create a start up specifically for the test that will replace any dependencies for DI with stubs/mocks if you do not want the test hitting actual production implementations.
What you could do, is to configure your testserver to add an anonymous filter middleware:
private HttpClient CreatControllerClient()
{
return _factory.WithWebHostBuilder(builder
=> builder.ConfigureTestServices(services =>
{
// allow anonymous access to bypass authorization
services.AddMvc(opt => opt.Filters.Add(new AllowAnonymousFilter()));
})).CreateClient();
}
First remeove IAuthorizationHandler
var authorizationDescriptor = services.FirstOrDefault(d => d.ServiceType == typeof(IAuthorizationHandler));
if (authorizationDescriptor != null)
services.Remove(authorizationDescriptor);
Then add
services.AddScoped<IAuthorizationHandler, TestAllowAnonymous>();
public class TestAllowAnonymous : IAuthorizationHandler
{
public Task HandleAsync(AuthorizationHandlerContext context)
{
foreach (IAuthorizationRequirement requirement in context.PendingRequirements.ToList())
context.Succeed(requirement); //Simply pass all requirementsreturn Task.CompletedTask;
return Task.CompletedTask;
}
}
I have a MVC 5 Web API which returns a custom response in case of unexpected exceptions or if the controller or action were not found. Essentially, I've done exactly as shown there: http://weblogs.asp.net/imranbaloch/handling-http-404-error-in-asp-net-web-api Everything's working like a charm.
The problem is: I'd like to submit the error code from SelectController() and SelectAction() to my ErrorController. This way I would not have duplicate code and all the logic would be in the controller.
Unfortunately, I do not find any possible way to submit the error code to my controller. All the examples are redirecting to a specific error action (e.g. ErrorController.NotFound404) I'd like to redirect to ErrorController.Main and do all the magic there.
Another issue with the custom ApiControllerActionSelector is that the Request property is null in the ErrorController. This problem does not exist with the custom DefaultHttpControllerSelector.
Any ideas?
Best regards,
Carsten
Fortunately, I was able to find the solution myself. Let me show you how I got it up and running.
The custom controller and action selector are forwarding the requested language and the current HTTP response code:
public class CustomDefaultHttpControllerSelector: DefaultHttpControllerSelector
{
public CustomDefaultHttpControllerSelector(HttpConfiguration configuration) : base(configuration)
{
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
HttpControllerDescriptor descriptor = null;
try
{
descriptor = base.SelectController(request);
}
catch (HttpResponseException e)
{
var routeValues = request.GetRouteData().Values;
routeValues.Clear();
routeValues["controller"] = "Error";
routeValues["action"] = "Main";
routeValues["code"] = e.Response.StatusCode;
routeValues["language"] = request.Headers?.AcceptLanguage?.FirstOrDefault()?.Value ?? "en";
descriptor = base.SelectController(request);
}
return descriptor;
}
}
public class CustomControllerActionSelector: ApiControllerActionSelector
{
public CustomControllerActionSelector()
{
}
public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
{
HttpActionDescriptor descriptor = null;
try
{
descriptor = base.SelectAction(controllerContext);
}
catch (HttpResponseException e)
{
var routeData = controllerContext.RouteData;
routeData.Values.Clear();
routeData.Values["action"] = "Main";
routeData.Values["code"] = e.Response.StatusCode;
routeData.Values["language"] = controllerContext.Request?.Headers?.AcceptLanguage?.FirstOrDefault()?.Value ?? "en";
IHttpController httpController = new ErrorController();
controllerContext.Controller = httpController;
controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "Error", httpController.GetType());
descriptor = base.SelectAction(controllerContext);
}
return descriptor;
}
}
Two important changes:
1.1. The list of route values needs to be cleared. Otherwise it tries to find an action in the ErrorController which maps to this list of values.
1.2. The code and language were added.
The ErrorController itself:
[RoutePrefix("error")]
public class ErrorController: BaseController
{
[HttpGet, HttpPost, HttpPut, HttpDelete, HttpHead, HttpOptions, AcceptVerbs("PATCH")]
[Route("{code}/{language}")]
public HttpResponseMessage Main(string code, string language)
{
HttpStatusCode parsedCode;
var responseMessage = new HttpResponseMessage();
if (!Enum.TryParse(code, true, out parsedCode))
{
parsedCode = HttpStatusCode.InternalServerError;
}
responseMessage.StatusCode = parsedCode;
...
}
}
I've removed the route mapping routes.MapHttpRoute(...). No matter what I've entered in the browser, it never called Handle404.
HTTP status 400 (bad request) was not covered, yet. This could be easily achieved by using the ValidationModelAttribute as described on http://www.asp.net/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api (section "Handling Validation Errors").
Maybe this will help someone...
I have an MVC5 project, from the MVC controller I need to call the Web API method. The Web API uses token based authentication, so I have to pass the token for each call. I am using the code below to pass the token in the HTTP header:
HttpClient httpClient = new HttpClient();
string baseUrl = "http://localhost:60477/";
dynamic token = Session["token"];
if (token.AccessToken != null)
{
httpClient.DefaultRequestHeaders.Add("Authorization", String.Format("Bearer {0}", token.AccessToken));
}
There are multiple action methods in my controller, and I want to use a single HttpClient and headers, added in one place, instead of adding a header in each and every action method.
Where can I place the HttpClient headers registration code in the MVC application, so it can be common to all controllers? That means I don't want to repeat code, like adding the token in each and every action method. How can I do that?
Public ActionResult Postuser(UserModel user)
{
// post code
}
Public ActionResult getuser(UserModel user)
{
HttpResponseMessage response = httpClient.GetAsync(baseUrl + "api/Admin/GetStates").Result;
if (response.IsSuccessStatusCode)
{
string stateInfo = response.Content.ReadAsStringAsync().Result;
}
}
Public ActionResult PostRoles(RoleModel role)
{
// post roles code
}
You can try creating a small helper class for creating your httpclient object. Something like
public class HttpClientHelper
{
public static HttpClient GetHttpClient()
{
var MyHttpClient = new HttpClient();
dynamic _token = HttpContext.Current.Session["token"];
if (_token == null) throw new ArgumentNullException(nameof(_token));
MyHttpClient.DefaultRequestHeaders.Add("Authorization", String.Format("Bearer {0}", _token.AccessToken));
return MyHttpClient;
}
}
and then call it in your controllers as
public ActionResult getuser(UserModel user)
{
var httpClient = HttpClientHelper.GetHttpClient();
HttpResponseMessage response = httpClient.GetAsync(baseUrl + "api/Admin/GetStates").Result;
if (response.IsSuccessStatusCode)
{
string stateInfo = response.Content.ReadAsStringAsync().Result;
}
}
It is better to adhere to the Single Responsibility Principle and extract the interaction with another service in a it's own class, e.g.
public class ServiceClient : IServiceClient
{
private HttpClient m_Client;
public ServiceClient
{
m_Client = new HttpClient();
// Initialize the client as you need here
}
public void CallSomeMethod()
{
// Call method on the client
}
}
Then you inject the IServiceClient in your controller and just call it's methods. If you do not use injection (which I advise you do) you can just create a new instance in the controller's constructor.
You can try using an action filter in your controller. Try adding an override that looks something like this-
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// some condition code to target a specific method in the controller
// Example
if (filterContext.ActionDescriptor.ActionName == "getuser") // <-- your method
{
// put your token based authentication code here
}
base.OnActionExecuting(filterContext);
}
The OnActionExecuting method is at the controller scope so you can have different logic for different controllers.
There's also an OnActionExecuted method override if you want to run code after your action method.
------edit--------------
As far as where to place your HttpClient code snippet, you can try this-
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpClient httpClient = new HttpClient();
string baseUrl = "http://localhost:60477/";
dynamic token = Session["token"];
if (token.AccessToken != null)
{
httpClient.DefaultRequestHeaders.Add(
"Authorization",
string.Format("Bearer {0}", token.AccessToken)
);
httpClient.BaseAddress = new Uri(baseUrl);
}
if(filterContext.ActionParameters.ContainsKey("httpClient"))
{
filterContext.ActionParameters["httpClient"] = httpClient;
}
else
{
// error
}
base.OnActionExecuting(filterContext);
}
So the HttpClient object along with the assignment of your baseUrl is established in OnActionExecuting. This code will run before any method returning a ActionResult in the controller you are refactoring. If you want to target some and not all methods, see the first example of OnActionExecuting above.
public ActionResult getuser(UserModel user, HttpClient httpClient)
{
HttpResponseMessage response = httpClient.GetAsync("api/Admin/GetStates").Result;
if(response.IsSuccessStatusCode)
{
string stateInfo = response.Content.ReadAsStringAsync().Result;
}
// the rest of your code for getuser..
return View();
}
Now your getuser method has an extra parameter ( HttpClient httpClient ).
why don't you move the code in Global asax or create custom Atribute?
here is one good link:
http://www.diaryofaninja.com/blog/2011/07/24/writing-your-own-custom-aspnet-mvc-authorize-attributes
I am trying to add various Web API plugins to my MVC website. Copying and pasting DLLs of API files allow me to call appropriate service methods using the default route - hostname/api/{controller}/{id}, but it throws an error, when I have controllers with the same name but in different locations (DLLs, namespaces). The error message is something like this (which is normal):
Multiple types were found that match the controller named 'Names'. This can happen if the route that services this request ('api/{controller}/{id}') found multiple controllers defined with the same name but differing namespaces, which is not supported. The request for 'Names' has found the following matching controllers: ApiExt.NamesController ApiExt1.NamesController
I have the same "Names" controller in different DLLs (namespaces) ApiExt, ApiExt1.
I already found a similar topic to select controller depending of API version - http://shazwazza.com/post/multiple-webapi-controllers-with-the-same-name-but-different-namespaces/, but this is not quite what I need. I need to select the controller (namespace) depending on route value, something like this:
hostname/api/{namespace}/{controller}/{id}
I believe this is absolutely possible, but I'm not familiar with overriding MVC controller selector (implementing IHttpControllerSelector).
Any suggestions?
Thank you.
You can definitely achieve this. You need to write your own Controller Selector by implementing IHttpControllerSelector. Please refer to this link for detailed, step-by-step explanation.
The blog post describing a solution, https://blogs.msdn.microsoft.com/webdev/2013/03/07/asp-net-web-api-using-namespaces-to-version-web-apis/, doesnt contain the complete code and has a bad link to what used to provide it.
Here's a blob providing the class, original from Umbraco
https://github.com/WebApiContrib/WebAPIContrib/blob/master/src/WebApiContrib/Selectors/NamespaceHttpControllerSelector.cs,
Full listing in case it's removed:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
namespace WebApiContrib.Selectors
{
//originally created for Umbraco https://github.com/umbraco/Umbraco-CMS/blob/7.2.0/src/Umbraco.Web/WebApi/NamespaceHttpControllerSelector.cs
//adapted from there, does not recreate HttpControllerDescriptors, instead caches them
public class NamespaceHttpControllerSelector : DefaultHttpControllerSelector
{
private const string ControllerKey = "controller";
private readonly HttpConfiguration _configuration;
private readonly Lazy<HashSet<NamespacedHttpControllerMetadata>> _duplicateControllerTypes;
public NamespaceHttpControllerSelector(HttpConfiguration configuration)
: base(configuration)
{
_configuration = configuration;
_duplicateControllerTypes = new Lazy<HashSet<NamespacedHttpControllerMetadata>>(InitializeNamespacedHttpControllerMetadata);
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
var routeData = request.GetRouteData();
if (routeData == null || routeData.Route == null || routeData.Route.DataTokens["Namespaces"] == null)
return base.SelectController(request);
// Look up controller in route data
object controllerName;
routeData.Values.TryGetValue(ControllerKey, out controllerName);
var controllerNameAsString = controllerName as string;
if (controllerNameAsString == null)
return base.SelectController(request);
//get the currently cached default controllers - this will not contain duplicate controllers found so if
// this controller is found in the underlying cache we don't need to do anything
var map = base.GetControllerMapping();
if (map.ContainsKey(controllerNameAsString))
return base.SelectController(request);
//the cache does not contain this controller because it's most likely a duplicate,
// so we need to sort this out ourselves and we can only do that if the namespace token
// is formatted correctly.
var namespaces = routeData.Route.DataTokens["Namespaces"] as IEnumerable<string>;
if (namespaces == null)
return base.SelectController(request);
//see if this is in our cache
var found = _duplicateControllerTypes.Value.FirstOrDefault(x => string.Equals(x.ControllerName, controllerNameAsString, StringComparison.OrdinalIgnoreCase) && namespaces.Contains(x.ControllerNamespace));
if (found == null)
return base.SelectController(request);
return found.Descriptor;
}
private HashSet<NamespacedHttpControllerMetadata> InitializeNamespacedHttpControllerMetadata()
{
var assembliesResolver = _configuration.Services.GetAssembliesResolver();
var controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();
var controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);
var groupedByName = controllerTypes.GroupBy(
t => t.Name.Substring(0, t.Name.Length - ControllerSuffix.Length),
StringComparer.OrdinalIgnoreCase).Where(x => x.Count() > 1);
var duplicateControllers = groupedByName.ToDictionary(
g => g.Key,
g => g.ToLookup(t => t.Namespace ?? String.Empty, StringComparer.OrdinalIgnoreCase),
StringComparer.OrdinalIgnoreCase);
var result = new HashSet<NamespacedHttpControllerMetadata>();
foreach (var controllerTypeGroup in duplicateControllers)
{
foreach (var controllerType in controllerTypeGroup.Value.SelectMany(controllerTypesGrouping => controllerTypesGrouping))
{
result.Add(new NamespacedHttpControllerMetadata(controllerTypeGroup.Key, controllerType.Namespace,
new HttpControllerDescriptor(_configuration, controllerTypeGroup.Key, controllerType)));
}
}
return result;
}
private class NamespacedHttpControllerMetadata
{
private readonly string _controllerName;
private readonly string _controllerNamespace;
private readonly HttpControllerDescriptor _descriptor;
public NamespacedHttpControllerMetadata(string controllerName, string controllerNamespace, HttpControllerDescriptor descriptor)
{
_controllerName = controllerName;
_controllerNamespace = controllerNamespace;
_descriptor = descriptor;
}
public string ControllerName
{
get { return _controllerName; }
}
public string ControllerNamespace
{
get { return _controllerNamespace; }
}
public HttpControllerDescriptor Descriptor
{
get { return _descriptor; }
}
}
}
}
Then just add the Namespaces token to the route
route.DataTokens["Namespaces"] = new string[] {"Foo.Controllers"};
It's also more production ready with the caching.
I'm building dynamic routing system in Web API service.
For example, service might have several routes (I don't know exact count and prefixes on application start). I want to create some custom route that will decide which route to choose (some kind of internal redirection to another route), initialize it on the first request and pass the request to it.
I've tried to inherit from HttpRoute and override GetRouteData, but for some reason after changing the route in IHttpRouteData server returns me 406 Not Accepted status code. Simple example with only one route for substitution:
public static void Register(HttpConfiguration config) // on Application_Start
{
WebApiConfig.Configuration.Routes.Add("InitRoute", new LazyInitializationRoute("myservice/{*params}"));
}
public class LazyInitializationRoute : HttpRoute
{
private static bool _initialized = false;
public LazyInitializationRoute(string routeTemplate)
: base(routeTemplate) { }
public override IHttpRouteData GetRouteData(string virtualPathRoot, HttpRequestMessage request)
{
IHttpRouteData baseRouteData = base.GetRouteData(virtualPathRoot, request);
if (baseRouteData == null) return null;
if (!_initialized)
{
WebApiConfig.Configuration.Routes.MapHttpRoute("MyRoute", "myservice");
_initialized = true;
}
IHttpRoute odataRoute = WebApiConfig.Configuration.Routes["MyRoute"];
var values = baseRouteData.Values;
IHttpRouteData routeData = values == null ? new HttpRouteData(odataRoute) : new HttpRouteData(odataRoute, new HttpRouteValueDictionary(values));
return routeData;
}
}
Please help. How can I achieve desired behavior?