I have my API route attribute class like this
public class MyRouteAttribute : RouteAttribute
{
private const string BaseRoute = "api/default";
private const string PrefixRouteBase = BaseRoute + "/";
public MyRouteAttribute() : base(BaseRoute)
{
}
public MyRouteAttribute(string route):
base(string.IsNullOrEmpty(route) ?
BaseRoute : PrefixRouteBase + route)
{
}
}
And it is used in controller like this
[MyRoute]
public class MyController : Controller
{
.....
}
How do I pass IOptions to MyRoute if I have to make the route configurable?
For example, if I do this:
public class MyRouteAttribute : RouteAttribute
{
private const string BaseRoute = "api/default";
public MyRouteAttribute(IOptions<ApiRouteBaseConfiguration> routeOptions) :
base(routeOptions.Value.Url)
{
}
public MyRouteAttribute(IOptions<ApiRouteBaseConfiguration> routeOptions, string route):
base(string.IsNullOrEmpty(route) ? (routeOptions.Value.Url: $"{routeOptions.Value.Url}/" + route)
{
}
}
Then I get error here [MyRoute] asking me to pass IOptions.
How do I access configuration in MyRoute attribute
Attribute instances are created by CLR when attributes are requested from Reflection routines. You have no way to force instantiation of attributes via any DI container.
I see two possible approaches to workaround your challenge. Both of them allow you to have configurable attribute, however configuration is set not via attribute constructor.
The simpler way is to set configuration through static property loaded on application startup:
public class MyRouteAttribute : RouteAttribute
{
public static ApiRouteBaseConfiguration RouteConfiguration { get; } = new ApiRouteBaseConfiguration();
public MyRouteAttribute() :
base(RouteConfiguration.Url)
{
}
public MyRouteAttribute(string route) :
base(string.IsNullOrEmpty(route) ? RouteConfiguration.Url : $"{RouteConfiguration.Url}/" + route)
{
}
}
Configuration (Configuration section is named "Routing" here):
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
Configuration.Bind("Routing", MyRouteAttribute.RouteConfiguration);
}
Well, this solution is not perfect because of static property usage. However it's quite simple and should do the trick.
The second possible approach - use Property Injection pattern for attribute configuration and set it in custom implementation of IApplicationModelProvider. Such approach is described in this answer, I will not duplicated the code here.
Related
I have a custom action filter that takes in a property but I need the property to come from my appsettings.json file. I pass my configuration into the controller, but when I try to pass in the "_config.GetValue< string >("myString")" the "_config" is red underlined with the message:
An object reference is required for the non-static field, method, or property 'MyController._config'
Action Filter
public class MyActionFilter : ActionFilterAttribute
{
public string Property1 { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
...
}
}
Controller
[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
private readonly IConfiguration _config;
public MyController(IConfiguration config) {
_config = config;
}
[Authorize]
[HttpPost(Constants.ActionName.MyMethod, Name = nameof(MyMethod))]
[MyActionFilter(Property1 = _config.GetValue<string>("myString"))] // Breaks here!
public ActionResult<string> MyMethod()
{
...
}
}
How can I do this? Or at least, how can I avoid hardcoding a value for my action filter properties?
Your current approach does not work because constructor parameters and properties of attributes are evaluated at compile time.
You could use the service locator pattern in your filter like so:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var config = filterContext.HttpContext.RequestServices.GetService<IConfiguration>();
string Property1 = config.GetValue<string>("myString");
}
However, this approach is debatable because the service locator pattern is considered an anti-pattern.
Another approach is to use a ServiceFilter. First, create a constructor for the action filter:
public MyActionFilter(string property1)
{
Property1 = property1;
}
Second, change the action filter to a service filter in the controller:
[ServiceFilter(typeof(MyActionFilter))]
public ActionResult<string> MyMethod()
{
...
}
Third, register the action filter:
builder.Services.AddScoped(p => new MyActionFilter(p.GetService<IConfiguration>().GetValue<string>("myString")));
Here is a great blogpost about the topic: Dependency Injection in action filters in ASP.NET Core.
I have a library class that accepts an IOptions parameter in its constructor as below:
public class MyClass{
MySetting _setting;
public MyClass(MySetting setting){
_setting = setting;
}
public void DoSomeThings(){
//...
}
}
now I want to create an IServiceCollection Extension to facilitate the setup of my library in user code but I am not certain about the best way to inject MySetting into the Constructor.
I can Rely on users to use this code in their startup.cs:
services.Configure<MySettings>(Configuration.GetSection("MySetting"));
or I can inject MySetting into my Extention method as a parameter like this:
public static IServiceCollection AddMtService<T>(this IServiceCollection services, MySetting setting)
Or I can read the appsetting.json file in my extension method and create the setting inside it.
Which one is the best approach and is there any other option that can be better of these?
You can check in more detail from: link
Basic usage:
Inject IOptions with your config class in your service:
public class TestService : ITestService
{
private readonly MySettings _config;
public TestService(IOptions<MySettings> config)
{
_config = config.Value; // You have the configuration class hire...
}
public void TestFunction()
{
string name = _config.Name;
bool isTrue = _config.IsTrue;
bool isFalse = _config.IsFalse;
}
}
When your config class looks something like this:
public class MySettings
{
public const string ConfigSectionKey = "MySettings";
public string Name { get; set; }
public bool IsTrue { get; set; }
public bool IsFalse { get; set; }
}
Registration of your config class to IOptions:
builder.Services.Configure<MySettings>(builder.Config.GetSection(MySettings.ConfigSectionKey));
And the properties are present in appsettings.json (or where you hold your configuration)
{...
"MySettings": {
"Name": "Tralalaa",
"IsTrue": true,
"IsFalse": false
}
}
I have my main .net core application called AppOne. In its appsettings.json I define which api's it should be able to call. For example:
"ApiSettings": {
"UrlToCall": "http://test",
}
Then there is my intermediate and shared project library, called InfraApp that makes the call itself to the Api.
There might be a second app called AppTwo where the url is different.
Both AppOne and AppTwo reference the InfraApp since the logic is common and call the code in there to make the actual call. However the settings (that specifies which url to call) are specific to the api's themselves and therefore cannot be specified in the InfraApp.
Let's consider only AppOne so far.
Such settings are registered through the Options pattern (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-3.0) into the startup.cs:
services.AddOptions();
var apiSettings = Configuration.GetSection("ApiSettings");
services.Configure<ApiSettings>(apiSettings);
and I have my ApiSettings class:
public class ApiSettings
{
public string UrlToCall { get; set; }
}
what is the correct way to pass such ApiSettings to the InfraApp ? InfraApp doesn't know anything about ApiSettings since this is defined in the AppOne. Should I defined the ApiSettings class into the InfraApp? IMHO sounds wrong because it is something specific about the AppOne api but maybe I am thinking in the wrong way. Thanks!
I think, if you have something like this:
public interface IApiSettings
{
string UrlToCall { get; set; }
}
public class ApiSettings:IApiSettings
{
public string UrlToCall { get; set; }
public ApiSettings()
{
...
Console.WriteLine($"ApiSettings");
...
}
}
public class IInfraApp{}
public class InfraApp : IInfraApp
{
private IApiSettings _ApiSettings;
//using Microsoft.Extensions.Options:
public InfraApp(IOptions<ApiSettings> settings)
{
_ApiSettings = (IApiSettings)settings.Value;
Console.WriteLine($"InfraApp {_ApiSettings.UrlToCall}");
}
}
then, you can add/register, something along these lines:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
...
...
//
services.Configure<ApiSettings>(Configuration);
services.AddTransient<IInfraApp, InfraApp>();
...
...
//get instance of infraapp:
var provider = services.BuildServiceProvider();
var infraapp = provider.GetService<IInfraApp>();
//
...
...
}
My Startup is like this :
public void ConfigureServices(IServiceCollection services)
{
// code here
Bootstraper.Setup(services);
}
And my Bootstraper class is like this :
public static partial class Bootstraper
{
// code here
public static IServiceCollection CurrentServiceCollection { get;set;}
public static IServiceProvider CurrentServiceProvider
{
get { return CurrentServiceCollection.BuildServiceProvider(); }
}
public static void Setup(IServiceCollection serviceCollection)
{
// code here
SetupLog();
InitializeCulture();
InitializeDbContexts();
RegisterDataModelRepositories();
}
and this is content of my RegisterDataModelRepositories():
CurrentServiceCollection.AddTransient<IDefAccidentGroupRepository>(p => new DefAccidentGroupRepository(ApplicationMainContextId));
CurrentServiceCollection.AddTransient<IDefGenderRepository>(p => new DefGenderRepository(ApplicationMainContextId));
in short : I just want to be able to use Service Locator in my methods without resolving dependency in class constructor ... is there any way around it ....
Dependency injection can also be done on a by action basis.
Referece Dependency injection into controllers: Action Injection with FromServices
Sometimes you don't need a service for more than one action within your controller. In this case, it may make sense to inject the service as a parameter to the action method. This is done by marking the parameter with the attribute [FromServices]
public IActionResult SomeAction([FromServices] IReportService reports) {
//...use the report service for this action only
return View();
}
Just make sure that the required services are registered with the service collection.
services.AddTransient<IDefAccidentGroupRepository>(p => new DefAccidentGroupRepository(ApplicationMainContextId));
services.AddTransient<IDefGenderRepository>(p => new DefGenderRepository(ApplicationMainContextId));
services.AddTransient<IReportService, ReportService>().
well , thanks for your help ...
There is a easier and better way for it , I just need to add another Service that use these repository and then resolve that service in my controller and let Asp.net Core 2.0 DI to solve the problem for me ...
public interface IActionService
{
IRepositoryA repA {get;set;}
IRepositoryB repB { get;set;}
DoTaskX();
DoTaskY();
}
then in my ActionService :
public class ActionService : IActionService
{
public IRepositoryA repA {get;set;}
public IRepositoryB repB { get;set;}
public ActionService (IRepositoryA rep_a , IRepositoryB rep_b ) {
repA = rep_a;
repB = rep_b;
}
DoTaskX(){
// do task using repository A and B
}
}
then I register IActionService in Startup.cs and resolve itin my ActionController and life become easier and code become cleaner ...
the solution was easy but I had to change my mindset to solve the problem ...
I'musing ASP.NET Web API and I need to have authorization so I've created custom authorization attribute
public class CustomAuthorizationAttribute : AuthorizeAttribute
In order to inject dependency inside constructor I have following :
public CustomAuthorizationAttribute(IAccountBL accountBl)
{
_accountBL = accountBl;
}
In IAccountBL I have method which interacts with database checking if user is authorized to make request.
Inside Member API controller I've register that attribute
[CustomAuthorization]
public class MemberController : ApiController
But I get following error
Project.Account.AccountBL' does not contain a constructor that takes 0 arguments
And if I register it like
[CustomAuthorization(IAccountBL)]
Thank you
Action filters are just attributes. You do not have control over when those attributes are instantiated by the CLR. One possibility is to write a marker attribute:
public class CustomAuthorizationAttribute : Attribute { }
and then the actual action filter:
public class CustomAuthorizationFilter : ActionFilterAttribute
{
private readonly IAccountBL accountBL;
public CustomAuthorizationFilter(IAccountBL accountBL)
{
this.accountBL = accountBL;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes<CustomAuthorizationAttribute>().Any() ||
actionContext.ActionDescriptor.GetCustomAttributes<CustomAuthorizationAttribute>().Any())
{
// here you know that the controller or action is decorated
// with the marker attribute so that you could put your code
}
}
}
and finally register it as a global action filter:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
...
IAccountBL accountBL = ...
config.Filters.Add(new CustomAuthorizationFilter(accountBL));
}
}
and finally you could use the marker attribute:
[CustomAuthorization]
public class MemberController : ApiController
{
...
}
You can get dependency in your filter by using extension method GetDependencyScope for class HttpRequestMessage. It's not a canonical way for dependency injection, but can be used as workaround. A basic example may look like this:
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
var dependencyScope = context.Request.GetDependencyScope();
var dependency = dependencyScope.GetService(typeof (MyDependencyType));
//use your dependency here
}
This method may be used with constructor injection to simplify unit testing:
public class MyAuthenticationFilter : Attribute, IAuthenticationFilter
{
private Func<HttpRequestMessage, MyDependencyType> _dependencyFactory;
public MyAuthenticationFilter() :
this(request => (MyDependencyType)request.GetDependencyScope().GetService(typeof(MyDependencyType)))
{
}
public MyAuthenticationFilter(Func<HttpRequestMessage, MyDependencyType> dependencyFactory)
{
_dependencyFactory = dependencyFactory;
}
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
var dependencyScope = context.Request.GetDependencyScope();
var dependency = dependencyFactory.Invoke(context.Request);
//use your dependency here
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public bool AllowMultiple { get; private set; }
}
If anyone finds similar issue here's how I manage to solve it.
My custom filter inherits IAutofacAuthorizationFilter. Besides this one you can also inherit IAutofacExceptionFilter and IAutofacActionFilter.
And inside my DI container I've register this filter for each controller I want to use like this
builder.Register(c => new CustomAuthorizationAttribute(c.Resolve<IAccountBL>()))
.AsWebApiAuthorizationFilterFor<MemberController>()
.InstancePerApiRequest();
If you registered your service on the application using any container, it's very easy to get the instance of your service from anywhere in the scope. Just follow the below code to get your service.
var myService = DependencyResolver.Current.GetService(typeof(IMyService)) as IMyService;
Please make sure you have included System.Web.Mvc in the file.
Happy coding!!!