why does UserManager.GetUserAsync not return? - c#

why does UserManager.GetUserAsync not return?
Property IsAdmin will be accessed in blazor UI when i click manually on About page. I don't get why the call to GetUSerAsync does not return. Any clues or mistakes? For simplicity I removed locking code. (doesn't work either without)
About.razor.cs
using System.Collections.Generic;
using System.Linq;
using Common.Server.Logic.Services;
using Common.Shared.Extensions;
using Microsoft.AspNetCore.Components;
namespace Common.Server.UI.Pages
{
public partial class About : ComponentBase
{
#nullable disable
[Inject]
private UserSessionInfo UserSession { get; set; }
private string IsAdminText { get; set; }
private IEnumerable<string> UserRoleNames { get; set; } = Enumerable.Empty<string>();
#nullable restore
protected override void OnInitialized()
{
IsAdminText = UserSession.IsAdmin.Format();
UserRoleNames = UserSession.UserRoles;
}
}
}
UserSessionInfo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AspNetCore.ServiceRegistration.Dynamic.Interfaces;
using Common.Server.Logic.Bases;
using Common.Shared;
using Common.Shared.Extensions;
using Common.Shared.Models.Logging.Bases;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
namespace Common.Server.Logic.Services
{
public class UserSessionInfo : LockingBase, IHasUsername, IScopedService
{
public DateTime LoginAt { get; set; }
private UserManager<IdentityUser> UserManager { get; }
private AuthenticationStateProvider AuthenticationStateProvider { get; }
public IEnumerable<string> UserRoles { get; private set; } = Enumerable.Empty<string>();
private string? _username;
public string? Username
{
get
{
CheckInitialized();
return _username;
}
}
private bool _isAdmin;
public bool IsAdmin
{
get
{
CheckInitialized();
return _isAdmin;
}
}
public UserSessionInfo(IServiceProvider serviceProvider)
{
UserManager = serviceProvider.GetRequiredService<UserManager<IdentityUser>>();
AuthenticationStateProvider = serviceProvider.GetRequiredService<AuthenticationStateProvider>();
}
private void CheckInitialized()
{
if (_username is null)
// double lock
SyncLock.Lock(async () => await InitializeAsync()).GetAwaiter().GetResult();
}
private async Task InitializeAsync()
{
var claimsPrincipal = (await AuthenticationStateProvider.GetAuthenticationStateAsync()).User;
var user = await UserManager.GetUserAsync(claimsPrincipal); // Does not return
_isAdmin = await UserManager.IsInRoleAsync(user, Const.Authorization.Roles.AdminRoleName);
UserRoles = await UserManager.GetRolesAsync(user);
var username = await UserManager.GetUserNameAsync(user);
Interlocked.Exchange(ref _username, username);
}
}
}
also (startup.cs):
services.AddDefaultIdentity<IdentityUser>()
.AddRoles<IdentityRole>()
.AddClaimsPrincipalFactory<UserClaimsPrincipalFactory<IdentityUser, IdentityRole>>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddDefaultUI();
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddScoped<UserSessionInfo>();

When an async method doesn't return then .GetAwaiter().GetResult() is the first suspect.
Most likely your calling code (the renderer?) is getting your Username and IsAdmin properties overlapped, and then you have a deadlock. But we can't be sure without seeing the whole call chain.
As a rule of thumb, you shouldn't need locking or GetResult (or .Wait(), .Result). When you think you do it may be time to back up.

