I am working on an ASP.NET Core web application. This application works with a SQL Server database and I am using Entity Framework 6.
I want to automatically add, on each entity, a last modification date and user id.
My question is: how can I automatically set the user id to each entity?
I have tried to override SaveChanges() in the DbContext. It works fine, but I can't access the Microsoft identity classes there...
Thanks
(Apologies in advance if the method signatures look a bit odd, I'm one of the rare breed that uses explicit interface implementation to help avoid stale code dust-bunnies littering code as I re-factor)
An example on accessing a session state to get the user from within a DbContext using a session token from a Forms Authentication web application:
[Serializable]
public class UserDetails
{
public int UserId { get; set; }
public string DisplayName { get; set; }
}
public interface ISessionHelper
{
UserDetails CurrentUser { get; set; }
void Clear();
bool IsAuthenticated();
}
public class SessionHelper : ISessionHelper
{
private const string UserDetailsKey = "YourUniqueSessionIdKey";
private ISessionHelper This => this;
UserDetails ISessionHelper.CurrentUser
{
get
{
try
{
var token = (UserDetails)HttpContext.Current.Session[UserDetailsKey];
return token;
}
catch
{
throw new ApplicationException("The current session could not be resolved.");
}
}
set
{
try
{
HttpContext.Current.Session[UserDetailsKey] = value;
}
catch
{
throw new ApplicationException("The current session state could not be set.");
}
}
}
/// <summary>
/// <see cref="ISessionHelper.Clear"/>
/// </summary>
void ISessionHelper.Clear()
{
HttpContext.Current.Session.Clear();
}
/// <summary>
/// <see cref="ISessionHelper.IsAuthenticated(string)"/>
/// </summary>
bool ISessionHelper.IsAuthenticated()
{
try
{
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
var ticket = FormsAuthentication.Decrypt(cookie.Value);
var userDetails = This.CurrentUser;
return (userDetails != null && userDetails.UserId != 0);
}
catch
{
return false;
}
}
}
public interface ICurrentUserLocator
{
bool IsUserLoggedIn { get; }
int CurrentUserId { get; }
string CurrentUserDisplayName { get; }
}
public sealed class FormsAuthenticationUserLocator : ICurrentUserLocator
{
private const string UnauthorizedAccessExceptionMessage = "No user is currently logged in.";
private readonly ISessionHelper _sessionHelper = null;
[ExcludeFromCodeCoverage]
private ICurrentUserLocator This => this;
private UserDetails _userDetails = null;
[ExcludeFromCodeCoverage]
private UserDetails CurrentUser
{
get { return _userDetails ?? (_userDetails = SessionHelper.UserDetails); }
}
[ExcludeFromCodeCoverage]
bool ICurrentUserLocator.IsUserLoggedIn => CurrentUser != null;
[ExcludeFromCodeCoverage]
int ICurrentUserLocator.CurrentUserId => CurrentUser?.UserId ?? throw new UnauthorizedAccessException(UnauthorizedAccessExceptionMessage);
[ExcludeFromCodeCoverage]
string ICurrentUserLocator.CurrentUserDisplayName => CurrentUser?.DisplayName ?? throw new UnauthorizedAccessException(UnauthorizedAccessExceptionMessage);
public FormsAuthenticationUserLocator(ISessionHelper sessionHelper)
{
_sessionHelper = sessionHelper ?? throw new ArgumentNullException("sessioNHelper");
}
}
Assuming you are using dependency injection with a container, register the two classes against their respective interfaces in the container on a per-request lifespan. Then add a dependency on ICurrentUserLocator into your DbContext:
public class AppDbContext : DbContext
{
private readonly ICurrentUserLocator _currentUserLocator = null;
public AppDbContext(ICurrentUserLocator currentUserLocator)
{
_currentUserLocator = currentUserLocator ?? throw new ArgumentNullException("currentUserLocator");
}
}
Now in your overridden SaveChanges you can fetch the current user ID:
var currentUser = Users.Single(x => x.UserId == _currentUserLocator.CurrentUserId);
var updatedEntities = ChangeTracker.Entries()
.Where(x => x.State == EntityState.Modified)
.Select(x => x.Entity)
.Cast<EditableEntityBase>();
foreach (var entity in updatedEntities)
{
entity.LastModifiedBy = currentUser;
entity.LastModifiedAt = DateTime.Now;
}
var insertedEntities = ChangeTracker.Entries()
.Where(x => x.State == EntityState.Added)
.Select(x => x.Entity)
.Cast<EditableEntityBase>();
foreach (var entity in insertedEntities)
{
entity.CreatedBy = entity.LastModifiedBy = currentUser;
entity.CreatedAt = entity.LastModifiedAt = DateTime.Now;
}
If you only expose the FKs rather than user navigation properties then you can just set the ID. If your app has a Log Out feature be sure to call the SessionHelper.Clear() method. In this example I modified the session helper I typically use to just focus on the current user details to keep it simple. It normally tracks a few other details, hence why the Clear() method wipes the entire session. It's a standardized wrapper for handling known session state rather than littering code with Session["somestring"] everywhere.
Related
I'm building some REST API server in .NET Core and using Postman software to test it. I have a problem with POST method which doesn't return me any value ("Could not get any response") when I try to perform second Add operation on my DBContext class inside CreateUser method. My code :
UsersController :
[Produces("application/json")]
[Route("api/[controller]")]
public class UsersController : Controller
{
private readonly DBContext _context;
#region CONSTRUCTOR
public UsersController(DBContext context)
{
_context = context;
}
#endregion
#region HTTP GET
// GET: api/users || api/users?cardnr=xxx
[HttpGet]
public async Task<IActionResult> GetUsers(string cardNr)
{
if (String.IsNullOrEmpty(cardNr))
{
try
{
var users = await _context.Users.ToListAsync();
if (users.Any())
{
return Json(users);
}
else
{
return NotFound();
}
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
else
{
try
{
var user = await _context.Users.FirstOrDefaultAsync(u => u.Cards.Any(c => c.CardNumber.Equals(cardNr)));
if (user == null)
{
return NotFound();
}
else
{
return new ObjectResult(user);
}
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
}
//GET: api/users/1
[HttpGet("{id}", Name = "GetUserByID")]
public async Task<IActionResult> GetUserByID(Int32 id)
{
try
{
var user = await _context.Users.FirstOrDefaultAsync(u => u.IDUser == id);
if (user == null)
{
return NotFound();
}
else
{
return new ObjectResult(user);
}
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
#endregion
#region HTTP POST
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] Models.User userToCreate, string userGroupID)
{
if (userToCreate == null)
{
return BadRequest();
}
else
{
try
{
_context.Users.Add(userToCreate);
int parsingResult;
// if user passed userGroupID
if (userGroupID != null)
{
// parsing if userGroupID is a number
if (!int.TryParse(userGroupID, out parsingResult))
{
return BadRequest();
}
else
{
// if client want to assign a new user to some group
if (parsingResult > 0)
{
// creating new record in UserGroup table - assigning a user to group
var userGroup = new Models.UserGroup();
_context.Entry(userGroup).Property("IDGroup").CurrentValue = parsingResult;
_context.Entry(userGroup).Property("IDUser").CurrentValue = userToCreate.IDUser;
_context.UserGroups.Add(userGroup); // NOTE HERE
}
}
}
await _context.SaveChangesAsync();
return CreatedAtRoute("GetUserByID", new { id = userToCreate.IDUser }, userToCreate);
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
}
#endregion
}
User model :
public class User
{
[Key]
public int IDUser { get; set; }
[Required]
public string Name { get; set; }
public List<UserGroup> UsersGroups { get; set; }
}
UserGroup model :
public class UserGroup
{
public Group Group { get; set; }
public User User { get; set; }
}
DBContext class :
public class DBContext : DbContext
{
public DBContext(DbContextOptions<DBContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// shadow property - foreign key
modelBuilder.Entity<UserGroup>()
.Property<int>("IDUser");
// shadow property - foreign key
modelBuilder.Entity<UserGroup>()
.Property<int>("IDGroup");
modelBuilder.Entity<UserGroup>()
.HasKey( new string[]{ "IDUser", "IDGroup" });
modelBuilder.Entity<UserGroup>()
.HasOne(ug => ug.Group)
.WithMany(g => g.UsersGroups)
.HasForeignKey("IDGroup");
modelBuilder.Entity<UserGroup>()
.HasOne(ug => ug.User)
.WithMany(u => u.UsersGroups)
.HasForeignKey("IDUser");
base.OnModelCreating(modelBuilder);
}
public DbSet<Group> Groups { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UserGroup> UserGroups { get; set; }
}
The problem lies in HttpPost method in UsersController.
When I do "normal" POST and pass JSON object which contain a user to add without assigning it to group (empty userGroupID parameter) everything is ok - user gets added to the DataBase and Postman returns me a user with its ID.
screen :
and when I try to add a new user but with adding it to specific group I always get an error :
screen :
Even despite that error new user gets properly added to DB and associated with its group (record gets added to UserGroup table; UserGroup is join table between Users and Groups table). So I have proper data in my DB but I always get this error and I can't return new added user to client who called API and can't get his ID. Am I doing something wrong in my CreateUser method ?
UPDATE :
I have added a comment line in "NOTE HERE" in CreateUser method in UsersController. If I comment whole this line I don't get an error from Postman but obviously I don't get my User associated with its group (I don't get new record added to UserGroup join table). So it seems like another Add method on context object causing an error ... Does it make sense ?
Did you try to debug it?
Set a breakpoint on the row:
if (userToCreate == null)
Send again the request with Postman and debug your app. There you can see what and where it goes wrong.
Please let me know how it is going so I know how can I help you:)
I have table in my database with names of users (with domains, for example: Domain1\user1). My project has Windows Authentication. I have two controllers - one for all logged in users and second for specific user. My table has 3 columns: (Id, Name, Extra), where "Extra" is only fill for user, who is admin (it has varchar: "admin").
I want to create such authorization, where only admin will have access to site with second controller. How to write it?
For any suggestions I will be very appreciate.
Thanks in advance for help. ;)
Monic
====Edit====
from example: ASP.NET MVC 4 Custom Authorize Attribute with Permission Codes (without roles)
In my main controller:
[AuthorizeUser(AccessLevel = "Extra")]
public class SecureController : Controller
{
(...)
}
public class AuthorizeUserAttribute : AuthorizeAttribute
{
public string AccessLevel { get; set; }
private Report_DBEnt REn = new Report_DBEnt();
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (!isAuthorized)
{
return false;
}
string privilegeLevels = string.Join("", REn.Users.Where(u => u.Extra.Equals("admin")).FirstOrDefault());
if (privilegeLevels.Contains(this.AccessLevel))
{
return true;
}
else
{
return false;
}
}
}
I've tried use it sth like this, but I have no access to my site.
Try this:
[AuthorizeUser(AccessLevel = "admin")]
public class SecureController : Controller
{
(...)
}
Hope it will help
Update
public class AuthorizeUserAttribute : AuthorizeAttribute
{
public string AccessLevel { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if(httpContext.User.Identity.IsAuthenticated)
{
string privilegeLevels = string.Join("",GetUserRights(httpContext.User.Identity.Name.ToString());
if (privilegeLevels.Contains(this.AccessLevel))
{
return true;
}
else
{
return false;
}
}
else
return false;
}
}
private string GetUserRights(string userName)
{
private Report_DBEnt REn = new Report_DBEnt();
return REn.Users.Where(u => u.UserName== userName).Select(u=>u.Extra);
}
I have two Classes: LicenseType and EntityType.
[Table("LicenseType")]
public class LicenseType : ComplianceBase, INotifyPropertyChanged
{
private List<Certification> _certifications = new List<Certification>();
private List<EntityType> _entityTypes = new List<EntityType>();
public List<EntityType> EntityTypes
{
get { return _entityTypes; }
set { _entityTypes = value; }
}
public List<Certification> Certifications
{
get { return _certifications; }
set { _certifications = value; }
}
}
and
[Table("EntityType")]
public class EntityType : ComplianceBase, INotifyPropertyChanged
{
private List<LicenseType> _licenseTypes = new List<LicenseType>();
public List<LicenseType> LicenseTypes
{
get { return _licenseTypes; }
set
{
_licenseTypes = value;
// OnPropertyChanged();
}
}
}
The both derive from ComplianceBase,
public class ComplianceBase
{
private int _id;
private string _name;
private string _description;
public string Description
{
get { return _description; }
set
{
if (_description == value) return;
_description = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public int Id
{
get { return _id; }
set
{
if (value == _id) return;
_id = value;
OnPropertyChanged();
}
}
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
What I want is to be able to do is associate an EntityType with one or more LicenseTypes, so for instance, an EntityType "Primary Lender" could be associated with say two LicenseTypes, "Lender License" and "Mortgage License". In this situation, I want one record in the EntityType table, "Primary Lender" and two records in my LicenseType table: "Lender License" and "Mortgage License".
The code for adding related LicenseTypes to my EntityType is done by calling:
_currentEntity.LicenseTypes.Add(licenseType);
and then calling _context.SaveChanges();
There is an additional table, "EntityTypeLicenseTypes" that serves as the lookup table to relate these two tables. There are two records to join the EntityType with the two related LicenseTypes.
And this works. However, my code also adds (it duplicates) the LicenseType record and adds it in the LicenseType table for those records that are being associated.
How can I stop this from happening?
In order to avoid the duplication you must attach the licenseType to the context:
_context.LicenseTypes.Attach(licenseType);
_currentEntity.LicenseTypes.Add(licenseType);
_context.SaveChanges();
I'm a newbie with Entity Framework, and I have looked at the questions with the same title, but I haven't found a satisfying answer yet.
Here is my class:
public class MyUser
{
public string FirstName { get; set; }
public virtual ICollection<ProfileSkillEdu> Skills { get; set; }
}
And in the controller I have:
[HttpPost]
public ActionResult EditProfile(MyUser user, string emailAddress)
{
try
{
if (ModelState.IsValid)
{
_unitOfWork.GetMyUserRepository().Update(user);
_unitOfWork.Save();
return View(user);
}
}
catch (DataException)
{
//Log the error
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
}
return View(user);
}
In my user repository:
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
I keep getting An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key. at dbSet.Attach(entityToUpdate). I watched the variables and I found that if MyUser has only 1 Skill object, it's fine because when it does Attach, the key is unique (value is 0). But if MyUser has 2 skill objects, both primary key have the value of 0 and therefore the error.
Can someone explain why all the Skill object's primary key have the values of 0? Also, is there a simple solution to this problem? I thought it's supposed to be straight forward but I have been struggling with this for hours.
Edit:
I wonder if the problem is because of how I use the context.
In the controller definition I have:
public class MyAccountController : Controller
{
IUnitOfWork _unitOfWork;
public ActionResult EditProfile()
{
if (_user == null)
{
MembershipUser currentUser = Membership.GetUser();
if (currentUser == null)
{
return RedirectToAction("Logon", "Account");
}
Guid currentUserId = (Guid)currentUser.ProviderUserKey;
MyUserService svc = new MyUserService();
MyUser user = svc.GetUserLoaded(currentUserId); //this uses include/eager loading to get the Skills too.
if (user == null)
{
return RedirectToAction("Logon", "Account");
}
else
_user = user;
}
return View(_user);
}
}
In my UnitOfWork I have:
public class UnitOfWork:IUnitOfWork
{
private GlobalContext context = new GlobalContext();
private GenericRepository<MyUser> myUserRepository;
private GenericRepository<Skill> skillRepository;
.. and implementation of Save() and Dispose()
}
I am using AutoMapper to map between an entity and an Interface
First I created my mapping and checked it is valid.
AutoMapper.Mapper.CreateMap<User, IUserViewModel>();
AutoMapper.Mapper.AssertConfigurationIsValid();
Then I created a method that uses this mapping:
public IUserViewModel GetUser(int id)
{
var user= _userRepository.GetByKey(id);
var currentUser = Mapper.Map<User, IUserViewModel>(user);
return currentUser;
}
I am using this method in another place of my code
IUserViewModel myUser = XXXXX.GetUser(3);
This issue is this is myUser is always null.
However, when I debug my method and stop inside it juste before returning , I can see that my object currentSupplier is created and filled up correctly.
But when the method returns I get a null value.
I guess this has to do with the fact my object currentSupplier is created as
Proxy<....>
Any help?
Thank you.
Adding a full copy of my code that proves that the above works for me - couldnt fit it into the comments.
class Program
{
static void Main(string[] args)
{
Program program = new Program();
IUserViewModel myUser = program.GetUser(3);
Console.WriteLine(myUser.Name); // Prints Frank
Console.Read();
}
public Program()
{
Mapper.CreateMap<User, IUserViewModel>();
Mapper.AssertConfigurationIsValid();
}
private UserRepo _userRepository = new UserRepo();
public IUserViewModel GetUser(int id)
{
var user = _userRepository.GetByKey(id);
var currentUser = Mapper.Map<User, IUserViewModel>(user);
return currentUser;
}
}
public class UserRepo
{
public User GetByKey(int id)
{
return new User { Name = "Frank" };
}
}
public interface IUserViewModel
{
string Name { get; set; }
}
public class User
{
public string Name { get; set; }
}
Could you add some additional content to show where this is failing?