is there a way to access metadata from controller methods?
For example, I add metadata to a controller class with SetMetadata() - e.g. from a decorator.
I know how to access metadata in a guard. You need to inject reflector and guard.canActivate() has ExecutionContext parameter.
canActivate(context: ExecutionContext): boolean {
metadata: SomeType = this.reflector.get<EnabledFeatures>(SOME_METADATA_KEY, [context.getClass()]);
}
To get metadata I need 2 components: Reflector and ExecutionContext.
I can inject Reflector into controller, but how can I access ExecutionContext from a controller?
Assuming we set some metadata on Controller with #SetMetadata :
#Controller({...})
#SetMetadata('roles', ['admin'])
We can have access to it, by creating our custom param decorator:
export const Roles = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
// get roles metadata from #Controller class
const roles = Reflect.getMetadata('roles', ctx.getClass());
return roles;
},
);
And then we can use it on controller's method :
#Get()
getInfo(#Roles() roles): string {
// roles = ['admin']
//...
}
Some notes
#SetMetadata not a good practice
Usage of #SetMetadata directly is not really a good practice. Prefer to create a specific decorator (for maintenance and readability of code) :
export const SetRoles = (...roles: string[]) => SetMetadata('roles', roles);
...
#Controller({...})
#SetRoles('admin')
export class MyController {...}
Reflect.getMetadata API vs Injector
Even if Reflect.getMetadata is in fact called by Reflector API of NestJS, it could be changed in the future.
So if we want to deal with only public/documented API of NestJS, we can:
use a global guard, which will inject Injector,
get metadata with ExecutionContext
and then set result in Request instance.
An other custom param decorator will retrieve data from Request and return it.
More complicated, but without using direct call to Reflect.getMetadata API.
Related
I am developing a rest application.
Some endpoints require a custom header parameter, not related to authorisation. I created a custom annotation using jax-rs NameBinding. Here is an usage example:
#GET
#RequiresBankHeader
public int get(
#HeaderParam("bank")
#Parameter(ref = "#/components/parameters/banks")
String bank) {
return someService.getSomeInformation();
}
There is a provider that intercepts this call and do some routine using the information in the header parameter.
The problem is that I have to repeat '#HeaderParam("bank") #Parameter(ref = "#/components/parameters/banks") String bank' everywhere, just so it appears in Swagger, even though the service classes do not need it. I was able to at least reuse the parameter definition with ref = "#/components/parameters/banks", and declaring it in the OpenAPI.yml file, that Quarkus merges with generated code very nicely.
But I also want to create and interceptor to dynamically add this do the OpenApi definition whenever RequiresBankHeader annotation is present.
Is there a way to do it?
I dont think you can use interceptors to modify the generated Openapi schema output.
If all methods on a given endpoint require some parameter, you can specify it on class level like so:
#Path("/someendpoint")
public class MyEndpoint {
#HeaderParam("bank")
#Parameter(name = "bank")
String bank;
#GET
public Response getAll() {return Response.ok().build()}
#GET
#Path("{id}")
public Response someMethod(#PathParam("id") String id) {return Response.ok().build();}
}
As mentioned by Roberto Cortez, the MP OpenAPI spec provides a programmatic way to contribute metadata to the openapi.yml file.
It is not possible to detect an annotation in the JAX-RS endpoint definition, but it was good enough to automate what I needed. Since all methods that had the RequiresBankHeader return the same Schema, I was able to hack it like this:
public class OpenApiConfigurator implements OASFilter {
#Override
public Operation filterOperation(Operation operation) {
operation.getResponses().getAPIResponses().values().stream().
map(APIResponse::getContent).
filter(Objects::nonNull).
map(Content::getMediaTypes).
flatMap(mediaTypes -> mediaTypes.values().stream()).
map(MediaType::getSchema).
filter(Objects::nonNull).
map(Schema::getRef).
filter(Objects::nonNull).
filter(ref -> ref.contains("the common response schema")).
findAny().
ifPresent(schema -> {
ParameterImpl parameter = new ParameterImpl();
parameter.setRef("#/components/parameters/banks");
operation.addParameter(parameter);
});
return operation;
}
OpenApiConfigurator should be configure in the application properties, using mp.openapi.filter=com.yourcompany.OpenApiConfigurator
My ASP.NET MVC application uses Dependency Injection to inject services to the controllers.
I need to find some way of passing run-time data to the services, because as far as I know it's anti-pattern to send run-time data to the constructors using DI.
In my case I have four different services that all rely on access tokens, which can be re-used between the services. However, that access token can expire so something needs to take care of issuing new access token when it expires.
The services (independent NuGet packages) are all clients for various services, that require access token for every request made. One example would be the AddUserAsync method in the IUserServiceBusiness, it basically POSTs to an endpoint with JSON data and adds Authorization header with bearer access token.
My current solution is to accept access token as a parameter in all of the methods in the services, which means that the web application takes care of handling the access tokens and passing them when needed.
But this solution smells, there has to be a better way of doing this.
Here's an example on how it's done currently.
The RegisterContainer method where all of the implementations are registered.
public static void RegisterContainers()
{
// Create a new Simple Injector container
var container = new Container();
container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
SSOSettings ssoSettings = new SSOSettings(
new Uri(ConfigConstants.SSO.FrontendService),
ConfigConstants.SSO.CallbackUrl,
ConfigConstants.SSO.ClientId,
ConfigConstants.SSO.ClientSecret,
ConfigConstants.SSO.ScopesService);
UserSettings userSettings = new UserSettings(
new Uri(ConfigConstants.UserService.Url));
ICacheManager<object> cacheManager = CacheFactory.Build<object>(settings => settings.WithSystemRuntimeCacheHandle());
container.Register<IUserBusiness>(() => new UserServiceBusiness(userSettings));
container.Register<IAccessTokenBusiness>(() => new AccessTokenBusiness(ssoSettings, cacheManager));
container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
container.RegisterMvcIntegratedFilterProvider();
container.Verify();
DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
}
Implementation of IUserBusiness and IAccessTokenBusiness are injected to AccountController.
private readonly IUserBusiness _userBusiness;
private readonly IAccessTokenBusiness _accessTokenBusiness;
public AccountController(IUserBusiness userBusiness, IAccessTokenBusiness accessTokenBusiness)
{
_userBusiness = userBusiness;
_accessTokenBusiness = accessTokenBusiness;
}
Example endpoint in AccountController that updates the user's age:
public ActionResult UpdateUserAge(int age)
{
// Get accessToken from the Single Sign On service
string accessToken = _accessTokenBusiness.GetSSOAccessToken();
bool ageUpdated = _userBusiness.UpdateAge(age, accessToken);
return View(ageUpdated);
}
And here are some ideas that I've thought of:
Pass the access token to the services with a setter, in the constructor of the controllers. For example:
public HomeController(IUserBusiness userBusiness, IAccessTokenBusiness accessTokenBusiness)
{
_userBusiness = userBusiness;
_accessTokenBusiness = accessTokenBusiness;
string accessToken = _accessTokenBusiness.GetAccessToken();
_userBusiness.setAccessToken(accessToken);
}
I donĀ“t like this idea because then I would have to duplicate this code in every controller.
Pass the access token with every method on the services (currently doing this). For example:
public ActionResult UpdateUser(int newAge)
{
string accessToken = _accessTokenBusiness.GetAccessToken();
_userBusiness.UpdateAge(newAge, accessToken);
}
Works, but I don't like it.
Pass implementation of IAccessTokenBusiness to the constructor of the services. For example:
IAccessTokenBusiness accessTokenBusiness = new AccessTokenBusiness();
container.Register<IUserBusiness>(() => new IUserBusiness(accessTokenBusiness));
But I'm unsure how I would handle caching for the access tokens. Perhaps I can have the constructor of AccessTokenBusiness accept some generic ICache implementation, so that I'm not stuck with one caching framework.
I would love to hear how this could be solved in a clean and clever way.
Thanks!
As I see it, the requirement of having this access token for communication with external services is an implementation detail to the class that actually is responsible of calling that service. In your current solution you are leaking these implementation details, since the IUserBusiness abstraction exposes that token. This is a violation of the Dependency Inversion Principle that states:
Abstractions should not depend on details.
In case you ever change this IUserBusiness implementation to one that doesn't require an access token, it would mean you will have to make sweeping changes through your code base, which basically means you voilated the Open/close Principle.
The solution is to let the IUserBusiness implementation take the dependency on IAccessTokenBusiness itself. This means your code would look as follows:
// HomeController:
public HomeController(IUserBusiness userBusiness)
{
_userBusiness = userBusiness;
}
public ActionResult UpdateUser(int newAge)
{
bool ageUpdated = _userBusiness.UpdateAge(newAge);
return View(ageUpdated);
}
// UserBusiness
public UserBusiness(IAccessTokenBusiness accessTokenBusiness)
{
_accessTokenBusiness = accessTokenBusiness;
}
public bool UpdateAge(int age)
{
// Get accessToken from the Single Sign On service
string accessToken = _accessTokenBusiness.GetSSOAccessToken();
// Call external service using the access token
}
But I'm unsure how I would handle caching for the access tokens.
This is neither a concern of the controller nor the business logic. This is either a concern of the AccessTokenBusiness implementation or a decorator around IAccessTokenBusiness. Having a decorator is the most obvious solution, since that allows you to change caching independently of generation of access tokens.
Do note that you can simplify your configuration a bit by making use of the container's auto-wiring abilities. Instead of registering your classes using a delegate, you can let the container analyse the type's constructor and find out itself what to inject. Such registration looks as follows:
container.Register<IUserBusiness, UserServiceBusiness>();
container.Register<IAccessTokenBusiness, AccessTokenBusiness>();
ICacheManager<object> cacheManager =
CacheFactory.Build<object>(settings => settings.WithSystemRuntimeCacheHandle());
container.RegisterSingleton<ICacheManager<object>>(cacheManager);
Further more, a decorator for IAccessTokenBusiness can be added as follows:
container.RegisterDecorator<IAccessTokenBusiness, CachingAccessTokenBusinessDecorator>();
As explained in the asp.net core docs you can configure a custom provider for request localization. As stated in the docs:
Suppose you want to let your customers store their language and culture in your databases. You could write a provider to look up these values for the user.
For that the following code snippet is provided in the docs and also in the github sample Localization.StarterWeb:
services.Configure<RequestLocalizationOptions>(options => {
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("fr")
};
options.DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
options.RequestCultureProviders.Insert(0, new CustomRequestCultureProvider(async context =>
{
// My custom request culture logic
// DbContext needed here <--
return new ProviderCultureResult("en");
}));});
Can anybody explain me how to inject a DbContext to load the user specific language from DB in the above function?
Well, you can't inject it via constructor because you need to instantiate it during ConfigureServices method and the container isn't available at this point.
Instead you can resolve via HttpContext.
public class CustomRequestCultureProvider : RequestCultureProvider
{
// Note we don't inject any dependencies into it, so we can safely
// instantiate in ConfigureServices method
public CustomRequestCultureProvider() { }
public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
var dbContext = httpContext.RequestServices
.GetService<AppDbContext>();
}
}
Be aware though that this may be less than optimal, as you'll have calls to database on every request, so maybe it's worth to abstract this further and use an caching strategy depending on what exactly you want to do with the DbContext.
Usually one should avoid database calls in culture providers, filters etc. for performance reasons
Update:
There is a generic version of GetService<T>, but you need to import the namespace via using Microsoft.Extensions.DependencyInjection;.
My constructor has optional parameters and they seem to mess with the predominant way of doing DI.
constructor(public name:string, public age?:number, private _service:Service);
Typescript understandably doesn't like that I put a non optional parameter behind an optional one, furthermore the service doesn't get injected when the optional parameter isn't set. How do I solve that? I can't put it somewhere else in the constructor since I would be expected setting the service manually.
Is there something like field injection?
#Inject() private _service:Service;
constructor(public name:string, public age?:number);
Should I replace the optional parameters with default values? Any other suggestions?
EDIT:
As discussed below, I tried to inject a service into an object that isn't created by Angular's DI. This doesn't work. Since I can't create this class (model) using DI I now pass the service manually from the class that instantiates this objects.
Just add the #Optional() decorator before the constructor parameter that should only be injected if there was a provider registered.
import { Optional } from '#angular/core';
constructor(public name:string, #Optional() public age:number)
If I understand correctly what you are trying to do, you need a service factory. See here: https://angular.io/docs/ts/latest/guide/dependency-injection.html#factory-provider
Basically,
class MyClass {
constructor(public name:string, private _service:Service, public age?:number){}
}
And then
let myFactory = (_service: Service) => {
return isAgeOk ? new MyClass(name, _service, age) : new MyClass(name, _service);
};
And then you should provide your service like this:
providers: [{ provide: MyClass, useFactory: MyFactory, deps: [Service]}]
I'm having problems defining a function for odata4. The default get would work but I want to require a user parameter so a client set can be determined, other tables are involved so LINQ is required, I also return a DTO instead of the default table info (EF). Below is the code. I get a "Invalid EntitySetPath detected. 'bindingParameter/Client' is not a valid entity set path for procedure 'Default.GetClients'." What am I doing wrong here?
WebApiConfig
public static void Register(HttpConfiguration config)
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<client>("Client").EntityType.HasKey(p => p.int_id);
var function = builder.Function("GetClients");
function.Parameter<string>("user");
function.ReturnsCollectionFromEntitySet<client>("Client");
builder.EntitySet<ClientDTO>("ClientDTO");
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: null,
model: builder.GetEdmModel());
WebApp.Controller
[ODataRoute("GetClients(user={user})")]
[EnableQuery(PageSize=25)]
public IQueryable<ClientDTO> GetClients([FromODataUri] string user)
{
var clients = (from c in db.clients
join ...
If your OData controller is returning the DTO, the function should look like this:
var function = builder.Function("GetClients");
function.Parameter<string>("user");
function.ReturnsCollectionFromEntitySet<ClientDTO>("Client");
With your current setup, your OData route of GetClients says that it is returning a ClientDTO object, but your WebApiConfig is stating you are returning a Client object.
As the Entity Collection being returned is actually the DTO. The part that shows ("Client") is simply how the OData service will report the name of the object to the project consuming the OData service. For my own personal sanity, I typically include DTO as well so I know when I'm using a DTO and when I'm using a direct entity. So in my own setup i'd return ("ClientDTO"), just a personal preference.