Finally I found the solution. (and the reason why it didn't work)
The problem was that I accessed IsAdmin during OnInitialized callback instead of OnInitializedAsync.
Putting access to IsAdmin into OnInitializedAsync everything works!

Related

C# System.InvalidOperationException DisplayClass0_0 error while getting token

I need to get token when I register, but it gives me an error
Hello everyone.
I got that error when I was trying to register to my project on Postman:
https://anotepad.com/note/read/tgrka47d
(System.InvalidOperationException: An exception was thrown while attempting to evaluate the LINQ query parameter expression 'value(DataAccess.Concrete.EntityFramework.EfUserDal+<>c__DisplayClass0_0).user.Id'. See the inner exception for more information.)
My UserManager is here:
`
using System.Collections.Generic;
using Business.Abstract;
using Core.Entities.Concrete;
using DataAccess.Abstract;
namespace Business.Concrete
{
public class UserManager : IUserService
{
IUserDal _userDal;
public UserManager(IUserDal userDal)
{
_userDal = userDal;
}
public List<OperationClaim> GetClaims(User user)
{
return _userDal.GetClaims(user);
}
public void Add(User user)
{
_userDal.Add(user);
}
public User GetByMail(string email)
{
return _userDal.Get(u => u.Email == email);
}
}
}
`
My AuthManager is here:
`
using Business.Abstract;
using Business.Constants;
using Core.Entities.Concrete;
using Core.Utilities.Results;
using Core.Utilities.Security.Hashing;
using Core.Utilities.Security.JWT;
using Entities.DTOs;
namespace Business.Concrete
{
public class AuthManager : IAuthService
{
private IUserService _userService;
private ITokenHelper _tokenHelper;
public AuthManager(IUserService userService, ITokenHelper tokenHelper)
{
_userService = userService;
_tokenHelper = tokenHelper;
}
public IDataResult<User> Register(UserForRegisterDto userForRegisterDto, string password)
{
byte[] passwordHash, passwordSalt;
HashingHelper.CreatePasswordHash(password, out passwordHash, out passwordSalt);
var user = new User
{
Email = userForRegisterDto.Email,
FirstName = userForRegisterDto.FirstName,
LastName = userForRegisterDto.LastName,
PasswordHash = passwordHash,
PasswordSalt = passwordSalt,
Status = true
};
_userService.Add(user);
return new SuccessDataResult<User>(user, Messages.UserRegistered);
}
public IDataResult<User> Login(UserForLoginDto userForLoginDto)
{
var userToCheck = _userService.GetByMail(userForLoginDto.Email);
if (userToCheck == null)
{
return new ErrorDataResult<User>(Messages.UserNotFound);
}
if (!HashingHelper.VerifyPasswordHash(userForLoginDto.Password, userToCheck.PasswordHash, userToCheck.PasswordSalt))
{
return new ErrorDataResult<User>(Messages.PasswordError);
}
return new SuccessDataResult<User>(userToCheck, Messages.SuccessfulLogin);
}
public IResult UserExists(string email)
{
if (_userService.GetByMail(email) != null)
{
return new ErrorResult(Messages.UserAlreadyExists);
}
return new SuccessResult();
}
public IDataResult<AccessToken> CreateAccessToken(User user)
{
var claims = _userService.GetClaims(user);
var accessToken = _tokenHelper.CreateToken(user, claims);
return new SuccessDataResult<AccessToken>(accessToken, Messages.AccessTokenCreated);
}
}
}
`
My AuthController is here:
`
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Business.Abstract;
using Entities.DTOs;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace WebAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthController : Controller
{
private IAuthService _authService;
public AuthController(IAuthService authService)
{
_authService = authService;
}
[HttpPost("login")]
public ActionResult Login(UserForLoginDto userForLoginDto)
{
var userToLogin = _authService.Login(userForLoginDto);
if (!userToLogin.Success)
{
return BadRequest(userToLogin.Message);
}
var result = _authService.CreateAccessToken(userToLogin.Data);
if (result.Success)
{
return Ok(result.Data);
}
return BadRequest(result.Message);
}
[HttpPost("register")]
public ActionResult Register(UserForRegisterDto userForRegisterDto)
{
var userExists = _authService.UserExists(userForRegisterDto.Email);
if (!userExists.Success)
{
return BadRequest(userExists.Message);
}
var registerResult = _authService.Register(userForRegisterDto,userForRegisterDto.Password);
var result = _authService.CreateAccessToken(registerResult.Data);
if (result.Success)
{
return Ok(result.Data);
}
return BadRequest(result.Message);
}
}
}
`
My EfUserDal is here:
`
using System.Collections.Generic;
using Core.DataAccess.EntityFramework;
using Core.Entities.Concrete;
using DataAccess.Abstract;
using System.Linq;
namespace DataAccess.Concrete.EntityFramework
{
public class EfUserDal : EfEntityRepositoryBase<User,CarRentalContext>,IUserDal
{
public List<OperationClaim> GetClaims(User user)
{
using (CarRentalContext context = new CarRentalContext())
{
var result = from operationClaim in context.OperationClaims
join userOperationClaim in context.UserOperationClaims
on operationClaim.Id equals userOperationClaim.OperationClaimId
where userOperationClaim.UserId == user.Id
select new OperationClaim {Id = operationClaim.Id, Name = operationClaim.Name};
return result.ToList();
}
}
}
}
`
I need to get a token when I register. How can I fix this?
You picked up the wrong lines from your stacktrace to focus on.
It states the following in the inner exception:
System.NullReferenceException: Object reference not set to an instance of an object.
at DataAccess.Concrete.EntityFramework.EfUserDal.GetClaims(User user) in /home/mert/Desktop/Project/ReCapProject/DataAccess/Concrete/EntityFramework/EfUserDal.cs:line 20
Which means you have a variable on line 20 in your EfUserDal which does not have a value at runtime.
Since the next line mentions ToList, I'm guessing result is null after evaluating the query. Use debugging to see what value each variable in your query is getting before it's evaluated. Probably one of them is null

Why won't my API's updated DbContext fetch a newly added DB column?

I have an Azure SQL DB that initially had the following columns:
user name
password hash
password salt
This DB serves a .NET Core C# API that checks username and password to return a JWT token.
The API had a User object that comprised all three columns with the correct types, a DbContext with a DbSet<User>, and an IServiceCollection that used said DbContext.
The API worked fine, returning a JWT token as needed.
I have since needed to add an extra parameter to check and pass to the JWT creation - the relevant column has been created in the DB, the User object in the API has been updated to include the extra parameter and that extra parameter is observed in the Intellisense throughout the API code.
The issue is that when the API is deployed to Azure, the extra parameter isn't being recognised and populated; how do I make the API correctly update to use the new DbContext and retrieve the User with the extra parameter?
(I've omitted the interfaces for brevity, as they're essentially the corresponding classes)
User, UserRequest and MyApiDbContext Classes:
using Microsoft.EntityFrameworkCore;
namespace MyApi.Models
{
// Basic user model used for authentication
public class User
{
public string UserId { get; set; }
public byte[] PasswordHash { get; set; }
public byte[] PasswordSalt { get; set; }
public string ExtraParam { get; set; } // newly added parameter
}
public class UserRequest
{
public string UserId { get; set; }
public string password { get; set; }
}
public class MyApiDbContext : DbContext
{
public MyApiDbContext(DbContextOptions<MyApiDbContext> options)
: base(options)
{
}
public DbSet<User> Users { get; set; }
}
}
The AuthRepository that retrieves the user:
using Microsoft.EntityFrameworkCore;
using MyApi.Interfaces;
using MyApi.Models;
using System.Threading.Tasks;
namespace MyApi.Services
{
public class AuthRepository : IAuthRepository
{
private readonly MyApiDbContext _context;
public AuthRepository(MyApiDbContext context)
{
_context = context;
}
public async Task<User> Login(string username, string password)
{
// my test user gets returned
User returnedUser = await _context.Users.FirstOrDefaultAsync(x => x.UserId == username);
if (returnedUser == null)
{
return null;
}
// the password get verified
if (!VerifyPasswordHash(password, returnedUser.PasswordHash, returnedUser.PasswordSalt))
{
return null;
}
// this does not get changed, but the value set in the DB is definitely a string
if (returnedUser.ExtraParam == null || returnedUser.ExtraParam == "")
{
returnedUser.ExtraParam = "placeholder"
}
return returnedUser;
}
}
}
The AuthService that calls the AuthRepository for the user then "creates the JWT token" (just returning a string for this example), currently set up to return the user details:
using Microsoft.Extensions.Options;
using MyApi.Interfaces;
using MyApi.Models;
using System;
using System.Threading.Tasks;
namespace MyApi.Services
{
public class AuthService : IAuthService
{
private readonly IOptions<MyApiBlobStorageOptions> _settings;
private readonly IAuthRepository _repository;
public AuthService(IOptions<MyApiBlobStorageOptions> settings, IAuthRepository repository)
{
_repository = repository;
_settings = settings;
}
public async Task<string> Login(string username, string password)
{
User returnedUser = await _repository.Login(username, password);
if (returnedUser != null)
{
// currently returns "UserIdInDB,ProvidedPasswordFromLogin,"
return $"{returnedUser.UserId},{password},{returnedUser.ExtraParam}";
}
return null;
}
}
}
The controller that calls the AuthService:
using Microsoft.AspNetCore.Mvc;
using MyApi.Interfaces;
using MyApi.Models;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace MyApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly MyApiDbContext _context;
private readonly IAuthService _authService;
public AuthController(MyApiDbContext context, IAuthService authService)
{
_context = context;
_authService = authService;
}
[HttpPost("login")]
public async Task<IActionResult> Login(UserRequest loginUser)
{
string token = await _authService.Login(loginUser.UserId, loginUser.Password);
if (token != null)
{
return Ok(token);
}
return Unauthorized("Access Denied!!");
}
}
}
The startup class that registers everything:
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using MyApi.Interfaces;
using MyApi.Models;
using MyApi.Services;
using Microsoft.Extensions.Azure;
using Azure.Storage.Queues;
using Azure.Storage.Blobs;
using Azure.Core.Extensions;
using System;
namespace MyApi
{
public class Startup
{
public IConfiguration Configuration { get; }
private readonly ILogger<Startup> _logger;
private readonly IConfiguration _config;
public Startup(ILogger<Startup> logger, IConfiguration config)
{
_logger = logger;
_config = config;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add dBContext for DB
services.AddDbContextPool<MyApiDbContext>(options => options.UseSqlServer(_config.GetConnectionString("MyAzureDb")));
// Add DI Reference for Repository
services.AddScoped<IAuthRepository, AuthRepository>();
// Add DI Reference for Azure Blob Storage Processes
services.AddScoped<IBlobService, AzureBlobService>();
// DI Reference for AuthService
services.AddScoped<IAuthService, AuthService>();
// Add configuration section for Constructor Injection
services.Configure<ApiBlobStorageOptions>(_config.GetSection("MyApiBlobStorage"));
services.AddMvc(mvcOptions => mvcOptions.EnableEndpointRouting = false).SetCompatibilityVersion(CompatibilityVersion.Latest);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII
.GetBytes(_config.GetSection("MyApiBlobStorage:Secret").Value)),
ValidateIssuer = false,
ValidateAudience = false
};
options.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = context =>
{
_logger.LogWarning("Token authentication failed whilst attempting to upload file");
return Task.CompletedTask;
}
};
});
services.AddAzureClients(builder =>
{
builder.AddBlobServiceClient(Configuration["ConnectionStrings:MyApiBlobStorage/AzureBlobStorageConnectionString:blob"], preferMsi: true);
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseCors(x => x.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
app.UseAuthentication();
app.UseMvc();
}
}
internal static class StartupExtensions
{
public static IAzureClientBuilder<BlobServiceClient, BlobClientOptions> AddBlobServiceClient(this AzureClientFactoryBuilder builder, string serviceUriOrConnectionString, bool preferMsi)
{
if (preferMsi && Uri.TryCreate(serviceUriOrConnectionString, UriKind.Absolute, out Uri serviceUri))
{
return builder.AddBlobServiceClient(serviceUri);
}
else
{
return builder.AddBlobServiceClient(serviceUriOrConnectionString);
}
}
public static IAzureClientBuilder<QueueServiceClient, QueueClientOptions> AddQueueServiceClient(this AzureClientFactoryBuilder builder, string serviceUriOrConnectionString, bool preferMsi)
{
if (preferMsi && Uri.TryCreate(serviceUriOrConnectionString, UriKind.Absolute, out Uri serviceUri))
{
return builder.AddQueueServiceClient(serviceUri);
}
else
{
return builder.AddQueueServiceClient(serviceUriOrConnectionString);
}
}
}
}
Let me know if there is anything else required for understanding: the only difference between before and now is the addition of ExtraParam and the corresponding references throughout for the API, and the DB getting the identically named column.
I tried adding the parameter and deploying it to Azure and making the POST request as normal, starting and stopping the app service, deploying the API while the app service was stopped and starting it again, and restarting the app service. I don't know how much I could try changing up what I'm doing, I'm trying to do exactly the same as before, but with an extra parameter getting requested from the DB.
I can also confirm that the DB contains the ExtraParam column, and that it contains values against the existing data rows, as viewed using the Azure Portal's DB Query Editor.
I've resolved the issue, partially because of posting this question and sanitising the code for public discussion.
In the Login Controller, in my development code the request for the user to be returned was subsequently ignored, passing through the user request details which had a null ExtraParam, not the returned user which had the ExtraParam populated.
The moral of the story is to confirm which objects are being used at which points in the code, or have one object that is passed into, updated by, then returned from functions to maintain consistency.

