In few places in legacy code (more than 100 controllers), we are running action from other controllers.
In .NET Framework it runs OK - ClaimsPrincipal in both controller's action have correct values, but in .NET Core, running SecondController.internalPut() from FirstController gives me NullReferenceException.
FirstController:
[EnableCors]
public class FirstController : BaseApiController
{
public FirstController(IContextFactory contextFactory) : base(contextFactory)
{
}
[HttpPut]
[HttpPost]
[Route("/api/firstcontroller")]
public IActionResult Put([FromBody] MyDTO data)
{
var token = Identity.Token; // <--- correct value
var secondController = new SecondController(ContextFactory);
secondController.internalPut(something); <--- NullReferenceException
return Ok();
}
}
SecondController:
[EnableCors]
public class SecondController : BaseApiController
{
public SecondController(IContextFactory contextFactory) : base(contextFactory)
{
}
[HttpPut]
[HttpPost]
public async Task<IActionResult> Put(Guid myGuid)
{
internalPut(something); // <-- OK
return Ok();
}
internal void internalPut(object something)
{
var token = Identity.Token; // <--- NullReferenceException when running from FirstController!!
}
}
And BaseApiController with TokenIdentity:
[ApiController]
[Route("/api/[controller]")]
[Route("/api/[controller]/[action]")]
public class BaseApiController : ControllerBase
{
protected readonly IMyContextFactory ContextFactory;
public BaseApiController(IMyContextFactory contextFactory)
{
ContextFactory = contextFactory;
}
public TokenIdentity Identity => User?.Identity as TokenIdentity;
}
public class TokenIdentity : GenericIdentity
{
public Guid Token { get; set; }
public string User { get; set; }
public TokenIdentity(Guid token) : base(token.ToString())
{
Token = token;
}
}
How is the easiest fix for this bug? I know that I can change BaseApiController implementation to get ClaimsPrincipal from IHttpContextAccessor, but this means that I need to update constructors for all > 100 controllers in code...
It is another way to always have ClaimsPrincipal when we are calling action from another controller?
What I recommend as the correct solution
I can't emphasise enough how much I recommend moving shared functionality into its own services, or perhaps look at using the Mediator Pattern (e.g. using the MediatR library) to decouple your controllers from their functionality a little. What I provide below is not a solution, but a band-aid.
What I recommend a QUICK FIX only
Why is this only a quick fix?: because this doesn't instantiate the correct action details and route parameters, so it could potentially cause you some hard-to-find bugs, weird behaviour, URLs maybe not generating correctly (if you use this), etc.
Why am I recommending it?: because I know that sometimes time is not on our side and that perhaps you need a quick fix to get this working while you work on a better solution.
Hacky quick fix
You could add the following method to your base controller class:
private TController CreateController<TController>() where TController: ControllerBase
{
var actionDescriptor = new ControllerActionDescriptor()
{
ControllerTypeInfo = typeof(TController).GetTypeInfo()
};
var controllerFactory = this.HttpContext.RequestServices.GetRequiredService<IControllerFactoryProvider>().CreateControllerFactory(actionDescriptor);
return controllerFactory(this.ControllerContext) as TController;
}
Then instead of var secondController = new SecondController(ContextFactory); you would write:
var secondController = CreateController<SecondController>();
Related
I have a common base controller and a couple of inherited controller classes with similar routes applied to them. I want to apply the common part of the different routes to the base class. Below is the bare minimum of my code:
using Microsoft.AspNetCore.Mvc;
public abstract class CommonController : Controller
{
// Some common code here
}
[Route("api/v1/BookLookup")]
public class BooksController : CommonController
{
public async Task<JsonResult> GetAsync([FromBody]Dto1 filterParameters)
{
return await GetApiData(filterParameters);
}
}
[Route("api/v1/MovieLookup")]
public class MoviesController : CommonController
{
public async Task<JsonResult> GetAsync([FromBody]Dto1 filterParameters)
{
return await GetApiData(filterParameters);
}
}
I want to have something like the following:
using Microsoft.AspNetCore.Mvc;
[Route("api/v1/")] // or any other attribute?
public abstract class CommonController : Controller
{
// Some common code here
}
[Route("BookLookup")]
public class BooksController : CommonController
{
public async Task<JsonResult> GetAsync([FromBody]Dto1 filterParameters)
{
return await GetApiData(filterParameters);
}
}
[Route("MovieLookup")]
public class MoviesController : CommonController
{
public async Task<JsonResult> GetAsync([FromBody]Dto1 filterParameters)
{
return await GetApiData(filterParameters);
}
}
is this possible and if it is, how?
The project targets .NET Core 2.0 and the Controller class in the above example is ASP.NET Core MVC controller class, not Web API controller.
The built-in RouteAttribute will not work with controller inheritance by default. However, to achieve your goal, you could create a custom IControllerModelConvention to apply a rule that takes consideration of controller inheritance at startup-time.
To avoid mixing up with the default [RouteAttribute], I create a custom MyRoutePrefixAttribute instead of using the default [RouteAttribute] (Feel free to use the [RouteAttribute] if you think this behavior should be overrided ):
[AttributeUsage(AttributeTargets.Class)]
public class MyRoutePrefixAttribute : Attribute
{
public MyRoutePrefixAttribute(string prefix) { Prefix = prefix; }
public string Prefix { get; }
}
And to lookup this [MyRoutePrefixAttribute] from the inheritance chain, I create a RoutePrefixConvention that will combine the prefixes recursively:
public class RoutePrefixConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
foreach (var selector in controller.Selectors)
{
var prefixes = GetPrefixes(controller.ControllerType); // [prefix, parentPrefix, grandpaPrefix,...]
if(prefixes.Count == 0) continue;
// combine these prefixes one by one
var prefixRouteModels = prefixes.Select(p => new AttributeRouteModel(new RouteAttribute(p.Prefix)))
.Aggregate( (acc , prefix)=> AttributeRouteModel.CombineAttributeRouteModel(prefix, acc));
selector.AttributeRouteModel = selector.AttributeRouteModel != null ?
AttributeRouteModel.CombineAttributeRouteModel(prefixRouteModels, selector.AttributeRouteModel):
selector.AttributeRouteModel = prefixRouteModels;
}
}
private IList<MyRoutePrefixAttribute> GetPrefixes(Type controlerType)
{
var list = new List<MyRoutePrefixAttribute>();
FindPrefixesRec(controlerType, ref list);
list = list.Where(r => r!=null).ToList();
return list;
// find [MyRoutePrefixAttribute('...')] recursively
void FindPrefixesRec(Type type, ref List<MyRoutePrefixAttribute> results)
{
var prefix = type.GetCustomAttributes(false).OfType<MyRoutePrefixAttribute>().FirstOrDefault();
results.Add(prefix); // null is valid because it will seek prefix from parent recursively
var parentType = type.BaseType;
if(parentType == null) return;
FindPrefixesRec(parentType, ref results);
}
}
}
this convention will NOT influence the performance: it only searches all the [MyRoutePrefixAttribute] attributes through the inheritance chain at startup time.
Finally, don't forget to add this convention within your startup:
services.AddMvc(opts =>
{
opts.Conventions.Add(new RoutePrefixConvention());
});
Controller:
[MyRoutePrefix("api/v1")]
public class CommonController : Controller
{
}
[Microsoft.AspNetCore.Mvc.Route("MovieLookup")]
public class MoviesController : CommonController
{
public string GetAsync()
{
return "MovieLookup";
}
}
Test Result:
Hope this can help you.
Our team maintains a self-hosted ASP.NET Web API. The project uses attribute routing and we have dozens of existing controllers. Lets say, the API is exposed via the main path /api/purpose1/... with all the existing controllers being placed as resources underneath.
Now I want to introduce a new parallel main path, e. g. /api/purpose2/. It should be possible to activate both main paths independently of each other via a boolean variable in a config file.
Since all the controllers are within one assembly, the attribute routing approach always finds and adds them to both purpose1 and purpose2. This contradicts the independency of purpose1 and purpose2. So I used attribute routing for purpose1 and convention-based routing for purpose2. That at least worked, but I'm not happy with the mixture of two different routing approaches.
So my question is: can I disable certain controller classes with attribute routing?
OnActionExecuting example:
V1 controller
[Route("api/[controller]")]
[ApiController]
public class SampleV1Controller : VersioningAwareControllerBase
{
[HttpGet]
public IActionResult Get()
{
return new OkObjectResult("V1");
}
}
V2 controller
[Route("api/[controller]")]
[ApiController]
public class SampleV2Controller : VersioningAwareControllerBase
{
[HttpGet]
public IActionResult Get()
{
return new OkObjectResult("V2");
}
}
Versioning aware base controller
public abstract class VersioningAwareControllerBase: ControllerBase, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
if (!FeatureFlags.ShouldDeprecateV1 ||
!string.Equals(context.RouteData.Values["controller"].ToString(), "samplev1",
StringComparison.OrdinalIgnoreCase))
return;
context.Result = NotFound();
context.Canceled = true;
}
public void OnActionExecuting(ActionExecutingContext context) { }
}
Peter Csala's answer is fine, however, it has a dependency to System.Web.Mvc. In our case, this dependency wasn't there before and I found a solution that does not require adding it.
I've extended ApiControllerActionInvoker the following way:
internal class CustomHttpActionInvoker : ApiControllerActionInvoker
{
public CustomHttpActionInvoker(IConfigProvider configProvider)
{
ConfigProvider = configProvider;
InvokeActionFunc = base.InvokeActionAsync;
}
/// <summary>FOR AUTOMATED TESTS ONLY</summary>
internal CustomHttpActionInvoker(IConfigProvider configProvider,
Func<HttpActionContext, CancellationToken, Task<HttpResponseMessage>> invokeActionFunc)
{
ConfigProvider = configProvider;
InvokeActionFunc = invokeActionFunc;
}
private IConfigProvider ConfigProvider { get; }
private Func<HttpActionContext, CancellationToken, Task<HttpResponseMessage>> InvokeActionFunc { get; }
/// <inheritdoc />
public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
var isRelevantRequest = actionContext.ControllerContext.Controller is MyRelevantController;
if (isRelevantRequest && ConfigProvider.IsPurpose1)
{
return InvokeActionFunc(actionContext, cancellationToken);
}
if (!isRelevantRequest && ConfigProvider.IsPurpose2)
{
return InvokeActionFunc(actionContext, cancellationToken);
}
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound));
}
}
The internal constructor was introduced to support easier unit testing.
The following code registers the custom class:
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
config.Services.Replace(typeof(IHttpActionInvoker), new CustomHttpActionInvoker(MyConfigProvider));
I am trying to build an MVC service which calls 2 different APIs, an Amazon one and an Apple one. The code looks like this:
public abstract class ApiHttpCaller<T>
{
protected static HttpClient _client;
protected ApiHttpCaller()
{
_client = new HttpClient();
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public abstract Task<T> RetrieveApiResultAsync(string searchValue);
}
This ApiHttpCaller is implemented by my 2 specifics AmazonApiCaller and AppleApiCaller, let's take only one of them into account:
public class AmazonApiCaller : ApiHttpCaller<AmazonResponseModel>
{
protected static IOptions<ApiUrls> _apiUrls;
public AmazonApiCaller(IOptions<ApiUrls> apiUrls)
{
_apiUrls = apiUrls;
}
public override async Task<AmazonResponseModel> RetrieveApiResultAsync(string searchValue)
{
..logic to call the api..
string responseBody = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<AmazonResponseModel>(responseBody);
}
}
as you can see, correct me if the architecture is wrong, there is an AmazonResponseModel used as generics here. As you can imagine AmazonApi and AppleApi return 2 different models. That's why my abstract parent class ApiHttpCaller uses a generics T that into the specifc AmazonApiCaller becomes an AmazonResponseModel. Such APIs are called from my controller.
[Route("api/[controller]")]
[ApiController]
public class ItemsController<T> : ControllerBase
{
private readonly IEnumerable<ApiHttpCaller<T>> _apiCallers;
[HttpPost]
public async Task<ActionResult> Post([FromBody] string value)
{
var amazonCaller = _apiCallers.First(x => x.GetType() == typeof(AmazonApiCaller));
var itemResult = await amazonCaller.RetrieveApiResultAsync(value);
..more logic to map the itemResult to a viewModel..
}
}
So, first question is: do you think it's correct to use the genercis T in the controller that then becomes a specifc type inside each api caller?
Second and more important: I don't know how to register in Startup.cs the ApiHttpCallers in such a way that they get injected properly in my controller. First guess is:
services.AddSingleton<ApiCaller<T>, AmazonApiCaller<AmazonResponseModel>>();
services.AddSingleton<ApiCaller<T>, AppleApiCaller<AppleResponseModel>>();
point is Startup.cs doesn't know anything of T .
services to be registred:
services.AddSingleton<ApiCaller<AmazonResponseModel>, AmazonApiCaller>();
services.AddSingleton<ApiCaller<AppleResponseModel>, AppleApiCaller>();
services.AddTransient(typeof(ItemsController<>));
Change the controller as follows:
public class ItemsController<T> : ControllerBase
{
private readonly ApiHttpCaller<T> _apiCaller;
public ItemsController(ApiHttpCaller<T> apicaller){
_apiCaller = apicaller;
}
[HttpPost]
public async Task<ActionResult> Post([FromBody] string value)
{
// do something with the requested API Caller
}
}
This should now inject the correct ApiCaller into your service.
Of course you need to specify the type when injecting an ItemsController:
// Constructor
public AnyClass(ItemsController<AmazonResponseModel> controller){
// _apiCaller of controller will be AmazonApiCaller
}
Or maybe use another IoC Container like ninject.
You could benefit from Features like Contextual and named Bindings, which is documented on their page.
You DI registration is incorrect here. It should be like this:
services.AddSingleton<ApiCaller<AmazonResponseModel>, AmazonApiCaller>();
services.AddSingleton<ApiCaller<AppleResponseModel>, AppleApiCaller>();
you need to specify which generic would correspond to which implementation.
I'm using ThinkTecture's resource based authorization in my WebApi.
I'm trying to test one of my controller that I needed to check the access inside the function. But now, I can't test the function anymore since, I can't mock an extension method and since it's a nuget method, I can't modify the class to inject another value.
My controller look like this:
public class AlbumController : ApiController
{
public async Task<IHttpActionResult> Get(int id)
{
if (!(await Request.CheckAccessAsync(ChinookResources.AlbumActions.View,
ChinookResources.Album,
id.ToString())))
{
return this.AccessDenied();
}
return Ok();
}
}
And the ResourceAuthorizationManager is setted into the startup like this:
app.UseResourceAuthorization(new ChinookAuthorization());
Source code of the ThinkTecture project is here.
Thank you for your help
The ResourceAuthorizationAttribute uses Reqest.CheckAccess so I don't think it is a good solution to abstract away the implementation and then injecting it into the controller since in theory, the ResourceAuthorizationAttribute and the created service could use different implementations of the CheckAccess method.
I took a simpler approach by creating a BaseController
public class BaseController : ApiController
{
public virtual Task<bool> CheckAccessAsync(string action, params string[] resources)
{
return Request.CheckAccessAsync(action, resources);
}
}
and making CheckAccessAsync virtual so I can mock it (by for example Moq).
then from my controller
public class AlbumController : BaseController
{
public async Task<IHttpActionResult> Get(int id)
{
if (!(await CheckAccessAsync(ChinookResources.AlbumActions.View,
ChinookResources.Album,
id.ToString())))
{
return this.AccessDenied();
}
return Ok();
}
}
Unit testing the controller then is as easy as:
[TestClass]
public class TestClass
{
Mock<AlbumController> mockedTarget
AlbumController target
[TestInitialize]
public void Init()
{
mockedTarget = new Mock<AlbumController>();
target = mockedTarget.Object;
}
[Test]
public void Test()
{
mockedTarget.Setup(x => x.CheckAccessAsync(It.IsAny<string>(),
It.IsAny<string[]>()))
.Returns(Task.FromResult(true));
var result = target.Get(1);
// Assert
}
}
You could always wrap this static call into some abstraction of yours:
public interface IAuthorizationService
{
Task<bool> CheckAccessAsync(string view, string album, string id);
}
and then have some implementation that will delegate the call to the static extension method. But now since you will be working with the IAuthorizationService you can freely mock the CheckAccessAsync method in your unit tests.
As far as testing the implementation of this abstraction is concerned, you probably don't need it as it only acts as a bridge to the ThinkTecture's classes which should already be pretty well tested.
I finally solved my problem.
The real problem was that the CheckAccess method was an extension.
(for my answer, every class will refer to the sample that can be find here)
To stop using the extension method, I added these methods into my chinookAuthorization
public Task<bool> CheckAccessAsync(ClaimsPrincipal user, string action, params string[] resources)
{
var ctx = new ResourceAuthorizationContext(user ?? Principal.Anonymous, action, resources);
return CheckAccessAsync(ctx);
}
public Task<bool> CheckAccessAsync(ClaimsPrincipal user, IEnumerable<Claim> actions, IEnumerable<Claim> resources)
{
var authorizationContext = new ResourceAuthorizationContext(
user ?? Principal.Anonymous,
actions,
resources);
return CheckAccessAsync(authorizationContext);
}
Then I changed my controller to have an instance of the chinookAuthorization
public class AlbumController : ApiController
{
protected readonly chinookAuthorization chinookAuth;
public BaseApiController(chinookAuthorization chinookAuth)
{
if (chinookAuth == null)
throw new ArgumentNullException("chinookAuth");
this.chinookAuth = chinookAuth;
}
public async Task<IHttpActionResult> Get(int id)
{
if (!(await chinookAuth.CheckAccessAsync((ClaimsPrincipal)RequestContext.Principal, ChinookResources.AlbumActions.View,
ChinookResources.Album,
id.ToString())))
{
return this.AccessDenied();
}
return Ok();
}
}
And I'm still declaring my ChinookAuthorization into my owin startup, to keep using the same pattern for my attribute check access call.
So now, I just have to mock the chinookAuthorization, mock the response of the call to return true, and that's it!
I'm creating my first C# MVC site and quite early on I've hit a roadblock where I'm not sure if I'm going about things entirely the wrong way and I can't find an example similar to my own online but it seems like what I'm trying to do should be straightforward.
Basically, I have my initial controller (called ClientController) that sets up a list of clients and then displays them in my list view:
public class ClientController : Controller
{
private readonly IClientManagerRepository _clientManagerRepository;
public ClientController()
: this(new EntityClientManagerRepository())
{
}
public ClientController(IClientManagerRepository repository)
{
_clientManagerRepository = repository;
}
//
// GET: /Client/
public ViewResult List()
{
return View(_clientManagerRepository.GetAllClients());
}
}
Then in my view I have an action link where I want to route to my UserController, passing it the client name, so that it can build the list of users for that particular client.
#Html.ActionLink("View Admin Users","Index","User",new {clientName = item.ClientName},null)
This works with the following code:
public class UserController : Controller
{
private IUserManagerRepository _userManagerRepository;
//
// GET: /User/
public ActionResult Index(string clientName)
{
_userManagerRepository = new EntityUserManagerRepository(clientName);
return View(_userManagerRepository.GetAllUsers());
}
}
And my list of users is displayed correctly in my view.
However, when I then add in my details action method it doesn't work because the _userManagerRepository isn't instantiated:
//
// GET: /User/Details/5
public ActionResult Details(int contactId)
{
return View(_userManagerRepository.GetUser(contactId));
}
I would have to I guess pass in the clientname each time and re-instantiate my _userManagerRepository. That doesn't feel like a very good way though.
Ideally I'd like to create my _userManagerRepository in the constructor of my UserController. I've been looking into how I would do this so I'd have something like:
public class UserController : Controller
{
private IUserManagerRepository _userManagerRepository;
public UserController(string clientname)
: this(new EntityUserManagerRepository(clientname))
{
}
public UserController(IUserManagerRepository repository)
{
_userManagerRepository = repository;
}
I've researched that I can create my own controller factory so that I can have a parameter in my userController constructor however I still don't understand how I would pass my clientname parameter form a view to my UserController.
If you want to instantiate Repository class in controller's constructor,you can use NInject,
it's really nice approach to do it.
1-Install Ninject from Nuget
2-Create Repository Abstract for example ICustomerRepository
public abstract ICustomerRepository
{
string GetCustomerName();
}
3-Create Repository for example CustomerRepository
public class CustomerRepository:ICustomerRepository
{
string GetCustomerName()
{
return ("John");
}
}
4-create CustomerControllerFactory Class
public class CustomControllerFactory : DefaultControllerFactory
{
private static IKernel ninjectKernel;
public CustomControllerFactory()
{
ninjectKernel = new StandardKernel();
AddBindings(ninjectKernel);
}
protected override IController GetControllerInstance
(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
{
return (new Controllers.MessageController());
}
else
{
return ((IController)ninjectKernel.Get(controllerType));
}
}
public static void AddBindings(IKernel ninjectKernel)
{
Common.DependencyInjection.DependencyManager.GetDependencyInjections().ForEach(current =>
{
if (current.Abstract != null && current.Implementation != null)
{
ninjectKernel.Bind(current.Abstract).To(current.Implementation);
}
});
ninjectKernel.Bind<ICustomerRepository>().To(typeof(CustomerRepository));
}
}
ninjectKernel.Bind().To(typeof(CustomerRepository));
I bind ICustomerRepository to CustomerRepository in upper code
5- Add below code to Application_Start
ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory());
6-Create New Controller
public class CustomerController:Controller
{
public CustomerController(ICustomerRepository customerRepository)
{
//customerRepository instantiate to CustomerRepostory Class automatically
}
}
it's Dependency Injection that i think useful for you
Regards