Use .net core dependencies in extension methods - c#

I have created extension methods for IUrlHelper.
public static class UrlHelperExtensions
{
public static string JavaScript(this IUrlHelper helper, string contentPath, IOptions<TypeScriptOptions> tsOptions)
{
if (tsOptions.Value != null && tsOptions.Value.Minify)
{
contentPath = Path.ChangeExtension(contentPath, ".min.js");
}
return helper.Content(contentPath);
}
public static string Css(this IUrlHelper helper, string contentPath, IOptions<LessOptions> lessOptions)
{
if (lessOptions.Value != null && lessOptions.Value.Minify)
{
contentPath = Path.ChangeExtension(contentPath, ".min.css");
}
return helper.Content(contentPath);
}
}
I would like to pass IOptions<TypeScriptOptions> tsOptions and IOptions<LessOptions> lessOptions to the methods using .NET Core's dependency injection.
In a Razor view I have the following:
#inject IOptions<CssOptions> lessOptions
<link href="#Url.Css("~/css/site.css", lessOptions)" rel="stylesheet" asp-append-version="true">
But I would simply like to do:
<link href="#Url.Css("~/css/site.css")" rel="stylesheet" asp-append-version="true">
I've tried looking at the .NET Core docs and I've done a few Google searches but I can't seem to find a way to achieve what I want without resorting to Tag Helpers which isn't something I want to do.
How can I get this to work?

As #Romoku said, extension methods are static methods and only take state as argument (or from static state).
You either need to keep using the strategy you have, passing it as an argument. Or drop the idea of extension method and create some helper class or service that gets resolved via DI:
public class UrlHelperService
{
private IOptions<CssOptions> _cssOptions;
private IOptions<JavaScriptOptions> _jsOptions;
private IUrlHelper _urlHelper;
public UrlHelperService(
IOptions<CssOptions> cssOptions,
IOptions<JavaScriptOptions> jsOptions,
IUrlHelper urlHelper)
{
_cssOptions = cssOptions;
_jsOptions = jsOptions;
_urlHelper = urlHelper;
}
public string JavaScript(string contentPath)
{
if (_jsOptions.Value != null && _jsOptions.Value.Minify)
{
contentPath = Path.ChangeExtension(contentPath, ".min.js");
}
return _urlHelper.Content(contentPath);
}
public string Css(string contentPath)
{
if (_cssOptions.Value != null && _cssOptions.Value.Minify)
{
contentPath = Path.ChangeExtension(contentPath, ".min.css");
}
return _urlHelper.Content(contentPath);
}
}
The container needs this class registered, e.g:
services.AddScoped<UrlHelperService>()
Or whatever lifecycle this type should have.
And the service would be injected in your view instead of the options instances:
#inject UrlHelperService urlHelperService
<link href="#urlHelperService.Css("~/css/site.css")" rel="stylesheet" asp-append-version="true">

Related

How can I get the base route while doing service configuration in ASP.NET Core?