DbContext in ASP.NET Core throws System.ObjectDisposedException

I am saving a Todo model to database using EF Core in ASP.NET Core app.
But when I try to print the success message using Console.Writeline it throws System.ObjectDisposedException
Here's full application github code link
Here's ToDo model:
public class Todo
{
public int Id { get; set; }
[Required]
[MaxLength(10)]
public string Title { get; set; }
public bool IsCompleted { get; set; } = false;
}
Here's the business logic that throws exception.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ToDoApp.Models;
using ToDoApp.Data;
namespace ToDoApp
{
public class ToDoService
{
readonly ToDoContext _context;
public ToDoService(ToDoContext context)
{
_context = context;
}
public async Task<int> CreateToDo(CreateToDoBindingModel input)
{
Todo todo = new()
{
Title = input.Title,
IsCompleted = input.IsCompleted
};
_context.Add(todo);
await _context.SaveChangesAsync();
Console.WriteLine("Successfully saved todo.");
return task.Id;
}
}
}
Exception Details:
System.ObjectDisposedException
HResult=0x80131622
Message=Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'ToDoContext'.
Source=Microsoft.EntityFrameworkCore
StackTrace:
at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed()
Why does Console.Writeline throws exception?
Edit:
Here's ToDoContext:
public class ToDoContext: DbContext
{
public ToDoContext(DbContextOptions<ToDoContext> options)
: base(options)
{ }
public DbSet<Todo> Todos { get; set; }
}
Here's how it is registered in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
var connectionString = Configuration["ConnectionString"];
services.AddDbContext<ToDoContext>(options => options.UseNpgsql(connectionString));
services.AddScoped<ToDoService>();
}
Here's full application github code link
Problem is in this part of your code:
public IActionResult OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
Task<int> id = _service.CreateTask(Input);//<--- this line
return RedirectToPage("Index");
}
After call createTask that returns a task means not complete yet and you don't wait for it to be completed, When call RedirectToPage current request finished. because your service and dbcontext added to DI with Scope lifetime that means the life of object is during the request and when request finished that object will disposed.
So for solve problem just use this way:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
int id = await _service.CreateTask(Input);
return RedirectToPage("Index");
}

