Get user manager at extension class - c#

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);
}
}

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;
}
}

How to create a response from a generic class containing HttpContext data in .Net 5 Web API

I've started a new project using .Net 5 (my previous was .Net Framework 4.7). I'm writing a web API project and I want all my controllers/action responses to be of a certain type. This allows me to put some info I want included in every response, such as the current user info (and more stuff too).
My generic response looks like this (I've only left the relevant code):
public class MyResponse<T>
{
public T data { get; set; }
public User user { get; set; }
public MyResponse(T inputData)
{
data = inputData;
}
}
And I set the response on a controller's action this way:
public IActionResult Get()
{
var response = new MyResponse<string>("Hello");
return Ok(response);
}
So the idea is that the response always contains a "data" property with the actual data, and a bunch of other properties with metadata.
The problem is how to include information on the logged in user in .Net 5. In .Net 4.x you could just access HttpContext from anywhere, so you could just populate the User property. But this is not possible in .Net 5
I'm going crazy trying to understand how to achieve this in .Net 5.
The first thing I've tried is DI (which I'm new to, so I might not be understanding this properly).
The first thing I tried is to make my User class depend on IHttpContextAccessor as most documentation points to:
public class User : IIdentity
{
private readonly IHttpContextAccessor _httpContextAccessor;
public User(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
}
and register it this way on startup.cs:
services.AddHttpContextAccessor();
services.AddTransient<User>();
But that doesn't work well, since when I try to create my User class within MyResponse class:
var user = new User(); // This doesn't work, as the constructor requires one argument
So the constructor requires one argument so I can't create the class like that. I (believe) I would need to create the User from the DI container, but I don't have access to that on MyResponse class (or at least I couldn't really understand how to do it or if possible at all).
I could pass the HttpContext from the controller to MyResponse, but that seems plain wrong (plus, there might be other people writing controllers, so I think it's better if they don't explicitly need to pass that to the response, should be handled transparently)
My concrete questions:
Any thoughts of how can I get hold of the HttpContext within my custom response class?
Should I be looking for an alternative option (such as a Middleware or Filter) to generate my response?
Thank you very much.
You could use a factory along with dependency injection.
Create your user class:
using Microsoft.AspNetCore.Http;
using System.Security.Principal;
public class User : IIdentity
{
private IHttpContextAccessor HttpContextAccessor { get; }
public User(IHttpContextAccessor httpContextAccessor)
{
this.HttpContextAccessor = httpContextAccessor;
}
public string AuthenticationType => this.HttpContextAccessor.HttpContext.User.Identity.AuthenticationType;
public bool IsAuthenticated => this.HttpContextAccessor.HttpContext.User.Identity.IsAuthenticated;
public string Name => this.HttpContextAccessor.HttpContext.User.Identity.Name;
}
Use DI to inject factories with the types you want:
services.AddHttpContextAccessor();
services.AddSingleton(a => GetResponse<string>(a));
services.AddSingleton(a => GetResponse<int>(a));
services.AddSingleton(a => GetResponse<decimal>(a));
Func<T, MyResponse<T>> GetResponse<T>(IServiceProvider serviceProvider)
{
var contextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
var user = new User(contextAccessor);
return (data) => new MyResponse<T>(user, data);
}
Then inject it where you want:
namespace WebAppFiles.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class MyController : ControllerBase
{
private Func<int, MyResponse<int>> ResponseFactory { get; }
public MyController(Func<int, MyResponse<int>> responseFactory)
{
this.ResponseFactory = responseFactory;
}
[HttpGet]
public IActionResult Get([FromQuery] int value)
{
return Ok(this.ResponseFactory(value));
}
}
}

Why can't I save object in Session

_httpContext.Session["HistoryviewModel"] = viewModel;
my error is:
Error CS0021 Cannot apply indexing with [] to an expression of type 'ISession'
When you work with an ASP.NET Core application and you need to save in a session an instance of your customized type please consider the following solution. The solution is tested with an ASP.NET Core MVC application:
In the Startup.cs file add in the method Configure for the variable app of type
IApplicationBuilder this code:
app.UseSession();
Register a service for sessions in the Startup.cs file, in the method
ConfigureServices using the services variable:
services.AddSession();
Install using NuGet the package System.Text.Json
In my ASP.NET Core application, in the cs file with MVC controllers, I add:
using System.Text.Json;
using Microsoft.AspNetCore.Http;
In this file, in the namespace with MVC controllers, I add the following type that
will perform the necessary actions to set and get my object from a session:
public static class SessionExtensions
{
public static void SetObjectAsJson<T>(this ISession session, string key, T value)
{
session.SetString(key, JsonSerializer.Serialize<T>(value));
}
public static object GetObjectFromJson<T>(this ISession session, string key)
{
var value = session.GetString(key);
return value == null ? default(T) : JsonSerializer.Deserialize<T>(value);
}
}
Also, I add a custom type that I will save in the session:
public class MyClass {
public int MyInt { get; set; }
public string MyString { get; set; }
}
Now, in the first controller I put to a session an instance of the custom type:
var myComplexObject = new MyClass();
myComplexObject.MyInt = 1;
myComplexObject.MyString = "Hello World!";
HttpContext.Session.SetObjectAsJson<MyClass>("Test", myComplexObject);
In the second controller I get this instance from the session:
var myComplexObject = HttpContext.Session.GetObjectFromJson<MyClass>("Test");
I hope this solution helps.

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...

How to migrate a static class from .NET to .NET Core?

I am migrating a webform (not a WebForm technology, a stripped down Web Application into a webform functionality) from ASP.NET MVC to ASP.NET Core MVC. My current biggest problem is a static class that we had in the previous version of the webform. This static class uses packages that were available in .NET but not in .NET Core.
I understand that for some of the methods in this static class, I have to use dependency injection to resolve the package problems. However, it is not possible to pass a parameter to a static class making this an "antipattern" for .NET Core.
My Utils.cs static class has only two methods, RenderPartialToString and SendEmail. SendEmail is very simple and has no problems with the current .NET Core packages. However, I have the following code in my static class that does not work with current version.
public static class Utils
{
public static readonly string ApiUrl = ConfigurationManager.AppSettings["ApiUrl"];
public static readonly string ApiKey = ConfigurationManager.AppSettings["ApiKey"];
public static string RenderPartialToString(Controller controller, string viewName, object model)
{
controller.ViewData.Model = model;
using (StringWriter sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
ViewContext viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
viewResult.View.Render(viewContext, sw);
return "document.write('" + sw.GetStringBuilder().Replace('\n', ' ').Replace('\r', ' ').Replace("'","\\'").ToString() + "');";
}
}
...
}
ViewEngine and ConfigurationManager are not available in .NET Core making this static class very difficult to migrate. I believe, I could implement both of these features with dependency injection. However, I do not know how to change this static class so that I can use dependency injection and be able to use these methods in my Controllers.
How can I simply migrate this static class into .NET Core for some dependency injection implementation? Do I need to change all the instances of the Utils class and make it not static?
You should refactor it into an object with non static methods, then register the object with DI services so it can be injected into your controller's constructor or wherever you need it.
I actually have a ViewRenderer class with similar functionality here that I use to generate html email using razor.
I register it with DI like this:
services.AddScoped<ViewRenderer, ViewRenderer>();
Note that my ViewRenderer also has it own constructor dependencies similar to what you need in your static method:
public ViewRenderer(
ICompositeViewEngine viewEngine,
ITempDataProvider tempDataProvider,
IActionContextAccessor actionAccessor
)
{
this.viewEngine = viewEngine;
this.tempDataProvider = tempDataProvider;
this.actionAccessor = actionAccessor;
}
private ICompositeViewEngine viewEngine;
private ITempDataProvider tempDataProvider;
private IActionContextAccessor actionAccessor;
ViewRenderer's constructor dependencies will also be passed into it by dependency injection, so the whole idea is to get away from all the static stuff and let everything be provided by DI.
If I need an instance of ViewRenderer in a Controller I can just add it to the constructor signature of the controller. Actually I don't use it directly in a controller since I use it for email, instead I have an EmailService which depends on ViewRenderer and the controller depends on EmailService
so you want to get to dependency injection all the way down for all dependencies, which is easy if you refactor away from static methods into object instance methods
Although i would go with #Joe's way, there may be another way for your case. To use controller extension method:
// register configuration
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfiguration>(Configuration);
}
public static class ControllerExtension
{
public static string RenderPartialToString(this Controller controller, string viewName, object model)
{
controller.ViewData.Model = model;
var config = controller.HttpContext.RequestServices.GetService<IConfiguration>();
// other stuff
}
}

Categories