I have a series of services I am configuring in my application and one of those services require a base URL to a specific route so I can create links based on it. So if we have:
My Controller
[Route("api/v1/fancy")]
public class FancyController {
[HttpPost]
[Route("{fancyID}")]
public async Task<IActionResult> SubmitFancy(string fancyID){
// Do fancy stuff
}
}
My business class
public class Business {
private string _baseUrl;
public Business(string baseUrl){
_baseUrl = baseUrl
}
}
My Startup.cs
...
public void ConfigureServices(IServiceCollection services) {
services.AddScoped<Business>(provider => {
Business business = new Business("http://someweb.com/api/v1/fancy"); //TODO:REMOVE Hard Coded
return business;
}
services.AddRazorPages();
}
...
I have tried to use UrlHelper by adding a few more scoped services for IActionContextAccessor and IUrlHelperFactory, but I am getting null on ActionLink and RouteUrl methods, and I am not sure why.
Any ideas as to how I would go about solving this issue?
Please let me know if you need more clarification.
Thank you very much.
Inject a LinkGenerator & IHttpContextAccessor into your service;
public class Business {
private readonly LinkGenerator generator;
private readonly IHttpContextAccessor accessor;
public Business (LinkGenerator generator, IHttpContextAccessor accessor){...}
public void Foo(){
var context = accessor.HttpContext;
var link = generator.GetUriByAction(
context,
"SubmitFancy",
"Fancy",
new { fancyID="..." });
}
}
services.AddScoped<Business>();
You can use LinkGenerator without a reference to a HttpContext, but you'd need to supply the host, scheme and pathBase from somewhere else. Either from configuration, or perhaps by implementing middleware to capture them from the first request.
You can't use a string for an attribute routing. You need a CONSTANT string. Constants are immutable values which are known at compile time and do not change for the life of the program.
But if you need a route to use in ajax or httpclient, it takes several steps to get a string from appsettings.
create AppUrl section in appsettings.json
"AppUrl": {
"BusinessUrl": "http//..",
.... another urls if needed
},
2.Create class for this section
public class AppUrlSettings
{
public string BusinessUrl{ get; set; }
....another urls
}
configure settings in startup
services.Configure<AppUrlSettings>(Configuration.GetSection("AppUrl"));
now you can use them like this
public class MyClass
{
private readonly IOptions<AppUrlSettings> _appUrls;
public MyClass (IOptions<AppUrlSettings> appUrls)
{
_appUrls = appUrls;
}
public string GetBusinessUrl()
{
return _appUrls.Value.BussinesUrl;
}
}

Get user manager at extension class

I'm migrating my ASP.net MVC project to core version. I have an extension class with method, that returns user's name by user id (Guid).
public static class IdentityHelpers
{
public static MvcHtmlString GetUserName(this HtmlHelper html, string id)
{
var manager = HttpContext.Current
.GetOwinContext().GetUserManager<AppUserManager>();
return new MvcHtmlString(manager.FindByIdAsync(id).Result.UserName);
}
}
As I'm rewriting this to .NET Core, I don't know how to get user manager instance here. Normally I would just inject this through DI, but I don't know what to do as I'm using extension method so I can't inject it.
How can I get UserManager in static class?
Things have changed in the new version. Access the current HttpContext via the HtmlHelper.ViewContext and from there you should be able to get access to an IServiceProvider that can be used to resolve services.
public static class IdentityHelpers {
public static MvcHtmlString GetUserName(this HtmlHelper html, string id) {
HttpContext context = html.ViewContext.HttpContext;
IServiceProvider services = context.RequestServices;
var manager = services.GetService<AppUserManager>();
return new MvcHtmlString(manager.FindByIdAsync(id).Result.UserName);
}
}

ASPNET CORE Dependency Injection in an attribute class

I am trying to develop a DisplayName Attribute which has an interface for localization service, which is already registered at startup and working if injected in a constructor.
How can I get the localization service interface to be instantiated since I cant use a construction injection?
This is my code
public class MyDisplayNameAttribute : System.ComponentModel.DisplayNameAttribute
{
private string _resourceValue = string.Empty;
private ILocalizationService _localizationService;
public MyDisplayNameAttribute(string resourceKey)
: base(resourceKey)
{
ResourceKey = resourceKey;
}
public string ResourceKey { get; set; }
public override string DisplayName
{
get
{
_resourceValue = _localizationService.GetLocaleString(ResourceKey);
return _resourceValue;
}
}
public string Name
{
get { return nameof(MyDisplayNameAttribute); }
}
}
Thanks
I hope you could solve the problem, this is a very simple solution but as you knew it's an anti-pattern :
public class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
public LocalizedDisplayNameAttribute(string Name) : base(Name)
{
}
public override string DisplayName
{
get
{
var _localizationService= new HttpContextAccessor().HttpContext.RequestServices.GetService<ILocalizationService>();
return _localizationService.Get(base.DisplayNameValue).Result;
}
}
}
I Hope it helps others at least ;)
Dependency injection is working with invertion of control (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2). So framework controls your app and when instantiating your clsses injects dependencies requested via constructor.
Refer also here How to create a class without constructor parameter which has dependency injection
So I suspect that it is not possible to inject dependency without using constructor.
May be if you describe you intention there may be another good solution.

How to add the slug to all Link generation in an asp.net core website?