asp.net core 5 Request.Schema Not Recognizing correct namespace

Im using the following code to generate a link for Email Authentication ( user should click on the link and if it was valid , the account would be activarted) . the following code would work correctly in the controller but i refactured the code and moved it to the Service class . Request.Schema DOES'NT RECOGNIZE CORRECT NAMESPACE . i have tried several ways and packages but its not working . how can i Solve it ?
usings :
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
//using Microsoft.AspNetCore.Mvc;
using ProjectName.Core.DTOs.ClientDTOs;
using ProjectName.Core.Services.Interfaces;
using ProjectName.Core.Utilities;
using ProjectName.DataLayer.Context;
using ProjectName.DataLayer.Entities.PublicEntities;
using ProjectName.DataLayer.Entities.UserEntities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//using System.Web.Mvc;
using System.Threading.Tasks;
I followed this document on microsoft but still not working....
Url.Action
var address = Microsoft.AspNetCore.Mvc.IUrlHelper.Action(
"ConfirmEmail",
"Account",
new { username = newUser.UserName, token = emailConfirmationToken },
**Request.Scheme**);
at first make a class for encoding urls and tokens.
public class TokenUrlEncoderService
{
public string EncodeToken(string token)
{
return WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(token));
}
public string DecodeToken(string urlToken)
{
return Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(urlToken));
}
}
then make a class in your service layer like this.
public class IdentityEmailService
{
public IdentityEmailService(IEmailSender sender,
UserManager<IdentityUser> userMgr,
IHttpContextAccessor contextAccessor,
LinkGenerator generator,
TokenUrlEncoderService encoder)
{
EmailSender = sender;
UserManager = userMgr;
ContextAccessor = contextAccessor;
LinkGenerator = generator;
TokenEncoder = encoder;
}
public IEmailSender EmailSender { get; set; }
public UserManager<IdentityUser> UserManager { get; set; }
public IHttpContextAccessor ContextAccessor { get; set; }
public LinkGenerator LinkGenerator { get; set; }
public TokenUrlEncoderService TokenEncoder { get; set; }
private string GetUrl(string emailAddress, string token, string page)
{
string safeToken = TokenEncoder.EncodeToken(token);
return LinkGenerator.GetUriByPage(ContextAccessor.HttpContext, page,
null, new { email = emailAddress, token = safeToken });
}
}
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-5.0#use-httpcontext-from-custom-components
the following code would work correctly in the controller but i refactured the code and moved it to the Service class .
Request here is property in ControllerBase class, your service class must inherit from ControllerBase or Controller.
Change your code like below:
public interface IGenerateUrl
{
string GetUrl();
}
public class GernateUrl : IGenerateUrl
{
private readonly IUrlHelper url;
public GernateUrl(IUrlHelper url)
{
this.url = url;
}
public string GetUrl()
{
string scheme = url.ActionContext.HttpContext.Request.Scheme;
var address = url.Action(
"ConfirmEmail",
"Account",
new { username = "user", token = "token" },
scheme);
return address;
}
}
Be sure register the service like below:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<IGenerateUrl, GernateUrl>();
services.AddScoped<IUrlHelper>(x =>
{
var actionContext = x.GetRequiredService<IActionContextAccessor>().ActionContext;
var factory = x.GetRequiredService<IUrlHelperFactory>();
return factory.GetUrlHelper(actionContext);
});
services.AddControllersWithViews();
}

