I have a generic controller that's inside a razor class library referenced in a net core web app. Problem: When I launch the web app and try to navigate to /MyFeature/User, it still doesn't recognize that endpoint at all. Note that when I remove the generic type from the controller and its constructor, the endpoint /MyFeature/User works just fine.
Here is the controller:
[Area("MyFeature")]
[Authorize]
public class UserController<TIdentityUser> : Controller
where TIdentityUser : IdentityUser
{
Next, I'm trying to make the web app accept controllers that have generic types :
public void ConfigureServices(IServiceCollection services)
{
......
services.AddControllerFeatureProvider(Configuration);
....
}
Here is the implementation of AddControllerFeatureProvider from the razor class library:
public static void AddControllerFeatureProvider(this IServiceCollection services, IConfiguration config)
{
services.AddMvcCore().ConfigureApplicationPartManager(manager =>
{
manager.FeatureProviders.Add(new MyControllerFeatureProvider());
});
}
MyControllerFeatureProvider makes the app accept controllers with generic types. I've debugged that part and it works fine. UserController`1 goes into the breakpoint and it returns IsController as true.
But still, the endpoint doesn't work at all.
public class MyControllerFeatureProvider : ControllerFeatureProvider
{
protected override bool IsController(TypeInfo typeInfo)
{
var isController = base.IsController(typeInfo);
if (!isController && typeInfo.Name.Contains("Test") || typeInfo.Name.Contains("User"))
{
isController = typeInfo.Name.EndsWith("Controller`1", StringComparison.OrdinalIgnoreCase);
}
return isController;
}
}
Related
I am creating sample project based on DDD.
I created SharedKernel project where I have my class for DomainEvents
public static class DomainEvents
{
public static IContainer Container { get; set; }
static DomainEvents()
{
Container = StructureMap.Container.For<GenericTypesScanning>();
}
public static void Raise<T>(T args) where T : IDomainEvent
{
foreach (var handler in Container.GetAllInstances<IHandle<T>>())
{
handler.Handle(args);
}
}
}
and this is class GenericTypesScanning
public class GenericTypesScanning : Registry
{
public GenericTypesScanning()
{
Scan(scan =>
{
// 1. Declare which assemblies to scan
scan.Assembly("MyLib");
// 2. Built in registration conventions
scan.AddAllTypesOf(typeof(IHandle<>));
scan.WithDefaultConventions();
});
}
}
In MyLib project I have class AppointmentConfirmedEvent and handler for this event:
public class EmailConfirmationHandler: IHandle<AppointmentConfirmedEvent>
{
public void Handle(AppointmentConfirmedEvent appointmentConfirmedEvent)
{
// TBD
}
}
I have temporary rest api controller where I wanted to check if everything is correctly registered and I am doing this:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET: api/<ValuesController>
[HttpGet]
public IEnumerable<string> Get()
{
var appointmentConfirmedEvent = new AppointmentConfirmedEvent();
DomainEvents.Raise(appointmentConfirmedEvent);
return new string[] { "value1", "value2" };
}
}
but when DomainEvents.Raise is called the event is not handled because internal call Container.GetAllInstances<IHandle<T>>() returns empty array.
I did analogous example with Console app and there everything works fine. Any idea why it does not work in case of ASP.NET Core .NET 5 project?
-Jacek
The AddAllTypesOf() method does not work with open generics. See the ConnectImplementationsToTypesClosing() method in the StructureMap docs: http://structuremap.github.io/generics/
And just a reminder, StructureMap is no longer supported. Moreover, 2.6.4.1 was the "haunted" version of StructureMap that was admittedly buggy.
The first thing to do is to check out the type scanning diagnostics:
http://structuremap.github.io/diagnostics/type-scanning/.
The type scanning can be a little brittle if there are missing assemblies. The diagnostics might point out where things are going wrong. Also, try your WhatDoIHave() diagnostics too.
And also, just making sure that you know that StructureMap is no longer supported and has been replaced by Lamar:
https://jeremydmiller.com/2018/01/29/sunsetting-structuremap/
https://jasperfx.github.io/lamar
I'm learning APB framework. ABP can automagically configure the application services as API Controllers by convention. The documentation says it is possible to fully customize it.
In the example they provide, I would like to change an action name of one of the following endpoints:
/api/app/book to /api/app/books.
But unfortunately I cannot find how to do it.
I tried to change the ActionName of the corresponding service method:
public class BookAppService :
CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto,
CreateUpdateBookDto, CreateUpdateBookDto>,
IBookAppService
{
public BookAppService(IRepository<Book, Guid> repository)
: base(repository)
{
}
[ActionName("books"), HttpGet]
public override Task<PagedResultDto<BookDto>> GetListAsync(PagedAndSortedResultRequestDto input)
{
return base.GetListAsync(input);
}
}
But the resulting endpoint is not what I want:
Any idea how to do it ?
in abp.io (ABP VNext) , you can diable or configure ConventionalControllers in HttpModules config file of your Project .
Edit this Method :
private void ConfigureConventionalControllers()
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ConventionalControllers.Create(typeof(SystemApplicationModule).Assembly);
});
}
I am trying to inject a service into my action filter but I am not getting the required service injected in the constructor. Here is what I have:
public class EnsureUserLoggedIn : ActionFilterAttribute
{
private readonly ISessionService _sessionService;
public EnsureUserLoggedIn()
{
// I was unable able to remove the default ctor
// because of compilation error while using the
// attribute in my controller
}
public EnsureUserLoggedIn(ISessionService sessionService)
{
_sessionService = sessionService;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
// Problem: _sessionService is null here
if (_sessionService.LoggedInUser == null)
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
context.Result = new JsonResult("Unauthorized");
}
}
}
And I am decorating my controller like so:
[Route("api/issues"), EnsureUserLoggedIn]
public class IssueController : Controller
{
}
Startup.cs
services.AddScoped<ISessionService, SessionService>();
Using these articles as reference:
ASP.NET Core Action Filters
Action filters, service filters and type filters in ASP.NET 5 and MVC 6
Using the filter as a ServiceFilter
Because the filter will be used as a ServiceType, it needs to be registered with the framework IoC. If the action filters were used directly, this would not be required.
Startup.cs
public void ConfigureServices(IServiceCollection services) {
services.AddMvc();
services.AddScoped<ISessionService, SessionService>();
services.AddScoped<EnsureUserLoggedIn>();
...
}
Custom filters are added to the MVC controller method and the controller class using the ServiceFilter attribute like so:
[ServiceFilter(typeof(EnsureUserLoggedIn))]
[Route("api/issues")]
public class IssueController : Controller {
// GET: api/issues
[HttpGet]
[ServiceFilter(typeof(EnsureUserLoggedIn))]
public IEnumerable<string> Get(){...}
}
There were other examples of
Using the filter as a global filter
Using the filter with base controllers
Using the filter with an order
Take a look, give them a try and see if that resolves your issue.
Hope this helps.
Global filters
You need to implement IFilterFactory:
public class AuthorizationFilterFactory : IFilterFactory
{
public bool IsReusable => false;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
// manually find and inject necessary dependencies.
var context = (IMyContext)serviceProvider.GetService(typeof(IMyContext));
return new AuthorizationFilter(context);
}
}
In Startup class instead of registering an actual filter you register your filter factory:
services.AddMvc(options =>
{
options.Filters.Add(new AuthorizationFilterFactory());
});
One more way for resolving this problem. You can get your service via Context as in the following code:
public override void OnActionExecuting(ActionExecutingContext context)
{
_sessionService = context.HttpContext.RequestServices.GetService<ISessionService>();
if (_sessionService.LoggedInUser == null)
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
context.Result = new JsonResult("Unauthorized");
}
}
Please note that you have to register this service in Startup.cs
services.AddTransient<ISessionService, SessionService>();
Example
private ILoginService _loginService;
public override void OnActionExecuting(ActionExecutingContext context)
{
_loginService = (ILoginService)context.HttpContext.RequestServices.GetService(typeof(ILoginService));
}
Hope it helps.
After reading this article ASP.NET Core - Real-World ASP.NET Core MVC Filters (Aug 2016) I implemented it like this:
In Starup.cs / ConfigureServices:
services.AddScoped<MyService>();
In MyFilterAttribute.cs:
public class MyFilterAttribute : TypeFilterAttribute
{
public MyFilterAttribute() : base(typeof (MyFilterAttributeImpl))
{
}
private class MyFilterAttributeImpl : IActionFilter
{
private readonly MyService _sv;
public MyFilterAttributeImpl(MyService sv)
{
_sv = sv;
}
public void OnActionExecuting(ActionExecutingContext context)
{
_sv.MyServiceMethod1();
}
public void OnActionExecuted(ActionExecutedContext context)
{
_sv.MyServiceMethod2();
}
}
}
In MyFooController.cs :
[MyFilter]
public IActionResult MyAction()
{
}
Edit: Passing arguments like [MyFilter("Something")] can be done using the Arguments property of the TypeFilterAttribute class: How do I add a parameter to an action filter in asp.net? (rboe's code also shows how to inject things (the same way))
While the question implicitly refers to "filters via attributes", it is still worth highlighting that adding filters "globally by type" supports DI out-of-the-box:
[For global filters added by type] any constructor dependencies will be populated by dependency injection (DI). Adding a filter by type is equivalent to filters.Add(new TypeFilterAttribute(typeof(MyFilter))).
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#dependency-injection
With regards to attribute-based filters:
Filters that are implemented as attributes and added directly to controller classes or action methods cannot have constructor dependencies provided by dependency injection (DI). This is because attributes must have their constructor parameters supplied where they're applied. This is a limitation of how attributes work.
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#dependency-injection
However, as mentioned in the previous answers to the OP, there are ways of indirection that can be used to achieve DI. For the sake of completeness, here are the links to the official docs:
ServiceFilterAttribute
TypeFilterAttribute
IFilterFactory implemented on your attribute
I have created an ASP.NET Core 1.0.1 WebApi project and am trying to initialize an injected dependency with some custom options before using it in my controllers. After searching online I found a few articles (here, here and here explaining how to use IConfigureServices to do just this. Seems pretty simple! Unfortunately, I can't get it to work and I can't figure out why, I'm sure it must be a simple oversight..
I have created a simple project, and added the following classes to illustrate the most basic scenario:
public class Tester
{
public void Initialize(TestOptions options)
{
//do something with options.
}
}
public class TestConfigurator : IConfigureOptions<TestOptions>
{
private Tester _tester;
public TestConfigurator(Tester tester)
{
_tester = tester;
}
public void Configure(TestOptions options)
{
_tester.Initialize(options);
}
}
public class TestOptions
{
}
The 'Tester' class gets injected into the constructor of a Controller class:
[Route("api/[controller]")]
public class ValuesController : Controller
{
public ValuesController(Tester tester)
{
//do something with tester..
}
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
Finally, I have added the following configuration in ConfigureServices of the Startup class:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddOptions();
services.AddMvc();
services.AddSingleton<Tester, Tester>();
services.AddSingleton<IConfigureOptions<TestOptions>, TestConfigurator>();
}
When I run the project and call the 'api/values' call, the Tester class is created and injected into the ValuesController, but the TestConfigurator class never gets constructed and so the class never gets Initialized with the options class. What am I missing?
UPDATE
The answers below are of course all valid to this simplified example. I realize now that I oversimplified a bit, as the dependency I'm using (illustrated here as Tester) is from a 3rd party library, so I don't have the constructor to play with. Wrapping the 3rd party class in an extended class will do the trick, but if anybody has an alternative suggestion on manipulating it without modifying its constructor, then I'm still open to suggestions, thanks.
Ok, now I got it, I feel silly for all the edits.
you are using IOptions wrong, and it got me all confused.
implementing a custom IConfigurationOptions<> gives you the abilty to either configure your options from database, or to just use a different class (instead of a lambda)
what you are trying to do, is instantiate a Tester class based on those options, this is fine - but it's not the IConfigureOptions<> job.
in order to initialize your Tester class based on the TestOptions you should create a constructor on the Tester class that receives it like this
public class Tester
{
public Tester(IOptions<TestOptions> options)
{
//do something with options.
}
}
and what you are trying to do will work.
Taken from Asp.Net Core Configuration Documentation and adapted to your example
Assuming
public class TestOptions {
public string SomeOption { get; set; }
}
Options can be injected into your application using the
IOptions<TOptions> accessor service.
You could try abstracting Tester and registering that with the service collection.
public interface ITester {
//tester contract
}
public class Tester : ITester {
public Tester(IOptions<TestOptions> options) {
//do something with test options.
}
}
To setup the IOptions<TOptions> service you call the AddOptions
extension method during startup in your ConfigureServices method.
You configure options using the Configure<TOptions> extension method.
You can configure options using a delegate or by binding your options
to configuration:
public void ConfigureServices(IServiceCollection services) {
services.AddApplicationInsightsTelemetry(Configuration);
// Setup options with DI
services.AddOptions();
// Configure TestOptions using config by installing Microsoft.Extensions.Options.ConfigurationExtensions
services.Configure<TestOptions>(Configuration);
// Configure TestOptions using code
services.Configure<TestOptions>(testOptions => {
testOptions.SomeOption = "value1_from_action";
});
// Add framework services.
services.AddMvc();
// Add your services.
services.AddSingleton<ITester, Tester>();
}
And finally just refactor the controller to depend on the abstraction instead of the concretion.
[Route("api/[controller]")]
public class ValuesController : Controller {
public ValuesController(ITester tester) {
//do something with tester..
}
// GET api/values
[HttpGet]
public IEnumerable<string> Get() {
return new string[] { "value1", "value2" };
}
}
Where is TestOptions supposed to come from? Are you trying to get it mapped auto-magically from your settings file? I think you are over-thinking how this should work, unless there is some reason you have to use initialize instead of constructor injection.
All you are trying to do is make some options available to tester, right?
If so, just use the basic IOptions features - no need to go advanced with IConfigureOptions
public class Tester
{
private TestOptions _options;
public Tester(IOptions<TestOptions> options)
{
_options = options.Value;
}
}
// don't need this anymore
/* public class TestConfigurator : IConfigureOptions<TestOptions>
{
private Tester _tester;
public TestConfigurator(Tester tester)
{
_tester = tester;
}
public void Configure(TestOptions options)
{
_tester.Initialize(options);
}
}
*/
public class TestOptions
{
}
And then configure the options using one of the two methods below (depending on whether it comes from config or has to be manually constructed).
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddOptions();
services.AddMvc();
services.AddSingleton<Tester, Tester>();
// Configure TestOptions using config
services.Configure<TestOptions>(Configuration);
// Configure MyOptions using code
services.Configure<TestOptions>(testOptions =>
{
// initialize them here, e.g. testOptions.Foo = "Bar"
});
}
As the title says. I'm creating a Web API and in my API controller, I'm trying to declare a repository in the constructor. I successfully declare it, but every API method I try to call in that controller returns a 500 error. When I remove the constructor/repository variable, I have no issues.
Controller
[Route("api/[controller]")]
public class TestController: Controller
{
private ITestRepository _testRepository;
public TestController(ITestRepository testRepository)
{
_testRepository= testRepository;
}
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services
.AddMvcCore()
.AddJsonFormatters()
.AddApiExplorer();
services.AddScoped<ITestRepository , TestRepository >();
services.AddSwaggerGen();
}
Am I missing something?
Short Answer
I'm trying to declare a repository in the constructor. I successfully declare it, but every API method I try to call in that controller returns a 500 error. When I remove the constructor/repository variable, I have no issues.
You probably need to make one of two changes:
remove the parameters from the repository's constructor, or
register the services that the repository's constructor takes.
Explanation
The exact code from your question works with the following repository code.
public interface ITestRepository { }
public class TestRepository : ITestRepository { }
The code throws a 500 error, though, if the constructor takes a parameter.
public class TestRepository : ITestRepository
{
public TestRepository(object someObject)
{
}
}
It throws with that constructor, because a call to services.AddScoped<ITestRepository, TestRepository>() requires that the TestRepository constructor meets one of these two criteria.
a constructor without parameters, or
a constructor that takes resolvable services.
So to fix your code you need to make one of two changes:
remove the parameters from the constructor, or
register the services that your constructor takes.
For instance, if the repository takes a DbContext in its constructor, then your code might look like this.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore()
.AddJsonFormatters()
.AddApiExplorer();
services
.AddEntityFramework()
.AddInMemoryDatabase()
.AddDbContext<TestDbContext>(); // register a service
services.AddScoped<ITestRepository, TestRepository>();
services.AddSwaggerGen();
}
TestRepository.cs
public class TestRepository : ITestRepository
{
// pass the registered service to the ctor
public TestRepository(TestDbContext testDbContext)
{
}
}
First we register the dependent component using Microsoft.practices.Unity, and second we resolve them where we are to use them.
You have not resolved your dependency before using it.
public class TestController: Controller
{
private ITestRepository _testRepository;
public TestController(ITestRepository testRepository)
{
_testRepository= testRepository;
}
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
Registering here:
DIContainer.Instance.RegisterType<ITagManager, TagManager>();
We resolve our dependencies before using them.
DIContainer.Instance.Resolve<ITagManager>().RetrieveTwitterTags();