I need to be able to control the links being generated by my Url.Content("~") call to be able to accept a Slug in the beginning of the link. Basically the hosting URL will be behind a Load-balancer and may be at the root level or behind a friendlier Url...
As an example:
The site is configured to run under http://localhost:5001, so Url.Content("~/scripts/site.js") will generate "/scripts/site.js"
this is fine if the browser is coming directly to that url or even to an alias such as www.mysite.com.
But i want o be able to have the flexibility to host the site under www.mysite.com/Slug (think certs and such)...
now my link that was generated goes to www.mysite.com/scripts.site.js which resolves to a 404.
Ideally, the slug can be configured in a custom IUrlHelper, or even a custom LinkGenerator, but i cannot seem to inject those and overwrite the current ones.
I've tried:
services.AddScoped<IUrlHelper>(x =>
{
var actionContext = x.GetService<IActionContextAccessor>().ActionContext;
return new MyCustomUrlHelper(actionContext);
});
but was unable to get that injected. When i tried debugging, I noticed that if you call the same command in a controller, you get an instance of Microsoft.AspNetCore.Mvc.Routing.EndpointRoutingUrlHelper instead.
Is there a way to change that without creating a custom helper (because that will be missed in some areas and will make debugging near impossible to find the misused helper)
Binding IUrlHelper directly has no effect, as MVC internally resolves the instance using a factory. To get an instance of your own custom URL helper in your controllers and razor views, you need to provide a custom implementation of IUrlHelperFactory in your startup class.
The following code snippets allow you to decorate the original URL helper with your own functionality:
In your Startup class, you need to add the custom implementation for IUrlHelperFactory with singleton scope after AddMvc:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSingleton<IUrlHelperFactory, CustomUrlHelperFactory>();
}
And the custom implementation could look like this:
public class CustomUrlHelper : IUrlHelper
{
private IUrlHelper _originalUrlHelper;
public ActionContext ActionContext { get; private set; }
public CustomUrlHelper(ActionContext actionContext, IUrlHelper originalUrlHelper)
{
this.ActionContext = actionContext;
this._originalUrlHelper = originalUrlHelper;
}
public string Action(UrlActionContext urlActionContext)
{
return _originalUrlHelper.Action(urlActionContext);
}
public string Content(string contentPath)
{
return _originalUrlHelper.Content(contentPath);
}
public bool IsLocalUrl(string url)
{
return _originalUrlHelper.IsLocalUrl(url);
}
public string Link(string routeName, object values)
{
return _originalUrlHelper.Link(routeName, values);
}
public string RouteUrl(UrlRouteContext routeContext)
{
return _originalUrlHelper.RouteUrl(routeContext);
}
}
public class CustomUrlHelperFactory : IUrlHelperFactory
{
public IUrlHelper GetUrlHelper(ActionContext context)
{
var originalUrlHelperFactory = new UrlHelperFactory();
var originalUrlHelper = originalUrlHelperFactory.GetUrlHelper(context);
return new CustomUrlHelper(context, originalUrlHelper);
}
}
The IUrlHelper is not injectable by default.
You will have to modify your startup.cs code a bit as explained in this blog.
You will have to first register IActionContextAccessor.
Then with the help of UrlHelperFactory, you can inject your custom implementation as shown below:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<IUrlHelper>(x => {
var actionContext = x.GetRequiredService<IActionContextAccessor>().ActionContext;
var factory = x.GetRequiredService<IUrlHelperFactory>();
return factory.GetUrlHelper(actionContext);
});
Both IActionContextAccessor and IUrlHelperFactory live in the Microsoft.AspNetCore.Mvc.Core package.
If you're using the Microsoft.AspNetCore.All metapackage you should have this referenced already.
This should help you to resolve your problem.
Can you not just brute force some flexibility into your solution with string concatenation like this:
public string SlugUrl(string slug, string url, bool tilde = false)
{
if (tilde) then
{
return Url.Content("~" + slug + url);
}
else
{
return Url.Content(slug + url);
}
}
[...]
string slug1 = "www.mysite.com";
string slug2 = "www.mysite.com/Slug";
string trailUrl = "/scripts/site.js";
string result1 = SomeClass.SlugUrl(slug1, trailUrl);
string result2 = SomeClass.SlugUrl(slug2, trailUrl);
string result3 = SomeClass.SlugUrl(slug1, trailUrl, true);
string result4 = SomeClass.SlugUrl(slug2, trailUrl, true);
etc...

Getting Attribute Value on Member Interception

