Extending API in one project fully exposes referenced API but routes clash - c#

I'm trying to split a Core 2.1 WebAPI project into two in order that we can expose two different APIs according to circumstances. Simplified, we have one API and we want all the read-only (GET) requests in one API and the entire set in another (the "admin" API). Swagger is enabled in the projects.
I duplicated the project, renaming one (namespaces, etc.) and adding both to the same solution, then commented out all the non-GET controller methods in the read-only project and commented out all the GET methods in the admin project. I then added a reference to the read-only project in the admin project.
Running the read-only project, the swagger page came up fine, just the GETs. Running the admin project gave a 500 on the swagger page. Interestingly, during debugging, I found that removing All the controllers from the admin project, the underlying API from the read-only project was completely exposed straight through and appeared fully functional - not something I was expecting and a potential security issue for anyone not expecting it.
However, I then added one controller back and changed it to decend from one of the read-only controllers, over-riding the ancestor contructor, etc. - it still gave a 500.
Base class:
namespace InfoFeed.WebAPI.Features.Account
{
/// <summary>
/// Handle user account related tasks
/// </summary>
[Authorize]
[Produces("application/json")]
[Route("api/account")]
public class AccountController : Controller
{
private readonly ILogger _log;
protected readonly IMediator _mediator;
public AccountController(ILogger<AccountController> log,
IMediator mediator)
{
_log = log;
_mediator = mediator;
}
Descendent class:
namespace InfoFeedAdmin.WebAPI.Features.Account
{
/// <summary>
/// Handle user account related tasks
/// </summary>
[Authorize]
[Produces("application/json")]
[Route("api/account")]
public class AccountAdminController
: InfoFeed.WebAPI.Features.Account.AccountController
{
public AccountAdminController(ILogger<AccountAdminController> log,
IMediator mediator)
: base(log, mediator)
{
}
I thought that perhaps the route might be causing a clash so I tried changing that to [Route("api/admin/account")] - this worked as long as there were no clashing method signatures. However, it means that there are two sets of routes exposed to the same underlying controller methods.
POST /api/account/signin
GET /api/account/signout
POST /api/admin/account/signin
GET /api/admin/account/signout
Does anyone know how I can hide (perhaps selectively) the routes from the ancestor class so that only the routes I choose to expose from the descendent class are visible/accessible?
Cheers

By default MVC will search the dependency tree and find controllers (even in other assemblies).
You can use application parts to avoid looking for controllers in a particular assembly or location.
If you have an assembly that contains controllers you don't want to be used, remove it from the ApplicationPartManager:
services.AddMvc()
.ConfigureApplicationPartManager(apm =>
{
var dependentLibrary = apm.ApplicationParts
.FirstOrDefault(part => part.Name == "DependentLibrary");
if (dependentLibrary != null)
{
p.ApplicationParts.Remove(dependentLibrary);
}
})
Source: https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/app-parts?view=aspnetcore-2.1

Related

ASP.NET Core api controller not being instantiated

I've created an API controller in my .NET Core 3.1 application (with an Angular frontend, C#).
For some strange reason its not being instantiated, if I try to call any methods on the controller from my Typescript service, nothing happens, it just skips past the call, no error message is generated but the method in the controller isn't accessed.
I've traced it to the fact that the controller isn't being instantiated but I can't see why. Has anyone else experienced this issue?
I inject a service into the constructor, but the service is being added to ioc at startup so it cant be that (along with the other services used), can anyone help?
This is part of the controller code, I've added a breakpoint to the constructor, but its not being hit. I had exactly the same issue with a previous controller I had added, I spent ages trying to figure out why it wasn't being instantiated, then suddenly, it was, despite the fact that I had made no code changes, so I'm baffled by this.
public class RepController : BaseApiController
{
private readonly IRepService _repService;
private readonly ILookupService _lookupService;
private readonly IUserContext _userContext;
public RepController(IRepService repService,
ILookupService lookupService,
IUserContext userContext)
{
Assert.NullCheck(repService);
Assert.NullCheck(lookupService);
Assert.NullCheck(userContext);
_repService = repService;
_lookupService = lookupService;
_userContext = userContext;
}
}
I think the problem is inheriting form BaseApiController. You should try setting that to Controller.
You should also make your that you specify routing on your controllers. A link to the docs about endpoint routing.
Maybe your endpoints dont have a correct return type, this should be of Type ActionResult
I found the problem, I had this decorator
[Route("api/[controller]"]
instead of this
[Route("api/[controller]/[action]"]
its working now

Dynamically load ControllerBase implementations from assemblies

I'm trying to create a web service using ASP.NET Core 2.1 where I need the service to be able to register BaseControllers loaded from DLL's through reflection. However I can't seem to find how to register a BaseController to the service configuration.
In Startup.cs
public class Startup
{
....
public void ConfigureServices(IServiceCollection services)
{
var builder = services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
/// This now loads the base controllers located
/// within the dll's through reflection and the
/// BaseControllers are initialized as well
IList<ControllerBase> controllers = PluginLoader.Instance.GetControllers();
foreach (ControllerBase controllerBase in controllers)
{
/// Here I hope that I can add the controllerBase to services but
/// I just can't seem to find a way to do it. Is it even possible?
/// I'm thinking that builder.AddControllersAsServices() might be useful for something but just can't seem to get it right
}
}
}
Does anyone have any tips?
It seems like it's possible to add an assembly to the builder and that will make the controller(s) available. So it's achievable with a minor refactor of the loader.
Tested code:
if (controllers != null && controllers.Any())
{
foreach (ControllerBase controllerBase in controllers)
{
builder.AddApplicationPart(controllerBase.GetType().Assembly);
}
}

Structuremap mvc 5 injecting applicationdbcontext

Adding Structuremap MVC 5 to an ASP.NET MVC project. I would like to have a singleton of my database connection per request - my controllers would share the same database connection. I am implementing the repository pattern here and need each controller to have a copy of its respective repository. I know this is possible but I think I'm missing or mis-interpretting something wrong.
I have a controller, "Bag," that needs a "IBagRepo"
public class BagController : Controller
{
private readonly IBagRepo repo;
public BagController(IBagRepo repo)
{
this.repo = repo;
}
// actions
}
My first attempt was hooking the singleton database connection in the ControllerConvention, as I assume its called once
public class ControllerConvention : IRegistrationConvention {
public void Process(Type type, Registry registry) {
if (type.CanBeCastTo<Controller>() && !type.IsAbstract) {
// Tried something like
registry.For(type).Singleton().Is(new ApplicationDbContext()); // this
registry.For(type).LifecycleIs(new UniquePerRequestLifecycle());
}
}
}
But it came clear that this isn't the right file to make this change. I went into the registry class that was automatically generated upon installing the nuget package and tried fiddling around with this.
public class DefaultRegistry : Registry {
#region Constructors and Destructors
public DefaultRegistry() {
Scan(
scan => {
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.With(new ControllerConvention());
});
// httpContext is null if I use the line below
// For<IBagRepo>().Use<BagRepo>().Ctor<ApplicationDbContext>().Is(new ApplicationDbContext());
}
#endregion
}
I haven't seen a problem like this out here yet. Am I passing in the right types within my DefaultRegistry class?
What you're wanting is effectively the default behavior if you had been using the StructureMap.MVC5 nuget: https://www.nuget.org/packages/StructureMap.MVC5/. As long as your DbContext is registered with the default lifecycle, that package is using a nested container per http request which effectively scopes a DbContext to an HTTP request for unit of work scoping.
Different tooling than MVC & EF, but I described similar mechanics for FubuMVC + RavenDb w/ StructureMap in this blog post: http://jeremydmiller.com/2014/11/03/transaction-scoping-in-fubumvc-with-ravendb-and-structuremap/
I ended overriding the default controller factory and not using structuremap

ASP.NET MVC3 controller AOP proxy not intercepting all methods, only IController.Execute

I have a project with several layers - among them the web front end (ASP.NET MVC3) and the service back end (mainly business logic). The project is a few months old, so everything is working as expected. Now I am trying to add a logging aspect to some of the MVC3 controller methods using custom [Log] attributes.
I am using Castle Windsor for dependency injection. To get a logging aspect I leverage Castle DynamicProxy through SNAP. Controllers are being resolved using WindsorControllerFactory from Krzysztof Koźmic's helpful tutorial - but I modified it to look for the default interface for the controller (see below).
In my service layer:
[Log(LoggingLevel.Info)]
public void Save(MyBusinessDto dto)
{
// business logic and other checks
this.repository.Save(mbo);
}
In my web front end's IWindsorInstaller for controllers:
private static BasedOnDescriptor FindControllers()
{
return AllTypes
.FromThisAssembly()
.BasedOn<IController>()
.WithService.DefaultInterface();
}
In my (slightly customized) WindsorControllerFactory that looks for the default interface for the controller:
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
{
throw new HttpException(404, string.Format(Error404, requestContext.HttpContext.Request.Path));
}
string controllerName = controllerType.Name;
string defaultInterfaceName = 'I' + controllerName;
Type defaultInterface = controllerType.GetInterface(defaultInterfaceName);
object controller = this.kernel.Resolve(defaultInterface);
return (IController)controller;
}
In my controllers:
public class MyBusinessController : MyBusinessControllerBase, IMyBusinessController
{
[Log(LoggingLevel.Debug)]
public ActionResult CreateOrUpdate(MyBusinessFormModel fm)
{
// Convert form model to data transfer object,
// perform validation and other checks
this.service.Save(dto);
return View(fm);
}
}
This all works fine in the service project, but in the controllers the methods are not being intercepted.
I have confirmed that the WindsorControllerFactory returns proxied controllers.
I have confirmed that the controllers have the interceptor registered.
I have confirmed that the MasterProxy in SNAP intercepts the controller - but it only intercepts IController.Execute(RequestContext requestContext).
How can I intercept all controller methods that have my [Log] attribute?
Update 1: I have considered using DynamicProxy directly instead of SNAP, but this is secondary to getting it to work for controllers as well.
Update 2+4: It seems that SNAP is missing from github back on github.
Update 3: This is what I see in the Visual Studio debugger when breaking in the WindsorControllerFactory (see above). The inspected controller variable is what is returned to MVC, and it is indeed proxied.
controller {Castle.Proxies.IMyBusinessControllerProxy}
__interceptors {Castle.DynamicProxy.IInterceptor[1]}
[0] {Snap.MasterProxy}
__target {My.Business.Web.Controllers.MyBusinessController}
service {Castle.Proxies.IMyBusinessServiceProxy}
(other contructor injections)
MyInjectedProperty {My.Business.Useful.MyOtherType}
In IController GetControllerInstance(...), don't serve interface proxies, serve class proxies with virtual methods.
The user-implemented methods in the controller returned from IController GetControllerInstance(...) will not be accessed through the proxied IMyBusinessController interface, but cast from IController to to the actual class of the controller; for example MyBusinessController. Use a class proxy instead, to make MVC3's cast return the proxy. Also, mark methods as virtual, otherwise the intercepting proxy won't be able to intercept the method calls and check for custom attributes.
In the controllers, add virtual to your methods with attributes:
public class MyBusinessController : MyBusinessControllerBase, IMyBusinessController
{
[Log(LoggingLevel.Debug)]
public virtual ActionResult CreateOrUpdate(MyBusinessFormModel fm)
{
// Convert form model to data transfer object,
// perform validation and other checks
this.service.Save(dto);
return View(fm);
}
}
Why is only Execute(...) intercepted? The IController interface only contains Execute(...). Execute is called on the returned controller interface proxy, thus it can be intercepted. But once MVC3's internal ControllerBase.Execute(...) gets the call, it performs the cast to the class it expected from the ControllerFactory.
The problem is similar to this leaking, in that both bypass the interface proxy. I guess it could be solved in a number of ways; perhaps by creating a custom type converter, creating a class proxy from the interface proxy's target in the factory, a clever Windsor configurations etcetera.
Krzysztof Koźmic's IController installer and WindsorControllerFactory should work out of the box. Interface proxies may be recommended in the bigger picture (and they work well until using interceptors in the controllers) but in this case there might be a reason not to go that far, to avoid further side effects.
Thanks to Marius for pointing me in the right direction!
Since DynamicProxy (SNAP uses dynamicproxy) can't intercept non-virtual methods I am guessing that the returned proxy is a derived class of your controller and thus, the non virtual methods are ignored. You either need to make SNAP (don't know how this works though) return an interface proxy with target (your implementation) or simply try to make your controller methods virtual.

Questions on lifetime of Ninject-created action filters in MVC 3

I would like to use a global-scoped action filter in my MVC 3 application using Ninject; however, I'm trying to understand the lifetime of that filter, its dependencies, and how to introduce variations to its dependencies by decorating my controllers and/or action methods.
I'd like to have my filter type depend on objects whose lifetimes are bound to request scope, so, something like this:
public sealed class MyGlobalActionFilter : IActionFilter
{
public MyGlobalActionFilter(IService1 svc1, IService2 svc2, RequestType reqType)
{
// code here
}
// IActionFilter implementation here...
}
... and in the module config ...
Bind<IService1>().To<ConcreteService1>().InRequestScope()
Bind<IService2>().To<ConcreteService2>().InRequestScope()
BindFilter<MyGlobalActionFilter>(FilterScope.Global, null)
.WhenControllerHas<RequestTypeAttribute>()
.WithConstructorArgumentFromControllerAttribute<RequestTypeAttribute>(
"reqType",
x => x.RequestType
);
BindFilter<MyGlobalActionFilter>(FilterScope.Global, null)
.WhenActionMethodHas<RequestTypeAttribute>()
.WithConstructorArgumentFromActionAttribute<RequestTypeAttribute>(
"reqType",
x => x.RequestType
);
BindFilter<MyGlobalActionFilter>(FilterScope.Global)
.When(x => true)
.WithConstructorArgument("reqType", RequestType.Undefined)
And an attribute on controllers and/or action methods to represent an application-specific "request type":
[RequestType(RequestType.Type1)]
public sealed class SomeController : Controller { /* code here*/ }
Am I understanding properly how this should work? Will a new instance of MyGlobalActionFilter get spun up and injected on each HTTP request? If this won't work, what am I missing, and what would be a better way to make this work?
Also, with injecting the RequestType, the BindFilter syntax here seems unnecessarily verbose, I'm not sure if it works like I expect, and it seems there would be a better way to inject a default RequestType into the action filter if a RequestTypeAttribute isn't present on the controller or the action method.
Please enlighten me!
I havn't seen an official documentation from Microsoft when and how often IFilterProvider is called exactly. But from my observations it seems to be called once for each request. This means that transient bound filters are basically InRequestScope bound with the difference that they aren't disposed by Ninject at the end of the request.
There are some changes you should do:
Do not derive from ActionFilterAttribute but implement IActionFilter
instead to prevent that it is accidentially used as attribute.
Rethink the use of FilterScope.Global for all bindings. I consider it
as bad practice to have filters on actions/controllers running with
global priority.
Also be aware that a filter for each matching binding is created and executed. This means that currently the one with RequestType.Undefined is run on every request independent of whether there is an attribute on the action or controller. Additionally, the ones for action and controllers are executed if there is a attribute on them.
if "System.Web.Mvc.GlobalFilters.Filters" is what you mean by "global-scoped action filter", then those filters are supposed to be instantiated once per application start/stop cycle and i doubt IoC container can be of any use here.
as i can see from your sample code you need some controller/action filters to modify beavior of global filters... what about creating base filter and derived filters with varying logic?
public abstract class BaseFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//user some service locator to retrieve IService1, IService2
//some logic based on RequestType
}
protected RequestType { get; set; }
}
public class SomeFilter : BaseFilter
{
public SomeFilter(RequestType requestType)
{
RequestType = requestType;
}
}

Categories