Why is my C# MVC4 Session Wrapper Service not working?

I am trying to implement a wrapper for my session (Loose coupling so it is easy to make changes later) but I am having problems, either the storing into the session is failing, or the retrieval but I do not know which.
I would greatly appreciate it if you could take a look at my code and tell me if there is anything obviously wrong, or a better way of doing what I am trying to do. I basically want to display different things to different types of user, but when I try to access the user in the ViewContext it is null.
Any links to tutorials or examples would be gratefully accepted.
Here is my code:
User and WEB_USER_LEVEL have a one to many relationship
I have used Entity Framework to create models from my existing database
I am currently in the early stages of the project and the User is not coming from the database yet (as the structure will change) so I am creating a new User and populating it before using CurrentUserService.Login(user). i have tried pulling a user out of the data base and logging that user in but it still does not work.
ICurrentUserService.cs (in Infrastructure folder)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MyProject.Infrastructure
{
public interface ICurrentUserService
{
User CurrentUser { get; }
void SetCurrentUser(WEB_USER user);
void SetAdminStatus(bool type);
bool GetAdminStatus { get; }
void SetManagerStatus(bool type);
bool GetManagerStatus { get; }
void Login(User user);
void Logout();
int? TryGetCurrentUserId();
}
}
CurrentUserService.cs (in Infrastructure folder)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MyProject.Controllers;
using MyProject.Infrastructure.Filters;
namespace MyProject.Infrastructure
{
public class CurrentUserService : ICurrentUserService
{
public const string CurrentUserKey = "CurrentUser";
public const string CurrentUserIdKey = "CurrentUserId";
public const string IsAdminKey = "IsAdmin";
public const string IsManagerKey = "IsManager";
private readonly IDb _db;
public CurrentUserService() : this(new Db()) { }
public CurrentUserService(IDb db)
{
_db = db;
}
public User CurrentUser
{
get
{
return (User)HttpContext.Current.Items[CurrentUserKey];
}
}
public void SetCurrentUser(User user)
{
HttpContext.Current.Items[CurrentUserKey] = user;
}
public void SetAdminStatus(bool type)
{
HttpContext.Current.Session[IsAdminKey] = type;
}
public bool GetAdminStatus
{
get { return (bool)HttpContext.Current.Session[IsAdminKey]; }
}
public void SetManagerStatus(bool type)
{
HttpContext.Current.Session[IsManagerKey] = type;
}
public bool GetManagerStatus
{
get { return (bool)HttpContext.Current.Session[IsManagerKey]; }
}
public void Login(User user)
{
HttpContext.Current.Session[CurrentUserIdKey] = user.ID;
HttpContext.Current.Items[CurrentUserKey] = user;
SetManagerStatus(user.WEB_USER_LEVEL.IsManager);
SetAdminStatus(user.WEB_USER_LEVEL.RefID == 1 ? true : false);
}
public void Logout()
{
HttpContext.Current.Items[CurrentUserKey] = null;
HttpContext.Current.Session[CurrentUserIdKey] = null;
SetManagerStatus(false);
SetAdminStatus(false);
}
public int? TryGetCurrentUserId()
{
return HttpContext.Current.Session[CurrentUserIdKey] as int?;
}
}
}
Extensions.cs (in Infrastructure folder)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MyProject.Infrastructure
{
public static class Extensions
{
public static User CurrentUser(this ViewContext view)
{
return (User)view.HttpContext.Items[CurrentUserService.CurrentUserKey];
}
}
}
HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MyProject.Infrastructure;
using MyProject.Infrastructure.Filters;
using MyProject.ViewModels;
using MyProject.Models;
using System.Data.Objects;
namespace MyProject.Controllers
{
public class HomeController : BaseController
{
readonly IDb _db;
readonly ICurrentUserService _currentUserService;
readonly IErrorReporter _errorReporter;
public HomeController() : this(new Db(), new CurrentUserService(), new ErrorReporter()) { }
public HomeController(IDb db, ICurrentUserService currentUserService, IErrorReporter errorReporter)
{
_db = db;
_currentUserService = currentUserService;
_errorReporter = errorReporter;
}
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Login(FormCollection form)
{
// Create new user and populate
_currentUserService.Login(user);
return RedirectToAction("Home");
}
public ActionResult Home()
{
return View();
}
}
}
Trying to access in ViewContext in _Layout.cshtml when the Home view is loaded
#using MyProject.Infrastructure
#if (ViewContext.CurrentUser() != null && ViewContext.CurrentUser().WEB_USER_LEVEL.IsManager)
{
#RenderPage("~/Views/Shared/_Menu.cshtml")
}
But ViewContext.CurrentUser() is always null.
Thank you for your help!
Instead of creating an extension method on top of ViewContext, I would suggest that you create a ViewModel for your view and pass into it the data that your view needs. Remember, any external data that a view needs should be fed into it through a ViewModel. This makes for a clean one to one relationship that's easy to follow.

Categories

Resources