How to call a service inside a static class? - c#

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

Related

How can acces ClaimsIdentity on Logic Layer

I want to move this service to logic for using on everywhere, but i can't successful because it was coming from the controller.
I have two services. There read caches and I use them in the controller layer when authenticating.
my first logic is reading companyId in cache
public virtual int GetCompanyIdFromCache(int id)
{
_memCache.TryGetValue(id, out int companyId);
return companyId;
}
My second service is also on the controller. (helps me find the user's id)
[HttpGet]
[Route("GetCompanyId")]
public int GetCompanyPublicId()
{
if (User.Identity is ClaimsIdentity claimsIdentity)
{
var userId = claimsIdentity.FindFirst(ClaimTypes.Name)?.Value;
var companyId = _userService.GetCompanyIdFromCache(Convert.ToInt32(userId));
return companyId;
}
throw new ArgumentException("Can't be found Company");
}
I want to use this method everywhere, so i want to move the second service completely to logic layer but User field comes from ControllerBase (on HttpContext i guess) and I can't move it to logic
if (User.Identity is ClaimsIdentity claimsIdentity)
What should I do to refactor the logic layer?
As far as I know, the User.Identity is ClaimsIdentity which is a property in the controllerbase. We couldn't directly use it in the other methods.
If you want to access the User.Identity in other service method, I suggest you could try to inject the httpaccessor service and get the ClaimsIdentity from it.
More details, you could refer to below codes:
Create myclass:
public class Myclass
{
public IHttpContextAccessor _accessor { get; set; }
public Myclass(IHttpContextAccessor accessor)
{
_accessor = accessor;
var re = accessor.HttpContext.User.Identity as ClaimsIdentity;
int i = 0;
}
public string GetName() {
var re = _accessor.HttpContext.User.Identity as ClaimsIdentity;
string name = re.Claims.First(x => x.Type == "name").Value;
return name;
}
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();
services.AddHttpContextAccessor();
services.AddScoped(typeof(Myclass));
}
Usage:
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public Myclass test { get; set; }
public HomeController(ILogger<HomeController> logger, Myclass _test)
{
_logger = logger;
test = _test;
}
public async Task<IActionResult> IndexAsync()
{
var claimsIdentity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
claimsIdentity.AddClaim(new Claim("name", "aaaa"));
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity)
);
return View();
}
public async Task<IActionResult> PrivacyAsync()
{
var re= test.GetName();
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
Result:

Get client IP and compare values with the configuration

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

EF Core DbContext being disposed of before Async methods are complete

I am having issues where my DbContext is being disposed of early. It is only ever apparent when calling any of the *Async methods, such as ToListAsync() - if i call any of the syncronous methods everything is fine.
I can't figure out what I'm doing wrong.
Any advise please?
Here is as much of the code as i believe is needed.
The DbContext and its interface
public interface IMyDbContext
{
DbSet<MyModel> MyModels { get; set; }
}
public class MyDbContext : DbContext, IMyDbContext
{
public DbSet<MyModel> MyModels { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new MyModelConfig());
}
}
A Repository using this DbContext
public class MyModelRepository : IMyModelRepository
{
private readonly IMyDbContext _dbContext;
private string _baseSql = "Some SQL here ";
public MyModelRepository(IMyDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<IList<MyModel>> GetAllAsync(Paging paging, Permission permission)
{
if (permission == null)
throw new ArgumentNullException("permission");
string sql = ApplyFilter(_baseSql, permission);
try
{
// THIS FAILS
return await _dbContext.MyModels.FromSql(sql).Skip(paging.Skip).Take(paging.Take).ToListAsync();
// THIS FAILS
return await _dbContext.MyModels.FromSql(sql).ToListAsync();
// THIS WORKS
return await _dbContext.MyModels.FromSql(sql).ToList();
}
catch (Exception e)
{
throw new InvalidOperationException("Could not retrieve data", e);
}
}
}
I'm calling the repo via a service that looks like this:
public class GetAllMyModelQuery : IGetAllMyModelQuery
{
private readonly IMyModelRepository _myModelRepository;
private readonly IPermissionService _permissionService;
private readonly ILogger _logger;
public GetAllAbsenceQuery(IMyModelRepository myModelRepository, IPermissionService permissionService, ILogger<GetAllMyModelQuery> logger)
{
_myModelRepository = myModelRepository;
_permissionService = permissionService;
_logger = logger;
}
public async Task<IList<Emp_AbsenceEtrac>> Execute(Paging paging)
{
if (_permissionService.Permission == null)
{
_logger.LogInformation("No permission to the requested resource");
return null;
}
// if external?
// call external repo
//TODO//
// else
return await _myModelRepository.GetAllAsync(paging, _permissionService.Permission);
}
}
This in turn is called by the controller
public class MyModelController : Controller
{
private readonly IQueryStore _queryStore;
public MyModelController(IQueryStore queryStore)
{
_queryStore = queryStore;
}
[HttpGet]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(BadRequestObjectResult), (int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Index([FromQuery] int offset = 0, [FromQuery] int limit = 25)
{
Paging paging = new Paging(offset, limit);
return Ok(_queryStore.GetAllMyModelQuery.Execute(paging));
}
}
Finally, it's all wired together in the startup:
services.AddScoped<IMyDbContext, MyDbContext>();
services.AddScoped<IMyModelRepository, MyModelRepository>();
// Everything else above is also added as scope..
services.AddDbContext<MyDbContext>(opts =>
{
opts.UseSqlServer(Configuration.GetConnectionString("MyDb"),
sqlServerOptions =>
{
sqlServerOptions.CommandTimeout(600);
// required to allow skip/take on sql server 2008
sqlServerOptions.UseRowNumberForPaging(true);
});
});
Is there anything jumping out that would cause my Async calls to result in a closed Db connection?
Error is:
You should await the GetAllMyModelQuery.Execute method in your Index controller action:
[HttpGet]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(BadRequestObjectResult), (int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Index([FromQuery] int offset = 0, [FromQuery] int limit = 25)
{
Paging paging = new Paging(offset, limit);
return Ok(await _queryStore.GetAllMyModelQuery.Execute(paging).ConfigureAwait(false));
}

How to dynamically change change DI using autofac?

We are developing windows service, and i want to change dbcontext class dynamically in repositories.
bellow is the scenario.
I have three db context classes
public abstract class Context : DbContext, IUnitOfWork
{
protected Context(string connectionString) : base(connectionString)
{
}
}
public class PlatformContext : Context
{
private readonly string _connectionString;
public PlatformContext(string connectionString)
: base(connectionString)
{
_connectionString = connectionString;
}
}
public class PlatformReplicaContext : Context
{
private readonly string _connectionString;
public PlatformReplicaContext(string connectionString)
: base(connectionString)
{
_connectionString = connectionString;
}
}
public class TempContext : Context
{
private readonly string _connectionString;
public TempContext(string connectionString)
: base(connectionString)
{
_connectionString = connectionString;
}
}
and i have repository
public interface ICategoryRepository : IRepository<Category>
{
}
public class CategoryRepository :Repository<Category>, ICategoryRepository
{
public CategoryRepository(Context context) : base(context)
{
}
}
hence im using CQRS i have another three classes
public class CategoryBasicQuery:IRequest<BaseQueryResponse>
{
public int CategoryId { get; set; }
}
public class CategoryBasicQueryHandler : IRequestHandler<CategoryBasicQuery, BaseQueryResponse>
{
private readonly ICategoryRepository _categoryRepository;
private readonly IMapper _mapper;
public CategoryBasicQueryHandler(ICategoryRepository categoryRepository, IMapper mapper)
{
_categoryRepository = categoryRepository;
_mapper = mapper;
}
public async Task<BaseQueryResponse> Handle(CategoryBasicQuery request, CancellationToken cancellationToken)
{
var entry = await _categoryRepository.FindAsync(request.CategoryId);
if (entry == null)
{
return new NotFoundResponse();
}
var response = _mapper.Map<CategoryBasicResponse>(entry);
return response;
}
}
Now here is the issue
Here category repository should be able to execute queries in all 3 types of contexts.
but how should i register classes in using autofac?
then i came up with a solution generating repositories in run time as below
public class RepositoryFactory
{
public static TRepository GetRepositoryInstance<T, TRepository>(
params object[] args)
where TRepository : IRepository<T>
{
return (TRepository)Activator.CreateInstance(typeof(TRepository), args);
}
}
im calling this method inside CategoryBasicQueryHandler class like this
var categoryRepo = RepositoryFactory.GetRepositoryInstance<Category, CategoryRepository>(new PlatformReplicaContext("connectionString"));
but when calling from CQRS
var categoty = new Category();
var command = new CategoryBasicQuery {CategoryId = categoryId};
var result = _mediator.Send(command);
VS give me following error
and my autofac registration as follows
builder.RegisterType<CategoryService>().AsSelf();
builder.RegisterType<ActionRepository>().As<IActionRepository>();
builder.RegisterType<CategoryRepository>().As<ICategoryRepository>();
builder.RegisterType<Mapper>().As<IMapper>();
can anyone help me resolve this or suggest good method to handle this situation.
thanks.
This may give you a good starting point for a possible solution: http://autofaccn.readthedocs.io/en/latest/resolve/relationships.html#keyed-service-lookup-iindex-x-b
builder.RegisterType<PlatformContext>().Keyed<Context>("platform");
builder.RegisterType<PlatformReplicaContext>().Keyed<Context>("replica");
builder.RegisterType<TempContext>().Keyed<Context>("temp");
You mentioned in a comment that there is a variable named action somewhere that will indicate which implementation to use:
public class Class1
{
private readonly IIndex<string, Context> contexts;
public Class1(IIndex<string, Context> contexts)
{
this.contexts = contexts;
}
public void Whatever()
{
string action = ...; // platform, replica or temp
Context context = this.contexts[action];
...
}
}
Of course this needs to be adapted so that it will fit in the rest of your application design. A possible example could be:
Context context = this.contexts[action];
using(ILifetimeScope scope = container.BeginLifetimeScope(builder =>
{
builder.RegisterInstance(context).As<Context>();
}))
{
// Because we are resolving IMediator from the scope, the selected Context will be used in all dependencies
var mediator = scope.Resolve<IMediator>();
mediator.Send(...);
}

HttpContext(User.Identity) in Controller (asp web api) not working

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.

Categories