I am trying to get client IP and compare values with the configuration. If it matches need to return true/false. How do I make this variable accessible to web application? I am new to .NET core. thanks
I have followed this article to create middleware class but not sure how to pass variable from this context.
https://learn.microsoft.com/en-us/aspnet/core/security/ip-safelist?view=aspnetcore-2.2
public class SafeListMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<SafeListMiddleware> _logger;
private readonly string _adminSafeList;
public SafeListMiddleware(
RequestDelegate next,
ILogger<SafeListMiddleware> logger,
string adminSafeList)
{
_adminSafeList = adminSafeList;
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
if (context.Request.Method != "GET")
{
var remoteIp = context.Connection.RemoteIpAddress;
string[] ip = _adminSafeList.Split(';');
var bytes = remoteIp.GetAddressBytes();
var match = false;
foreach (var address in ip)
{
var testIp = IPAddress.Parse(address);
var rangeA = IPAddressRange.Parse(address);
if(rangeA.Contains(remoteIp))
{
match = true;
break;
}
}
}
await _next.Invoke(context);
}
}
}
I would create an interface such as:
public interface IIPChecker
{
bool IsSafe(IPAddress remoteIpAddress);
}
with an implementation:
public class IPChecker : IIPChecker
{
private readonly IPAddress[] _safeList;
public IPChecker(string safeList)
{
var _safeList = safeList
.Split(';')
.Select(IPAddress.Parse)
.ToArray();
}
public bool IsSafe(IPAddress remoteIpAddress)
{
return _safeList.Contains(remoteIpAddress);
}
}
and inject it in the controllers that need it:
public class ValuesController : ControllerBase
{
private readonly IIPChecker _ipChecker;
public ValuesController(IIPChecker ipChecker)
{
_ipChecker = ipChecker;
}
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
var isValid = _ipChecker.IsSafe(HttpContext.Connection.RemoteIpAddress);
.....
}
}
If you need this information in all controllers, you can change them to inherit from something like
public class IpCheckController : ControllerBase
{
private readonly IIPChecker _ipChecker;
public IpCheckController(IIPChecker ipChecker)
{
_ipChecker = ipChecker;
}
private bool IsSafe => _ipChecker.IsSafe(HttpContext.Connection.RemoteIpAddress);
}
To get the client RemoteIp and compare with the configuration values. you have to first define Http Accessor in the Startup file like below.
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
and then in the Middleware access the RemoteIp using below code and compare the value.
var remoteIp = context.Request.HttpContext.Connection.RemoteIpAddress.ToString();
Related
I want to build in my .net core application an MVC scenario when I can inject in my controller 2 different implementations of an abstract class. These implementations call their external relative API. Maybe the architecture is wrong and therefore I ask you suggestions but first follow me in my thoughts please.
I create a general abstract class. Why abstract? Because the basic way/properties for calling an API is the same for everyone. In my case so far I only have an HttpClient.
public abstract class ApiCaller
{
protected static HttpClient client;
protected static string ApiUrl;
public ApiCaller(string apiUrl)
{
client = new HttpClient();
ApiUrl = apiUrl;
}
public abstract string GetApiResultAsync();
}
Afterwards I will have my two different classes Api1Service and Api2Service that extend ApiCaller and will have their own different ways of calling their relative APIs.
public class Api1Service : ApiCaller
{
public Api1Service(string apiUrl) : base(apiUrl)
{
}
public override string GetApiResultAsync()
{
...
}
}
public class Api2Service : ApiCaller
{
public Api2Service(string apiUrl) : base(apiUrl)
{
}
public override string GetApiResultAsync()
{
...
}
}
Now, in my controller I want to inject both istances since I want to use both business services.. but I don't know if this is possible.
[Route("api/[controller]")]
[ApiController]
public class MyController : ControllerBase
{
private readonly ApiCaller _apiCaller;
public BooksAndAlbumsController(ApiCaller apiCaller)
{
_apiCaller = apiCaller;
}
[HttpPost]
public void Post([FromBody] string value)
{
_apiCaller.GetApiResultAsync() //but I want to use both my apiCallers
}
}
So, somehow, in my container I would need to register both implementations of my abstract class. How can I achieve this? If you see flaws in my architecture please let me know!
You can inject an IEnumerable<ApiCaller> and then use them both.
Register both ApiCallers in the container and then inject the IEnumerable<ApiCaller> in your controller.
Something like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ApiCaller, Api1Service>();
services.AddSingleton<ApiCaller, Api2Service>();
}
MyController
[Route("api/[controller]")]
[ApiController]
public class MyController : ControllerBase
{
private readonly IEnumerable<ApiCaller> _apiCallers;
public MyController(IEnumerable<ApiCaller> apiCallers)
{
_apiCallers = apiCallers;
}
[HttpPost]
public async Task Post([FromBody] string value)
{
// Loop through one by one or call them in parallel, up to you.
foreach(var apiCaller in _apiCallers)
{
var result = await apiCaller.GetApiResultAsync();
}
}
}
Another possibility is to register the Api1Service and Api2Service and then inject them both like this. It will not be as dynamic/flexible as the first solution though.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<Api1Service>();
services.AddSingleton<Api2Service>();
}
MyController
[Route("api/[controller]")]
[ApiController]
public class MyController : ControllerBase
{
private readonly Api1Service _api1Service;
private readonly Api2Service _api2Service;
public MyController(Api1Service api1Service, Api2Service api2Service)
{
_api1Service = api1Service;
_api2Service = api2Service;
}
[HttpPost]
public async Task Post([FromBody] string value)
{
var result1 = await apiService1.GetApiResultAsync();
var result2 = await apiService2.GetApiResultAsync();
}
}
You can use NamedHttpClients and a factory
public static class NamedHttpClients {
public const string StarTrekApi = "StarTrekApi";
public const string StarWarsApi = "StarWarsApi";
}
services.AddHttpClient(NamedHttpClients.StarTrekApi, client => {
client.BaseAddress = new Uri("http://stapi.co/api/v1/rest");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("apiClientTest", "1.0"));
});
services.AddHttpClient(NamedHttpClients.StarWarsApi, client => {
client.BaseAddress = new Uri("https://swapi.co/api/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("apiClientTest", "1.0"));
});
and then create a factory which will be injected in the controller
public interface IFanApiClientFactory {
IFanApiClient CreateStarWarsApiClient();
IFanApiClient CreateStarTrekApiClient();
}
public class FanApiClientFactory : IFanApiClientFactory {
private readonly IHttpClientFactory _httpClientFactory;
public FanApiClientFactory(IHttpClientFactory httpClientFactory) {
_httpClientFactory = httpClientFactory;
}
public IFanApiClient CreateStarWarsApiClient() {
var client = _httpClientFactory.CreateClient(NamedHttpClients.StarWarsApi);
return new StarWarsApiClient(client);
}
public IFanApiClient CreateStarTrekApiClient() {
var client = _httpClientFactory.CreateClient(NamedHttpClients.StarTrekApi);
return new StarTrekApiClient(client);
}
}
register the factory
services.AddSingleton<IFanApiClientFactory, FanApiClientFactory>();
at least implement the concrete api clients
public class StarWarsApiClient : IFanApiClient {
private readonly HttpClient _client;
public StarWarsApiClient(HttpClient client) {
_client = client;
}
public async Task<string> GetMostImportantPerson() {
var response = await _client.GetAsync("people/1");
return await response.Content.ReadAsStringAsync();
}
}
public class StarTrekApiClient : IFanApiClient {
private readonly HttpClient _client;
public StarTrekApiClient(HttpClient client) {
_client = client;
}
public async Task<string> GetMostImportantPerson() {
var response = await _client.GetAsync("character/CHMA0000126904");
return await response.Content.ReadAsStringAsync();
}
}
and finally the controller
public class HomeController : Controller {
private readonly IFanApiClientFactory _fanApiClientFactory;
public HomeController(IFanApiClientFactory fanApiClientFactory) {
_fanApiClientFactory = fanApiClientFactory;
}
public async Task<IActionResult> Index() {
var starWarsApiClient = _fanApiClientFactory.CreateStarWarsApiClient();
var starTrekApiClient = _fanApiClientFactory.CreateStarTrekApiClient();
var person1 = await starTrekApiClient.GetMostImportantPerson();
var person2 = await starWarsApiClient.GetMostImportantPerson();
return View();
}
}
Check about Composite Pattern.
public sealed class CompositeApiCaller : ApiCaller
{
private const string SEPARATION_STRING = Environnement.NewLine;
private ApiCaller[] _apiCallers;
public CompositeApiCaller(params ApiCaller[] apiCallers)
{
_apiCallers = apiCallers;
}
public override string GetApiResultAsync()
{
var builder = new StringBuilder();
for (int i = 0; i < _apiCallers.Length; i++)
{
if (i > 0)
builder.Append(SEPARATION_STRING);
builder.Append(apiCaller.GetApiResultAsync());
}
return builder.ToString();
}
}
I am trying to add specific properties to telemetry request for every route.
After digging a bit, I've found that I can create my own custom TelemetryInitializer by implementing ITelemetryInitializer.
By doing this I've managed to add global properties to the request.
However, I still need to add specific properties at the controller level.
Do you have any idea how can I achieve this?
I've tried to inject TelemetryClient into the controller, but if I use it the properties are shared between requests.
This is how I've tried to log in the controller:
private TelemetryClient telemetryClient;
public ValueController(TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
}
[HttpGet]
public async Task<IActionResult> RouteOne([FromQuery(Name = "param1")]string param1, [FromQuery(Name = "param2")]string param2)
{
telemetryClient.Context.GlobalProperties["param1"] = param1;
telemetryClient.Context.GlobalProperties["param2"] = param2;
}
[HttpGet]
public async Task<IActionResult> RouteTwo([FromQuery(Name = "param3")]string param3, [FromQuery(Name = "param4")]string param4)
{
telemetryClient.Context.GlobalProperties["param3"] = param3;
telemetryClient.Context.GlobalProperties["param4"] = param4;
}
And this is the implementation of ITelemetryInitializer:
public class CustomPropertiesTelemetryInitializer : ITelemetryInitializer
{
private readonly IHttpContextAccessor httpContextAccessor;
public CustomPropertiesTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
telemetry.Context.GlobalProperties["RequestId"] = httpContextAccessor.HttpContext.GetProperty("requestId");
telemetry.Context.GlobalProperties["Ip"] = httpContextAccessor.HttpContext?.Connection.RemoteIpAddress.ToString();
telemetry.Context.GlobalProperties["RoutePath"] = httpContextAccessor.HttpContext?.Request.Path;
}
}
If the properties you added are always like "paramxxx", then there is a workaround(but it's really not very elegant).
In the controller constructor, check the GlobalProperties if it contains key like "paramxxx":
public ValueController(TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
var props = this.telemetryClient.Context.GlobalProperties;
foreach (var p in props)
{
if (p.Key.Contains("param"))
{
props.Remove(p.Key);
}
}
}
The key here is to use the DI framework. You can use it to get request-scoped data or services into your ITelemetryInitializer.
(These examples are based on the standard ASP.Net Dependency Injection framework. This pattern should work with any DI framework, but will need to be adjusted slightly.)
First, create a class to represent your request-scoped telemetry. I've used a simple DTO, but this could also be a service that knows how to fetch/generate the data itself. Register it using AddScoped. "Scoped" means that a new instance will be created for each HTTP request, and then that instance will be re-used within that request.
Because I used a DTO, I didn't bother with an interface--you should use an interface if the class contains any logic you'll want to mock in unit tests.
public class RequestScopedTelemetry
{
public string MyCustomProperty { get; set; }
}
services.AddScoped<RequestScopedTelemetry>();
Now, create the ITelemetryInitializer and register it as a singleton. App Insights will discover and use it through the DI framework.
class RequestScopedTelemetryInitializer : ITelemetryInitializer
{
readonly IHttpContextAccessor httpContextAccessor;
public RequestScopedTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
=> this.httpContextAccessor = httpContextAccessor;
public void Initialize(ITelemetry telemetry)
{
// Attempt to resolve the request-scoped telemetry from the DI container
var requestScopedTelemetry = httpContextAccessor
.HttpContext?
.RequestServices?
.GetService<RequestScopedTelemetry>();
// RequestScopedTelemetry is only available within an active request scope
// If no telemetry available, just move along...
if (requestScopedTelemetry == null)
return;
// If telemetry was available, add it to the App Insights telemetry collection
telemetry.Context.GlobalProperties[nameof(RequestScopedTelemetry.MyCustomProperty)]
= requestScopedTelemetry.MyCustomProperty;
}
}
services.AddSingleton<ITelemetryInitializer, RequestScopedTelemetryInitializer>();
Finally, in your controller method, set your per-request values. This part isn't necessary if your telemetry class is able to fetch or generate the data itself.
public class ExampleController : ControllerBase
{
readonly RequestScopedTelemetry telemetry;
public ValuesController(RequestScopedTelemetry telemetry)
=> this.telemetry = telemetry;
[HttpGet]
public ActionResult Get()
{
telemetry.MyCustomProperty = "MyCustomValue";
// Do what you want to
return Ok();
}
}
In order to add per request data into telemetry, you need to have a way to share data within the request. A reliable way is by using HttpContent.Items property, which is basically a Dictionary.
You can create a service to keep a Dictionary inside HttpContent.Items with all custom data you want in telemetry (key prefix is used to ensure we only read the things we want later in Initializer):
public class LogTelemetryRequest
{
private const string KEY_PREFIX = "CustomTelemetryData_";
private readonly IHttpContextAccessor _httpContextAccessor;
public LogTelemetryRequest(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void AddProperty(string key, string value)
{
_httpContextAccessor.HttpContext.Items[KEY_PREFIX + key] = value;
}
}
Register this as scoped in Startup.cs:
services.AddScoped<LogTelemetryRequest>();
Use it in your controller:
private LogTelemetryRequest logTelemetryRequest;
public ValueController(LogTelemetryRequest logTelemetryRequest)
{
this.logTelemetryRequest = logTelemetryRequest;
}
[HttpGet]
public async Task<IActionResult> RouteOne([FromQuery(Name = "param1")]string param1, [FromQuery(Name = "param2")]string param2)
{
// telemetryClient.Context.GlobalProperties["param1"] = param1;
// telemetryClient.Context.GlobalProperties["param2"] = param2;
logTelemetryRequest.AddProperty("param1", param1);
logTelemetryRequest.AddProperty("param2", param2);
}
Then read it within initializer:
public class AddCustomTelemetryInitializer : ITelemetryInitializer
{
private const string KEY_PREFIX = "CustomTelemetryData_";
private readonly IHttpContextAccessor _httpContextAccessor;
public AddCustomTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
if (requestTelemetry == null) return;
foreach (var item in _httpContextAccessor.HttpContext.Items)
{
if (item.Key is string key && key.StartsWith(KEY_PREFIX))
requestTelemetry.Properties.Add(key, item.Value.ToString());
}
}
}
Ideally LogTelemetryRequest should be registered using an interface, and the key prefix should be a single shared constant, didn't do for the sake of simplicity.
I created a ViewComponent to display a List<Product>, the list is valorized taken data from a REST API service, this is my class implementation:
public class ProductsViewComponent : ViewComponent
{
private readonly HttpClient _client;
public ProductsViewComponent(HttpClient client)
{
_client = client ?? throw new ArgumentNullException(nameof(client));
}
public async Task<IViewComponentResult> InvokeAsync(string date)
{
using (var response = await _client.GetAsync($"/"product/get_products/{date}"))
{
response.EnsureSuccessStatusCode();
var products = await response.Content.ReadAsAsync<List<Product>>();
return View(products);
}
}
}
I load the List inside an html table which is available inside the Components folder: Views\Shared\Components\Products\Default.cshtml.
In each View that needs to display the Products I did:
#await Component.InvokeAsync("Products", new { date = myDate })
The REST API is called using the HttpClient configured in the Startup.cs as following:
services.AddHttpClient<ProductsViewComponent>(c =>
{
c.BaseAddress = new Uri('https://api.myservice.com');
});
This works well, but the main problem is each time the user reload the page or maybe go inside another View which require to display the list of products, then the app will make another API call.
Is possible store the list in something like a cache and prevent to call the API again if the date is equal than the previous date selected?
I'm learning ASP.NET Core so I'm not really expert on this argument.
Thanks in advance for any help.
As per microsoft documentation https://learn.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-2.1
you can use IMemoryCache to cache data
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void Configure(IApplicationBuilder app)
{
app.UseMvcWithDefaultRoute();
}
}
and create instance of IMemoryCache. This is an example from Microsoft documentation. You can Create another class to handle this all together and In below example this is just saving DateTime But, you can save any object in cache and when you try to read that value from cache just need to cast that object into a Type.
I will strongly recommend you go through the above documentation.
public class HomeController : Controller
{
private IMemoryCache _cache;
public HomeController(IMemoryCache memoryCache)
{
_cache = memoryCache;
}
public IActionResult CacheTryGetValueSet()
{
DateTime cacheEntry;
// Look for cache key.
if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
{
// Key not in cache, so get data.
cacheEntry = DateTime.Now;
// Set cache options.
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Keep in cache for this time, reset time if accessed.
.SetSlidingExpiration(TimeSpan.FromSeconds(3));
// Save data in cache.
_cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
}
return View("Cache", cacheEntry);
}
}
Update: CacheKeys.Entry is a static class where all keys are defined. (Just coding standards). Please check the above documentation link.
public static class CacheKeys
{
public static string Entry { get { return "_Entry"; } }
public static string CallbackEntry { get { return "_Callback"; } }
public static string CallbackMessage { get { return "_CallbackMessage"; } }
public static string Parent { get { return "_Parent"; } }
public static string Child { get { return "_Child"; } }
public static string DependentMessage { get { return "_DependentMessage";} }
public static string DependentCTS { get { return "_DependentCTS"; } }
public static string Ticks { get { return "_Ticks"; } }
public static string CancelMsg { get { return "_CancelMsg"; } }
public static string CancelTokenSource { get { return "_CancelTokenSource";} }
}
You can use a distributed cache and so use Redis for example with a ConnectionMultiplexer.
And so foreach call you can call your redis for the cache which is implement thanks to an interface call here 'IDistributedCache'
You can find a lot of documentation to implement cache and use it.
: .Net framework
DotNet Core
Your controller X :
[HttpGet]
[Route("{itemId}")]
public async Task<IHttpActionResult> GetItemById(int eventId, [FromUri]EventTabs tabId)
{
ServiceResponse<ItemDto> result = await _itemDispatcher.GetItemById(itemId);
return WrapResponse(result);
}
Your dispatcher to get the item by id which use redis cache (already implement)
public class ItemDispatcher : ItemDispatcher
{
private readonly IUnitOfWork _unitOfWork;
private readonly IDistributedCache _distributedCache; // use interface of your implementation of redis cache
private readonly int _cacheDuration;
private readonly bool _isCacheEnabled;
public EventDispatcher(IUnitOfWork unitOfWork, IDistributedCache distCache)
{
_unitOfWork = unitOfWork;
_distributedCache = distCache; // init cache in constructor
_cacheDuration = _configuration.Get<int>("cache.duration"); // duration of your cache
_isCacheEnabled = _configuration.Get<bool>("cache.isEnable"); // if the cache is enable or not
}
public async Task<ServiceResponse<ItemDto>> GetItemById(int id)
{
// Add this for each Task call
var cacheKey = string.Empty;
if (_isCacheEnabled)
{
cacheKey = CacheUtils.GetCacheKey(CacheKeys.Item, id);
itemDto cacheResult = await _distributedCache.Get<ItemDto>(cacheKey);
if (cacheResult != null)
return new ServiceResponse<Item>(cacheResult);
}
}
Try This
Cache["KeyName"] = VariableOrTable; Cache.Insert("Key", VariableOrTable, null,
Cache.NoAbsoluteExpiration, ts);
I have some problems with correctly working HttpContext.Current.User.Identity. From Controller constructor this doesn't work, I have to implement this to some method. Look at this example.
public class SomeControler : ApiController
{
private UserData userData;
// NOT WORKING
public ChartsController(
RegisteredUserData registeredUserData,
NotLoggedInUserData NotLoggedInUserData
{
var isAuthenticated = HttpContext.Current.User.Identity.IsAuthenticated;
this.userData = isAuthenticated
? (IUserData)registeredUserData
: (IUserData)NotLoggedInUserData;
}
// WORKING
public SomeMethod(
RegisteredUserData registeredUserData,
NotLoggedInUserData NotLoggedInUserData
{
var isAuthenticated = HttpContext.Current.User.Identity.IsAuthenticated;
this.userData = isAuthenticated
? (IUserData)registeredUserData
: (IUserData)NotLoggedInUserData;
}
}
How I can fix this? I spent a lot of time for answer in web but i didnt get this.
Regards.
edit
I found an answer. Is it good solution ?
public class SomeControler : ApiController
{
private RegisteredUserData registeredUserData;
private NotLoggedInUserData notLoggedInUserData;
private UserData userData
{
get
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
return registeredUserData;
}
return notLoggedInUserData;
}
}
public ChartsController(
RegisteredUserData registeredUserData,
NotLoggedInUserData notLoggedInUserData
{
this.registeredUserData = registeredUserData;
this.notLoggedInUserData = notLoggedInUserData;
}
}
First, the request and HttpContext is not available yet in the construct of the controller because of where in the request flow the controller is initialized. You have to access it in an action where by then, the request and context would have been fully realized.
Next do not couple your controllers to HttpContext. It makes your code difficult to test and maintain.
Extract the desired information in a service abstraction.
public interface IUserDataAccessor {
IUserData UserData { get; }
}
public class UserDataAccessor : IUserDataAccessor {
private readonly RegisteredUserData registeredUserData;
private readonly NotLoggedInUserData notLoggedInUserData;
public UserDataAccessor(
RegisteredUserData registeredUserData,
NotLoggedInUserData notLoggedInUserData) {
this.registeredUserData = registeredUserData;
this.notLoggedInUserData = notLoggedInUserData;
}
public IUserData UserData {
get {
if (HttpContext.Current?.User?.Identity?.IsAuthenticated) {
return registeredUserData;
}
return notLoggedInUserData;
}
}
}
This allows the controller to remain lean with just the dependency on the abstraction.
public class ChartsController : ApiController {
private readonly IUserDataAccessor accessor;
public ChartsController(IUserDataAccessor accessor) {
this.accessor = accessor;
}
[HttpGet]
public IHttpActionResult SomeAction() {
var userData = accessor.UserData;
//...do something associated with user data
return OK();
}
}
Finally make sure that the abstraction and it's implementation a registered with your dependency container in your composition root.
I'm making an extension class to get user email, but I cannot refer to ApplicationDbContext service.
public static class Extensions
{
// private static readonly ApplicationDbContext _dbContext;
// static Extensions(ApplicationDbContext dbContext)
// {
// _dbContext = dbContext;
// }
public static string GetUserEmail(this ClaimsPrincipal principal)
{
string email = string.Empty;
string userid = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "";
if (!string.IsNullOrEmpty(userid))
{
email = _dbContext.Users.Single(x => x.Id == userid).Email;
}
return email;
}
}
Useage:
string email = User.GetUserEmail();
But static class cannot contain any parameter. Also, I don't want to pass dbContext as a parameter to GetUserEmail method.
Is there another way to do it?
Use the usermanager to get that information
// Inject UserManager<IdentityUser> into your controller or service
var user = await userManager.GetUserAsync(this.User); // pass ClaimsPrincipal
var email = await usermanager.GetEmailAsync(user);
Can you use a static initialization method?
public class Extensions
{
private static ApplicationDbContext _dbContext { get; set; }
public static void Init(ApplicationDbContext dbContext)
{
this._dbContext = dbContext;
}
}
Usage
Extensions.Init(dbContext);
var email = User.GetEmail();