0

Let's say we have code which retrieves a list of items from a database. The resulting list depends on the user permissions. What I want is to make this code reusable, i.e. callable from multiple controllers. What is the standard way of doing this?

For authentification in my code I load a User from the database via

User user = dbContext.User.SingleOrDefault(x => x.access_token == User.Identity.Name);

This is the authentification schmeme I'm using in Startup.cs:

services.AddAuthentication(options =>
{
    options.DefaultScheme = "Cookies";
}).AddCookie("Cookies", options =>
    {
        options.Cookie.Name = "auth_cookie";
        options.Cookie.SameSite = SameSiteMode.None;
        options.Events = new CookieAuthenticationEvents
        {
            OnRedirectToLogin = redirectContext =>
            {
                redirectContext.HttpContext.Response.StatusCode = 401;
                return Task.CompletedTask;
            }
        };
    });

I tried to put the code retrieving the list directly in one controller and for the other controllers which need the code to call that controller via

services.AddControllersAsServices()

and then

(MyController)HttpContext.RequestServices.GetService(typeof(MyController));

but the User property from ControllerBase was null.

I have read all of the documentation I could find but could not find a solution that the User or HttpContext properties have a value.

Tom
  • 503
  • 2
  • 13
  • 1
    See [my answer here](https://stackoverflow.com/a/45238310/455493). Controllers are not registered with IoC/DI Container by default. Also keep in mind that the controllers are resolved via a factory which populates some of the http specifics properties. HttpContext and the like are not injected in Controllers constructor but set via properties – Tseng Oct 22 '18 at 16:39
  • I'm not well versed enough in ASP.Net Core to gain any value from the linked answer. Can you give a small code example (or link one) of calling shared code between two controllers with authentification? Also User and HttpContext in ControllerBase are get only properties, I can not set them. – Tom Oct 22 '18 at 17:07
  • You will have to use `IControllerFactory` and/or `IControllerFactoryProvider` to get a properly initialized controller. Every controller (inheriting from `ControllerBase` or as Poco Controller by convention (just a `SomethingController` class without inheriting from `Controller` or `ControllerBase`) can be a controller in ASP.NET Core and if it has specific properties they will get populated by the controller factories (such as `HttpContext`/`ControllerContext` etc. – Tseng Oct 22 '18 at 17:18
  • See [this source on GitHub](https://github.com/aspnet/Mvc/blob/release/2.2/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs#L70-L90), all other properties (including `User` indirectly refer to the `ControllerContext` set there and its not happening (only a default new'd `ControllerContext` and this one has no HttpContext/User etc. set. I'm not that deep into the internals, so dunno where (without digging deep and long in the source) to properly get the fully ControllerContext instance which you can pass to the controller activator to get your full working controller instance – Tseng Oct 22 '18 at 17:20
  • P.S. Also keep in mind, if you resolve your controller that way, even with the proper `ControllerContext` instance, you will lose a lot of functionality since the ActionFilters won't be called if you do `controller.GetById(5)` method, which voids a lot of goodness of MVC, such as validation and data binding, so you should think twice if going that way is worth it – Tseng Oct 22 '18 at 17:36
  • Thanks for the explanation. But I think this kind of complexity means playing with fire for me. I guess I will just copy paste the code which retrieves the list, even if it makes the project a bit harder to maintain. – Tom Oct 22 '18 at 17:49
  • Maybe. Dunno exactly how your controllers work, but maybe a simple generic controller would do the trick, as long as you follow some conventions. This may work well for queries, but commands (inserts, updates, deletes) may be much harder this way since its pretty bad idea to directly return and retrieve persistence models (from ORMs such as EF Core) from Api controllers – Tseng Oct 22 '18 at 18:58
  • Im not sure if i understood your problem correctly, but how about moving the code that "retrieves a list of items from a database" to a seperate service, which you can register with the IServiceCollection, then inject this service into any of the controllers where u want to call that functionality, maybe pass the user as a parameter. I think that would be more simpler to manage. So in you controllers u will do something like `var listFromDb = this._myService.GetListByUser(this.User);` – Gerald Chifanzwa Oct 23 '18 at 13:12
  • I would say that's exactly what I want if it has no side effects. Could you make an answer with a code example containing the relevant code from the Startup class registering the service, the service class itself and the controller calling the service? – Tom Oct 23 '18 at 13:42
  • I think that I would have to call the service with the controller object as a parameter though. I need a lot of things from ControllerBase (mainly different kind of results, e.g. Unauthorized, StatusCode or Ok) – Tom Oct 23 '18 at 14:50

0 Answers0