Currently, I am using strings to define and consume routes, roles, policies, etc. It is easy to misspell or get the string values wrong, as they are not part of the IDE auto-completion and type checking.
Is there a way to utilize some kind of references or symbols in ASP.NET Core 3.1? Maybe through configuration providers and DI?
What I would like to see is to define these string configuration values once somewhere, and then reference them in various parts of the application?
Why not use constants classes?
public static class RoutingConstants
{
public const string Route1 = "route1";
...
}
And the same thing for any other need
You could then access the constant everywhere like this RoutingConstants.Route1
Authorize with roles:
You can refactor the roles into constants and consume them like this:
public class StaticRoles
{
public const string A = "A";
public const string B = "B";
public const string C = "C";
public const string ABC = "A, B, C";
}
Use these constants like this:
[Authorize(Roles = StaticRoles.ABC)]
[Route("foo")]
public IActionResult Foo()
Consuming routes in views:
When generating links, you can use tag helpers for links which automatically read the Route attribute from the controller method:
<a asp-controller="#nameof(HomeController).Replace("Controller", string.Empty)" asp-action="#nameof(HomeController.Foo)">Foo</a>
You should refactor .Replace("Controller", string.Empty) into a String extension method to reduce code bloat.
Consuming routes in code:
If you want to have the same functionality as the tag helpers in code, you can use the LinkGenerator class which is automatically injected
Use dependency injection to get the reference to the LinkGenerator
public class HomeController : Controller
{
private readonly LinkGenerator linkGenerator;
public HomeController(LinkGenerator linkGenerator)
{
this.linkGenerator = linkGenerator;
}
// ..
}
Inside HomeController, you can then use
linkGenerator.GetPathByAction(
nameof(HomeController.Index),
nameof(HomeController).Replace("Controller", string.Empty))
};
GetPathByAction has a third parameter when the route has parameters as part of the URL:
linkGenerator.GetPathByAction(
nameof(HomeController.Index),
nameof(HomeController).Replace("Controller", string.Empty),
values: new { version = user.Version})
};
Related
I am consuming third party APIs in which I fetch base URL from my configuration file (appsettings.json).
While creating dynamic endpoints I concatenate URL as follows:
$"{BaseUrl}/api/v1/users/{id}/apps"
Now problem is I got feedbacks on this as hardcoding and it is also hard to maintain for future modifications.
Is there any better approach for managing third party APIs endpoint or URLs?
Use static class to manage URLs.
static class ApiRoutes {
private const string Base = "Api"
Private static Class ControllerOne {
public static string Get = Base +"\ControllerOne\{id}"
public static string Post = Base +"\ControllerOne\{id}"
}
Than In your Controller,
You can do something like that:
[httpGet (ApiRoutes.ControllerOne.Get)]
Get (string something) {
/*your code*/
}
[httpPost(ApiRoutes.ControllerOne.Post)]
Post (string something) {
/*Your code*/
}
Then, if you will have the desire to change the URL in the future, it will be much easier!
Really simple I hope. I just want to do the equivalent of
Url.Action("SomeAction", "SomeController", new { id = 3 });
But inside a service class. Not inside a controller class or IActionResult method
In a plain old service class. Because of the service call having all the data I don't want to pass in other information so my service call is nice and clean.
I've come close but nothing seems to work, either that or it cant be done.
I tried to dependency inject this
services.AddScoped<IUrlHelper>(x => x
.GetRequiredService<IUrlHelperFactory>()
.GetUrlHelper(x.GetRequiredService<IActionContextAccessor>().ActionContext));
In my service call I used (DI) this
public AdminService(..., IUrlHelper urlHelper)
so in my service method I could to this
string editUrl = _urlHelper.Action("EditRole", "Admin", new { id = 0 });
which got rid of all the red squiglies but at run time this bit caused me a problem
.GetUrlHelper(x.GetRequiredService<IActionContextAccessor>().ActionContext));
You can inject IUrlHelper interface inside a service class.
public class ServiceClass
{
private readonly IActionContextAccessor _actionContextAccessor;
private readonly IUrlHelperFactory _urlHelperFactory;
public ServiceClass(IActionContextAccessor actionContextAccessor,
IUrlHelperFactory urlHelperFactory,)
{
_actionContextAccessor = actionContextAccessor;
_urlHelperFactory = urlHelperFactory;
}
public string CreateUrl()
{
var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext);
string url = urlHelper.Action("SomeAction", "SomeController");
return url;
}
}
#SMM I had to add this to my startup but otherwise, works, so thank you
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddSingleton<IUrlHelper, UrlHelper>();
Url.Action generates only an url.
#Url.Action("actionName", "controllerName", new { id = id })
Html.ActionLink generates an tag automatically.
I am attempting to verify that a user is authorized via a custom policy. I followed the tutorial at Ode To Code to add this functionality to my controller. From within Visual Studio, the code appears to be correct and utilizing a known overload.
Notice that it says that the overload is an "extension". I didn't take much notice of this until I spent 5 hours today trying to solve the following error:
As you can see, it would appear that the overload I'm attempting to use isn't being utilized. Am I doing something wrong here? Is there something special I have to do to include these extended methods? I've attempted cleaning and rebuilding the solution but this hasn't solved the problem.
While you've defined the field for IAuthorizationSerivce, you haven't provided any way for that to be set. You need to define a constructor for the LRController that takes a single parameter of IAuthorizationService, and assign that to the field.
I think there was a definition of that constructor in the tutorial.
Please note the name change: such as the global variable name for IAuthorizationService _authorization has been prefixed with an underscore. Obviously not required, but as a good rule of thumb/good coding standard, IMO. :-)
public class LRController : Controller
{
private readonly IAuthorizationService _authorization;
// you're missing this constructor & this pattern is known as Constructor Dependency Injection
public LRController(IAuthorizationService authorization)
{
_authorization = authorization;
}
public async Task<RedirectToActionResult> Index()
{
var superAdmin = await _authorization.AuthorizeAsync(User, "IsLucky");
//rest of your code here
}
}
EDIT
Additionally, if you wanted/needed to inject other interfaces into this controller, you would add it to that LRController constructor. Would look something like this:
public class LRController : Controller
{
private readonly IAuthorizationService _authorization;
private readonly IOtherService _otherService;
public LRController(IAuthorizationService authorization, IOtherService otherService)
{
_authorization = authorization;
_otherService = otherService;
}
public async Task<RedirectToActionResult> Index()
{
var superAdmin = await _authorization.AuthorizeAsync(User, "IsLucky");
}
public async Task Foo()
{
await _otherService.Bar();
}
}
I wish to use settings from appsettings.json in my own class.
I have this working well in a controller and in razor. I tried to use the same code as in a controller in my own class:
public class Email
{
private readonly IConfiguration _config;
public Email(IConfiguration config)
{
_config = config;
}
but when I try to call this
Email sendEmail = new Email();
it requires that I provide config as a parameter. Shouldn't the DI system provide (inject) this? In ConfigureServices I have this:
services.AddSingleton(Configuration);
Do I need to register Email class somewhere too? Do I need to call it some different way?
When you use the following code:
Email sendEmail = new Email();
The DI system isn't involved at all - You've taken things into your own hands. Instead, you should add Email to the DI system and then have it injected. e.g.:
services.AddSingleton<Email>(); // You might prefer AddScoped, here, for example.
Then, as an example, if you're accessing Email in a controller, you can have it injected too:
public class SomeController : Controller
{
private readonly Email _email;
public SomeController(Email email)
{
_email = email;
}
public IActionResult SomeAction()
{
// Use _email here.
...
}
}
Essentially, this just means you need to use DI all the way. If you want to provide more details about where you're currently creating your Email class, I can tailor the examples more to that.
It's a bit of an side, but you can also inject dependencies using the [FromServices] attribute inside of an action. Using this means you can skip the constructor and private field approach. e.g.:
public class SomeController : Controller
{
public IActionResult SomeAction([FromServices] Email email)
{
// Use email here.
...
}
}
As you mentioned, you defined a constructor which requires the parameter.
Please check out the concept of Class Constructors.
Injection is design pattern, when we use class and interfaces to implement it, it should still follow the basic Class methodology and concept.
Hope it helps.
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.