I have this CacheAttribute that accepts Duration Value like such
public class MyTestQuery : IMyTestQuery
{
private readonly ISomeRepository _someRepository;
public TestQuery(ISomeRepository someRepository)
{
_someRepository = someRepository;
}
[Cache(Duration = 10)]
public MyViewModel GetForeignKeysViewModelCache()
{
...code here...
return viewModel;
}
}
The Attribute looks like this
[AttributeUsage(AttributeTargets.Method)]
public class CacheAttribute : Attribute
{
public int Duration { get; set; }
}
When Intercepted using Castle.Proxy.IInterceptor it works but when I perform an Attribute.GetCustomAttribute either by IInvocation.MethodInvocationTarget or IInvocation.Method both returns a null value
Here it is in code
public class CacheResultInterceptor : IInterceptor
{
public CacheAttribute GetCacheResultAttribute(IInvocation invocation)
{
var methodInfo = invocation.MethodInvocationTarget;
if (methodInfo == null)
{
methodInfo = invocation.Method;
}
return Attribute.GetCustomAttribute(
methodInfo,
typeof(CacheAttribute),
true
)
as CacheAttribute;
}
public void Intercept(IInvocation invocation)
{
var cacheAttribute = GetCacheResultAttribute(invocation);
//cacheAttribute is null always
...more code here...
}
}
And this is how I register them
public class Bootstrapper
{
public static ContainerBuilder Builder;
public static void Initialise()
{
Builder = new ContainerBuilder();
...other codes in here...
CacheInstaller.Install();
var container = Builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}
public class CacheInstaller
{
public static void Install()
{
Bootstrapper.Builder.RegisterType<CacheResultInterceptor>()
.SingleInstance();
Bootstrapper.Builder.RegisterAssemblyTypes(Assembly.Load("MyApplication.Web"))
.Where(t => t.Name.EndsWith("Query"))
.AsImplementedInterfaces()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(CacheResultInterceptor))
.SingleInstance();
}
}
My Expensive Method Class Ends with Query
Now the question is why invocation.MethodInvocationTarget and/or invocation.Method returns null?
What am I doing wrong?
Any other strategies so I can pass a parameter value without creating a Method for each value I can think of?
BTW I am using
Autofac 4.3.0.0
Autofac.Extras.DynamicProxy 4.2.1.0
Autofac.Integration.Mvc 4.0.0.0
Castle.Core 4.0.0.0
UPDATE 1
Here is what it returns when it runs for clarity
Here's what I found.
invocation.Method returns the method declaration on the interface, in your case IMyTestQuery.
On the other hand, invocation.MethodInvocationProxy returns the method that is going to be called when invoking invocation.Proceed(). This means it can be:
the next interceptor if you have several
a decorator if you have decorators over your interface
the final implementation of your interface
As you can see, MethodInvocationProxy is less deterministic than Method, which is why I would recommend you avoid using it, at least for what you're trying to achieve.
When you think about it, an interceptor should not be tied to an implementation as it proxies an interface, so why don't you put the [Cache] attribute at the interface level?
Using your code, I could successfully retrieve it when put on the interface.
Edit:
OK, I've put together a repository on GitHub that uses the specific versions of the NuGet packages you mentioned and shows how to retrieve an attribute on intercepted methods.
As a reminder, here are the used NuGet packages:
Microsoft.AspNet.Mvc v5.2.3
Autofac v4.3.0
Autofac.Mvc5 4.0.0
Autofac.Extras.DynamicProxy v4.2.1
Castle.Core v4.0.0
I created 2 query interfaces, IMyQuery and IMySecondQuery. Please note that as mentioned in my original answer, the [Cache] attributes are placed on the interfaces methods, not on the implementing classes.
public interface IMyQuery
{
[Cache(60000)]
string GetName();
}
public interface IMySecondQuery
{
[Cache(1000)]
string GetSecondName();
}
Then we have 2 very basic implementations of these classes. Not relevant at all, but for the sake of completeness:
public class DefaultMyQuery : IMyQuery
{
public string GetName()
{
return "Raymund";
}
}
public class DefaultMySecondQuery : IMySecondQuery
{
public string GetSecondName()
{
return "Mickaƫl Derriey";
}
}
And then the interceptor:
public class CacheResultInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
var cacheAttribute = invocation.Method.GetCustomAttribute<CacheAttribute>();
if (cacheAttribute != null)
{
Trace.WriteLine($"Found a [Cache] attribute on the {invocation.Method.Name} method with a duration of {cacheAttribute.Duration}.");
}
invocation.Proceed();
}
}
Note that the GetCustomAttribute<T> method is an extension method over MemberInfo present in the System.Reflection namespace.
Let's move on to the registration in the Autofac container. I tried to follow you registration style as much as I could:
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly);
builder
.RegisterType<CacheResultInterceptor>()
.SingleInstance();
builder
.RegisterAssemblyTypes(typeof(MvcApplication).Assembly)
.Where(x => x.Name.EndsWith("Query"))
.AsImplementedInterfaces()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(CacheResultInterceptor));
DependencyResolver.SetResolver(new AutofacDependencyResolver(builder.Build()));
The queries are then used in the HomeController:
public class HomeController : Controller
{
private readonly IMyQuery _myQuery;
private readonly IMySecondQuery _mySecondQuery;
public HomeController(IMyQuery myQuery, IMySecondQuery mySecondQuery)
{
_myQuery = myQuery;
_mySecondQuery = mySecondQuery;
}
public ActionResult MyQuery()
{
return Json(_myQuery.GetName(), JsonRequestBehavior.AllowGet);
}
public ActionResult MySecondQuery()
{
return Json(_mySecondQuery.GetSecondName(), JsonRequestBehavior.AllowGet);
}
}
What I did to test this is just put a breakpoint in the interceptor, F5 the application, open a browser and navigate to both http://localhost:62440/home/myquery and http://localhost:62440/home/myquery.
It did hit the interceptor and find the [Cache] attribute. In the Visual Studio Output window, it did show:
Found a [Cache] attribute on the GetName method with a duration of 60000.
Found a [Cache] attribute on the GetSecondName method with a duration of 1000.
Hopefully that helps you pinpoint what's going on in your project.
I pushed changes to the repository so that the first query calls the second one.
It still works. You should really make an effort and put some code on the question.

Categories