In my application, I would like to check whether or not a particular user is logged in or not so I can display a status saying that that user is either "online" or "offline" to another user. This question is not about authenticating a user, only about getting the authentication status of a user.
How would I go about achieving this?
I think an option is to use some real-time solutions. SignalR for example.
When a user logs in , you connect it to the hub. OnConnected() action save its state.Then OnDisconnected() remove from "OnlineRepository".
Update with example
Here is how I did this in a asp.Net Mvc 5 app.
A Model class that holds a single user like:
public class SingleConnection
{
public SingleConnection()
{
ConnectionId = new List();
}
public string Id { get; set; }
public List ConnectionId { get; set; }
}
A connection mapping class that helps in adding/removeing and getting a user from list
public class ConnectionMapping
{
private readonly List _connections = new List();
public int Count
{
get
{
return _connections.Count;
}
}
public void Add(string key, string connectionId)
{
lock (_connections)
{
var sn = _connections.Where(x => x.Id == key).FirstOrDefault();
if (sn != null) // there is a connection with this key
{
_connections.Find(x => x.Id == key).ConnectionId.Add(connectionId);
}
else
{
_connections.Add(new SingleConnection { Id = key, ConnectionId = new List { connectionId } });
}
}
}
public List GetConnections(string id)
{
var con = _connections.Find(x => x.Id == id);
return con != null ? con.ConnectionId : new List();
}
public List AllConnectionIds()
{
List results = new List();
var allItems = _connections.Where(x => x.ConnectionId.Count > 0).ToList();
foreach (var item in allItems)
{
results.AddRange(item.ConnectionId);
}
return results;
}
public List AllKeys()
{
return _connections.Select(x => x.Id).ToList();
}
public void Remove(string key, string connectionId)
{
lock (_connections)
{
var item = _connections.Find(x => x.Id == key);
if (_connections.Find(x => x.Id == key) != null)
{
_connections.Find(x => x.Id == key).ConnectionId.Remove(connectionId);
if (_connections.Find(x => x.Id == key).ConnectionId.Count == 0)
{
_connections.Remove(item);
}
}
}
}
}
In my Hub Class
private void IsActive(string connection, bool connected)
{
Clients.All.clientconnected(connection, connected);
}
public override Task OnConnected()
{
string name = Context.User.Identity.Name;
_connections.Add(name, Context.ConnectionId);
IsActive(name, true);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
string name = Context.User.Identity.Name;
_connections.Remove(name, Context.ConnectionId);
IsActive(name, false);
return base.OnDisconnected(stopCalled);
}
public override Task OnReconnected()
{
string name = Context.User.Identity.Name;
if (!_connections.GetConnections(name).Contains(Context.ConnectionId))
{
_connections.Add(name, Context.ConnectionId);
}
IsActive(name, false);
return base.OnReconnected();
}
In _Layout.cshtml
// reference scripts
// add a callback or the OnConnected() Will not fire
chat.client.clientconnected = function (id,active){
/*
this will be called everytime a user connect or disconnect to the hub
*/
}
$.connection.hub.start();
Now with this I get in realtime all users that are online.
Note: This is an InMemory solution. Other solutions are here
Hope this helps...
I would create my own system to define what are your "active" users:
You can keep the track of your users with a dictionary stored in the System.Web.Caching.Cache class.
In your base controller (because it would be instantiate for any
client request) insert the current user to your cached dictionary
by calling the KeepTrackActiveUsers method.
Add the SetInactiveUser method to your logout method.
{
private Dictionary<int,DateTime> ActiveUsers
{
get
{
if(Cache["ActiveUsers"] == null)
Cache["ActiveUsers"] = new Dictionary<int,DateTime>();
return Cache["ActiveUsers"];
}
set { Cache["ActiveUsers"] = value; }
}
private void KeepTrackActiveUsers()
{
ActiveUsers[CurrentUserId] = DateTime.Now;
}
private const int expirationTime = 600;
private IEnumerable<int> GetActiveUsers()
{
DateTime now = DateTime.Now;
ActiveUsers = ActiveUsers
.Where(x => now.Subtract(x.Value).TotalSeconds < expirationTime)
.ToDictionary(x => x.Key, x => x.Value);
return ActiveUsers.Keys;
}
private void SetInactiveUser()
{
ActiveUsers.Remove(CurrentUserId);
}
//to be defined
private int CurrentUserId
{
get { return default(int); }
}
Related
I run into another problem. I have a UserRole domain class which takes UserID and RoleID
public class User_Role
{
public Guid Id { get; set; }
public Guid UserId { get; set; }
//nav prop
public User User { get; set; }
public Guid RoleId { get; set; }
//nav prop
public Role Role { get; set; }
}
Roles have 2 types Reader and Admin. I need them for authentication which I will implement later.
The problem is when I added a "Reader" I can add it again and again. I understand that I need to validate user_role. This is my User_role controller
[Route("api/[controller]")]
[ApiController]
public class UserRoleController : ControllerBase
{
public readonly IUserRepository _userRepository;
public readonly IRoleRepository _roleRepository;
public readonly IUserRoleRepository _userRoleRepository;
public UserRoleController(IUserRepository userRepository, IRoleRepository IRoleRepository,
IUserRoleRepository userRoleRepository)
{
_userRepository = userRepository;
_roleRepository = IRoleRepository;
_userRoleRepository = userRoleRepository;
}
[HttpGet]
public async Task<IActionResult> GetAllUserRoles()
{
var userRoleDomain = await _userRoleRepository.GetAllAsync();
return Ok(userRoleDomain);
}
[HttpGet]
[Route("{userRoleId:guid}")]
[ActionName("GetUserRoleById")]
public async Task<IActionResult> GetUserRoleById(Guid userRoleId)
{
var userRoleDomain = await _userRoleRepository.GetUserRoleByIdAsync(userRoleId);
if(userRoleDomain == null)
{
return NotFound();
}
var UserRoleDto = userRoleDomain.ConvertToDto();
return Ok(UserRoleDto);
}
[HttpPost]
public async Task<IActionResult> AddUserRole([FromBody]AddUserRoleRequest addUserRoleRequest)
{
if(!(await ValidateAddUserRoleAsync(addUserRoleRequest)))
{
return BadRequest(ModelState);
}
//CheckIfRoleExist
var userRoleDomain = new User_Role
{
RoleId = addUserRoleRequest.RoleId,
UserId = addUserRoleRequest.UserId
};
userRoleDomain = await _userRoleRepository.AddUserRoleAsync(userRoleDomain);
var userRoleDto = userRoleDomain.ConvertToDto();
return CreatedAtAction(nameof(GetUserRoleById), new { userRoleId = userRoleDto.Id}, userRoleDto);
}
[HttpDelete]
[Route("{userRoleId:guid}")]
public async Task<IActionResult> DeleteUserRole(Guid userRoleId)
{
var userRoleDomain = await _userRoleRepository.DeleteAsync(userRoleId);
if(userRoleDomain == null)
{
return NotFound();
}
var userRoleDto = userRoleDomain.ConvertToDto();
return Ok(userRoleDto);
}
[HttpPut]
[Route("{userRoleId:guid}")]
public async Task<IActionResult> UpdateUserRole([FromRoute]Guid userRoleId,
[FromBody]UpdateUserRoleRequest updateUserRoleRequest)
{
if(!(await ValidateUpdateUserRoleAsync(updateUserRoleRequest)))
{
return BadRequest(ModelState);
}
var userRoleDomain = new User_Role()
{
Id = userRoleId,
UserId = updateUserRoleRequest.UserId,
RoleId = updateUserRoleRequest.RoleId
};
userRoleDomain = await _userRoleRepository.UpddateAsync(userRoleId, userRoleDomain);
if(userRoleDomain == null)
{
return NotFound();
}
var userRoleDto = userRoleDomain.ConvertToDto();
return Ok(userRoleDto);
}
#region Validation methods
private async Task<bool> ValidateAddUserRoleAsync(AddUserRoleRequest addUserRoleRequest)
{
var user = await _userRepository.GetAsyncByIdWithRoles(addUserRoleRequest.UserId);
if (user == null)
{
ModelState.AddModelError(nameof(addUserRoleRequest.UserId),
$"{nameof(addUserRoleRequest.UserId)} UserId is invalid");
}
var role = await _roleRepository.GetAsync(addUserRoleRequest.RoleId);
if(role == null)
{
ModelState.AddModelError(nameof(addUserRoleRequest.RoleId),
$"{nameof(addUserRoleRequest.RoleId)} RoleId is invalid");
}
if(ModelState.ErrorCount > 0)
{
return false;
}
return true;
}
private async Task<bool> ValidateUpdateUserRoleAsync(UpdateUserRoleRequest updateUserRoleRequest)
{
var user = await _userRepository.GetUserByIdASync(updateUserRoleRequest.UserId);
if(user == null)
{
ModelState.AddModelError(nameof(updateUserRoleRequest.UserId),
$"{nameof(updateUserRoleRequest.UserId)} UserId is invalid");
}
var role = await _roleRepository.GetAsync(updateUserRoleRequest.RoleId);
if(role == null)
{
ModelState.AddModelError(nameof(updateUserRoleRequest.RoleId),
$"{nameof(updateUserRoleRequest.RoleId)} RoleId is invalid");
}
if(ModelState.ErrorCount > 0)
{
return false;
}
return true;
}
private async Task<bool> CheckIfRoleExist(AddUserRoleRequest addUserRoleRequest)
{
}
#endregion
}
I am thinking to validate this in my CheckIfRoleExist function. How do I check if this type of role is already added with a specific userId?
Adding call to repository
public async Task<bool> CheckIfAlreadyExistAsync(User_Role userRole)
{
var userRoleExist = await _webApiDbContext.User_Roles.AnyAsync(u => u.RoleId == userRole.RoleId && u.UserId == userRole.UserId);
if(userRoleExist != null)
{
return true;
}
return false;
}
Figured out something like this
private async Task<bool> CheckIfRoleExist(AddUserRoleRequest addUserRoleRequest)
{
var user = await _userRepository.GetAsyncByIdWithRoles(addUserRoleRequest.UserId);
if(user == null)
{
return false;
}
foreach(var roleAdded in user.UserRoles)
{
if(roleAdded.RoleId == addUserRoleRequest.RoleId)
{
ModelState.AddModelError(nameof(addUserRoleRequest.RoleId),
$"{nameof(addUserRoleRequest.RoleId)} user already have this role");
}
}
if (ModelState.ErrorCount > 0)
{
return false;
}
return true;
}
If you have cleaner answer dont be afraid to post it:)
I use an ASP.NET Web API. How do I check my login with a Post request? I want to check if the login already exists in the database, and if so, throw an error.
UsersController:
[HttpPost]
public async Task<ActionResult<User>> PostUser(User user)
{
if (_context.Users == null)
{
return Problem("Entity set 'ShopContext.Users' is null.");
}
var role = await _context.Roles.FindAsync(user.IdRole);
if (role == null)
{
return NotFound();
}
for (int i = 0; i < user.Id; i++)
{
if (i == user.UserName.Length)
{
return Problem("User already registered");
}
}
user.Role = role;
_context.Users.Add(user);
await _context.SaveChangesAsync();
return CreatedAtAction("GetUser", new { id = user.Id }, user);
}
User model:
namespace ShopAPI.Models
{
public class User
{
public int Id { get; set; }
public string? Password { get; set; }
public string? UserName { get; set; }
}
}
[HttpPost]
public async Task<ActionResult<User>> PostUser(User user)
{
var userWithIdAlreadyExists = _context.Users.Find(user.Id) != null;
if(userAlreadyExists)
{
return Problem ("user with id already exists");
}
var userNameAlreadyExists = _context.users
.FirstOrDefault(u => u.UserName == user.UserName) == null;
if(userNameAlreadyExists)
{
return Problem("User already exists with that name");
}
//Write the code you want to execute when the user has a valid id and username
}
I started learning C# and I want to update my model using the [HttpPost] annotation. I tried removing the [FromBody]Item itm parameter and the fields on the repository but it's not working either. Below is my code.
Controller:
[HttpPost("{id}")]
public ActionResult<Item> UpdateItem([FromBody]Item itm, int id)
{
var getItem = _repository.GetItemById(id);
if (getItem == null)
{
return NotFound();
}
_repository.UpdateItem(itm);
_repository.SaveChanges();
return Ok(getItem);
}
Repository:
public void UpdateItem(Item itm)
{
if (itm == null)
{
throw new ArgumentNullException(nameof(itm));
}
var itemToUpdate = this.GetItemById(itm.Id);
if (itm.Name != null)
{
itemToUpdate.Name = itm.Name;
}
itemToUpdate.Price = itm.Price;
itemToUpdate.Condition = itm.Condition;
itemToUpdate.Size = itm.Size;
itemToUpdate.DateSold = itm.DateSold;
itemToUpdate.SellMethod = itm.SellMethod;
_context.Items.Update(itemToUpdate);
}
Interface:
void UpdateItem(Item itm);
Model:
public int Id { get; set; }
[Required]
public string Name { get; set; }
public int Price { get; set; }
public string Condition { get; set; }
public string Size { get; set; }
public string DateSold { get; set; }
public string SellMethod { get; set; }
First of all verify that you're sending that item correctly:
Is the form correct and pointing to that method of your controller?
Are you sending that item via the form (have you used the provided methods for this) ?
After that, if you're sending the item in the body of your post request, then verify the item in the method's parameter is available.
EDIT:
Well, as already discussed with Panagiotis you should rather directly use the DbContext itself as it already provides everything you need.
[HttpPost("{id}")]
public ActionResult<Item> UpdateItem(int id, [FromBody]Item itemData)
{
var foundItem = _dbContext.Items.SingleOrDefault(x => x.Id == id);
if (foundItem == null)
{
return NotFound();
}
foundItem.Name = itemData.Name;
foundItem.Size = itemData.Size;
// and so on
_dbContext.SaveChanges();
return Ok(foundItem);
}
Another way to keep your current structure, but it's not recommended, would be the following:
[HttpPost("{id}")]
public ActionResult<Item> UpdateItem(int id, [FromBody]Item itemData)
{
var updatedItem = _repository.UpdateItem(id, itemData);
if (updatedItem == null)
{
return NotFound();
}
return Ok(updatedItem);
}
public void UpdateItem(int id, Item itemData)
{
// you can validate parameters and throw errors if e.g. itemData == null
var originalItem = GetItemById(id); // this should internally get the item e.g. _dbContext.Items.Where(x => x.id == itemData.Id);
if(originalItem == null)
{
return null;
}
originalItem.Name = itemData.Name;
originalItem.Price = itemData.Price;
originalItem.Condition = itemData.Condition;
originalItem.Size = itemData.Size;
originalItem.DateSold = itemData.DateSold;
originalItem.SellMethod = itemData.SellMethod;
SaveChanges(); // guess this will be _dbContext.SaveChanges() instead
return originalItem;
}
Well, you could also change it to first load the item and then pass the originalItem and the itemData into the UpdateItem method inside your repository. But as you see the better way to directly use the DbContext is more clearer and shorter.
I have a model called notes that I am feeding into a kendo grid via calls to an interface / repository class. Everything works but it is running synchronously and I want to run it asynchronously.
I'm using .NET core 3.1 so IAsyncEnumerable etc should all be available if I can work out how to do it. I've tried a lot of variations but always get errors. Any help much appreciated.
This is the interface
namespace FliveRetry.Models.PTs
{
public interface IPtNoteRepository
{
IEnumerable<Note> GetAllNotes();
Note GetNoteById(int NoteId);
}
}
This is the repository
namespace FliveRetry.Models.PTs
{
public class PtNoteRepository : IPtNoteRepository
{
private readonly FliveRetryContext context;
public PtNoteRepository(FliveRetryContext context)
{
this.context = context ?? throw new ArgumentNullException(nameof(context));
}
public IEnumerable<Note> GetAllNotes()
{
return context.Note;
}
public Note GetNoteById(int itemId)
{
var note = context.Note.SingleOrDefault(i => i.ID == itemId);
return note;
}
}
}
and this is the index model where I'm calling it and feeding it to the grid via OnPostRead
namespace FliveRetry.Pages.Notes
{
public class IndexModel : NoteSelectPageModel
{
private const int CURRENT_USER_ID = 21; //Fake user id for demo
private readonly IPtNoteRepository rpsNotesRepo;
public static IList<Note> Notes { get; set; }
[BindProperty(SupportsGet = true)]
public NoteScreenEnum? PresetScreen { get; set; }
public IndexModel(IPtNoteRepository rpsNotesData)
{
rpsNotesRepo = rpsNotesData;
}
public void OnGet()
{
IEnumerable<Note> notes;
switch (PresetScreen)
{
case NoteScreenEnum.GeneralNotes:
notes = rpsNotesRepo.GetAllNotes();
break;
case NoteScreenEnum.ThisNote:
notes = rpsNotesRepo.GetNoteByID(CURRENT_USER_ID);
break;
default:
notes = rpsNotesRepo.GetAllNotes();
break;
}
Notes = notes.ToList();
}
public JsonResult OnPostRead([DataSourceRequest] DataSourceRequest request)
{
return new JsonResult(Notes.ToDataSourceResult(request));
}
}
}
In other pages like create or edit.cshtml.cs for example I am successfully using async to edit and create, e.g:
namespace FliveRetry.Pages.Notes
{
public class EditModel : NoteSelectPageModel
{
private readonly FliveRetry.Data.FliveRetryContext _context;
public EditModel(FliveRetry.Data.FliveRetryContext context)
{
_context = context;
}
[BindProperty]
public Note Note { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Note = await _context.Note
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (Note == null)
{
return NotFound();
}
return Page();
}
public async Task<IActionResult> OnPostAsync(IFormCollection form, int? id, string[] selectedOrgs, string[] selectedClients, string[] selectedStaffs, string[] selectedNoteTypes)
{
if (!ModelState.IsValid)
{
return Page();
}
var noteToUpdate = await _context.Note
.FirstOrDefaultAsync(s => s.ID == id);
if (await TryUpdateModelAsync<Note>(noteToUpdate, "note", // Prefix for form value.
c => c.Title, c => c.NoteText, c => c.NoteDate, c => c.Amount, c => c.ImageURL, c => c.FileURL, c => c.Archived, c => c.DateSaved, c => c.UserID, c => c.StartTime, c => c.FinishTime))
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return Page();
}
}
}
Try to use below code to convert synchronous action to asynchronous action:
IPtNoteRepository:
public interface IPtNoteRepository
{
Task<IEnumerable<Note>> GetAllNotesAsync();
Task<Note> GetNoteByIdAsync(int NoteId);
}
Repository:
public class PtNoteRepository : IPtNoteRepository
{
private readonly FliveRetryContext context;
public PtNoteRepository(FliveRetryContext context)
{
this.context = context ?? throw new ArgumentNullException(nameof(context));
}
public async Task<IEnumerable<Note>> GetAllNotesAsync()
{
return await context.Note.ToListAsync();
}
public async Task<Note> GetNoteByIdAsync(int itemId)
{
var note = await context.Note.SingleOrDefaultAsync(i => i.ID == itemId);
return note;
}
}
IndexModel:
public async Task OnGetAsync()
{
IEnumerable<Note> notes;
switch (PresetScreen)
{
case NoteScreenEnum.GeneralNotes:
notes = await rpsNotesRepo.GetAllNotesAsync();
break;
case NoteScreenEnum.ThisNote:
notes = await rpsNotesRepo.GetNoteByIdAsync(CURRENT_USER_ID);
break;
default:
notes = await rpsNotesRepo.GetAllNotesAsync();
break;
}
Notes = notes.ToList();
}
I have the the following Repository with cache
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
}
public interface IUserRepository
{
User GetUser(int userId);
}
public class CacheObject
{
public int UserId { get; set; }
public User User { get; set; }
public DateTime CreationDate { get; set; }
}
public class CachedUserRepository : IUserRepository
{
private IUserRepository _userRepository;
private List<CacheObject> _cache = new List<CacheObject>();
private int _cacheDuration = 60;
public CachedUserRepository(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public User GetUser(int userId)
{
bool addToCache = false;
CacheObject valueFromCache = _cache.SingleOrDefault(u => u.UserId == userId);
// user was found
if (valueFromCache != null)
{
// if cache is outdated then remove value from it
if (valueFromCache.CreationDate.AddSeconds(_cacheDuration) < DateTime.Now)
{
_cache.Remove(valueFromCache);
addToCache = true;
}
else {
// update cache date
valueFromCache.CreationDate = DateTime.Now;
return valueFromCache.User;
}
}
// user is absent in cache
else {
addToCache = true;
}
if (addToCache)
{
User result = _userRepository.GetUser(userId);
_cache.Add(new CacheObject() { User = result, UserId = userId, CreationDate = DateTime.Now });
return result;
}
return null;
}
}
I would like to run method GetUser() in different threads so i need to make this method thread safe.
How can i make it ?
I don't see any elegant solution , only lock(someObject) to the whole method body. But as result i will not achieve any performance gain
We usually do this with a ReaderWriterLockSlim like this:
public class CachedUserRepository : IUserRepository
{
private readonly ReaderWriterLockSlim _cacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
private IUserRepository _userRepository;
private List<CacheObject> _cache = new List<CacheObject>();
private int _cacheDuration = 60;
public CachedUserRepository(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public User GetUser(int userId)
{
bool addToCache = false;
// Enter an upgradeable read lock because we might have to use a write lock if having to update the cache
// Multiple threads can read the cache at the same time
_cacheLock.EnterUpgradeableReadLock();
try
{
CacheObject valueFromCache = _cache.SingleOrDefault(u => u.UserId == userId);
// user was found
if (valueFromCache != null)
{
// if cache is outdated then remove value from it
if (valueFromCache.CreationDate.AddSeconds(_cacheDuration) < DateTime.Now)
{
// Upgrade to a write lock, as an item has to be removed from the cache.
// We will only enter the write lock if nobody holds either a read or write lock
_cacheLock.EnterWriteLock();
try
{
_cache.Remove(valueFromCache);
}
finally
{
_cacheLock.ExitWriteLock();
}
addToCache = true;
}
else
{
// update cache date
valueFromCache.CreationDate = DateTime.Now;
return valueFromCache.User;
}
}
// user is absent in cache
else
{
addToCache = true;
}
if (addToCache)
{
User result = _userRepository.GetUser(userId);
// Upgrade to a write lock, as an item will (probably) be added to the cache.
// We will only enter the write lock if nobody holds either a read or write lock
_cacheLock.EnterWriteLock();
try
{
if (_cache.Any(u => u.UserId != userId))
{
_cache.Add(new CacheObject() {User = result, UserId = userId, CreationDate = DateTime.Now});
}
}
finally
{
_cacheLock.ExitWriteLock();
}
return result;
}
}
finally
{
_cacheLock.ExitUpgradeableReadLock();
}
return null;
}
}
With this, multiple threads will be able to read the cache simultaneously, but if it has to be written, it will get locked.
Disclaimer: I haven't run the code to check it ;)