cannot make changes in context persist - c#

I am working on a simple 3-tier MVC app, and cannot make context changes persist. Here is my code:
Controller:
[HttpPost]
public IActionResult Register(UserDto user)
{
userService.Register(user);
return Ok("profile");
}
Service:
public void Register(UserDto userDto)
{
User user = new User();
user.FirstName = userDto.FirstName;
user.LastName = userDto.LastName;
user.Type = (UserType) userDto.Type;
unitOfWork.UserRepository.AddAsync(user);
}
Repository:
public Repository(ApplicationDbContext dbContext)
{
this.dbContext = dbContext;
this.dbSet = dbContext.Set<T>();
}
public async Task AddAsync(params T[] entities)
{
await dbSet.AddRangeAsync(entities);
await SaveChanges();
}
private async Task SaveChanges()
{
await dbContext.SaveChangesAsync();
}
Using my debugger, i found out that the dbset.AddRangeAsync does add the entity i set in the request into my dbContext. The problem is, on the next request, the entity is gone from the dbContext, and also does not appear in my database.Can somebody can explain me what am i doing wrong?

Your add method is asynchronous.other methods that call this method must be asynchronous.
public async Task Register(UserDto userDto)
{
User user = new User();
user.FirstName = userDto.FirstName;
user.LastName = userDto.LastName;
user.Type = (UserType) userDto.Type;
await unitOfWork.UserRepository.AddAsync(user);
}
[HttpPost]
public async Task<IActionResult> Register(UserDto user)
{
await userService.Register(user);
return Ok("profile");
}

Related

Variable in Authorization Attribute .net in base Controller

In my application, I'm trying to build an authorization - privilege based authentication.
For better understanding here's how my privileges are named and stored in my database : "GET_USERS" , "GET_USER", "DELETE_USER" ...
What I want to do is to specify the privilege name in the authorization attribute from my base controller, but the problem is that the authorization attribute only allows constant parameters.
Here's my base Controller :
public class BaseController<T> : ControllerBase where T : class
{
protected readonly ConcreetDataContext _context;
protected readonly IBaseRepository<T> _repo;
protected readonly INotificationRepository _notificationRepo;
protected readonly IHubContext<NotificationHub> _hubContext;
protected readonly DbSet<T> _dbSet;
public BaseController(IHubContext<NotificationHub> hubContext,
ConcreetDataContext context,
IBaseRepository<T> repo,
INotificationRepository notifRepo)
{
_context = context;
_hubContext = hubContext;
_repo = repo;
_dbSet = _context.Set<T>();
_notificationRepo = notifRepo;
}
// GET: api/[items]
[HttpGet]
// HERE's THE ISSUE
[PrivilegeAuthorize("GET_"+typeof(T).Name.toUpper()] // this is not allowed
public async Task<ActionResult<IEnumerable<T>>> Get([FromQuery] GlobalParams globalParams)
{
Type t = typeof(T);
Console.WriteLine(t.Name.ToUpper());
var classes = await PagedList<T>.CreateAsync(_repo.Get(globalParams),globalParams.PageNumber,globalParams.PageSize);
Response.AddPagination(classes.CurrentPage, classes.PageSize, classes.TotalCount, classes.TotalPages);
return Ok(classes);
}
// GET: api/[items]/5
[HttpGet("{id}")]
public virtual async Task<ActionResult<T>> Get(int id)
{
var item = await this._repo.GetByID(id);
if (item == null)
{
return NotFound();
}
return item;
}
// PUT: api/[items]/5
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPut("{id}")]
public async Task<IActionResult> Put(int id, T item)
{
// if (id != item.ID)
// {
// return BadRequest();
// }
try
{
await this._repo.Update(item);
// Creating the notification
await this._notificationRepo.CreateNotification("Update",typeof(T).ToString(),"Updated "+typeof(T).ToString()+" with ID : "+id);
}
catch (DbUpdateConcurrencyException)
{
if (!(await Exists(id)))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/Classes
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPost]
public async virtual Task<ActionResult<T>> Post(T item)
{
await this._repo.Insert(item);
await this._notificationRepo.CreateNotification("Create",typeof(T).ToString(),"Created "+typeof(T).ToString());
return CreatedAtAction("Get", item);
}
// DELETE: api/Classes/5
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
var item = await _dbSet.FindAsync(id);
if (item == null)
{
return NotFound();
}
_dbSet.Remove(item);
await _context.SaveChangesAsync();
var notification = await this._notificationRepo.CreateNotification("Delete",typeof(T).ToString(),"Deleted "+typeof(T).ToString());
// Invoking BroadCastToUserFunction
var useID = Request.HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value;
await _hubContext.Clients.User(useID).SendAsync("simo",notification);
return NoContent();
}
private async Task<bool> Exists(int id)
{
var item = await this._repo.GetByID(id);
if(item != null)
return true;
return false;
}
[HttpGet("count")]
public async Task<ActionResult<int>> Count([FromQuery] GlobalParams globalParams)
{
return await this._repo.Count(globalParams);
}
}
Thank you in advance !
As Lasse V. Karlsen pointed out in comments: "base class is compiled once, it is not compiled for each Variant, so the compiler will have to figure out what to pass as a string there once".
So I went with the following solution for the moment :
[PrivilegeAuthorize("GET_USERS")]
public override Task<ActionResult<IEnumerable<City>>> Get([FromQuery] GlobalParams globalParams)
{
return base.Get(globalParams);
}
I should override each method where I want to apply this authorization attribute.

ASP.NET Boilerplate: Get and GetAll is POST instead of GET method

I'm using the ASP.NET Boilerplate framework to do a small project and I used Swagger UI Integration to check the API within the project. I noticed that the Get and GetAll are still using POST method while the others are in their respective HTTP verb for example Create is POST, Edit is UPDATE, etc... I read the documentation and tried what they suggested in this [Documentation]
(https://aspnetboilerplate.com/Pages/Documents/Dynamic-Web-API#http-verbs)
Edit 1: Code for UserAppServiceClass
[AbpAuthorize(PermissionNames.Pages_Users)]
public class UserAppService : AsyncCrudAppService<User, UserDto, long, PagedResultRequestDto, CreateUserDto, UpdateUserDto>, IUserAppService
{
private readonly UserManager _userManager;
private readonly RoleManager _roleManager;
private readonly IRepository<Role> _roleRepository;
public UserAppService(
IRepository<User, long> repository,
UserManager userManager,
IRepository<Role> roleRepository,
RoleManager roleManager)
: base(repository)
{
_userManager = userManager;
_roleRepository = roleRepository;
_roleManager = roleManager;
}
[HttpGet]
public override async Task<UserDto> Get(EntityDto<long> input)
{
var user = await base.Get(input);
var userRoles = await _userManager.GetRolesAsync(user.Id);
user.Roles = userRoles.Select(ur => ur).ToArray();
return user;
}
[HttpPost]
public override async Task<UserDto> Create(CreateUserDto input)
{
CheckCreatePermission();
var user = ObjectMapper.Map<User>(input);
user.TenantId = AbpSession.TenantId;
user.Password = new PasswordHasher().HashPassword(input.Password);
user.IsEmailConfirmed = true;
//Assign roles
user.Roles = new Collection<UserRole>();
foreach (var roleName in input.RoleNames)
{
var role = await _roleManager.GetRoleByNameAsync(roleName);
user.Roles.Add(new UserRole(AbpSession.TenantId, user.Id, role.Id));
}
CheckErrors(await _userManager.CreateAsync(user));
return MapToEntityDto(user);
}
[HttpPut]
public override async Task<UserDto> Update(UpdateUserDto input)
{
CheckUpdatePermission();
var user = await _userManager.GetUserByIdAsync(input.Id);
MapToEntity(input, user);
CheckErrors(await _userManager.UpdateAsync(user));
if (input.RoleNames != null)
{
CheckErrors(await _userManager.SetRoles(user, input.RoleNames));
}
return await Get(input);
}
[HttpDelete]
public override async Task Delete(EntityDto<long> input)
{
var user = await _userManager.GetUserByIdAsync(input.Id);
await _userManager.DeleteAsync(user);
}
[HttpGet]
public async Task<ListResultDto<RoleDto>> GetRoles()
{
var roles = await _roleRepository.GetAllListAsync();
return new ListResultDto<RoleDto>(ObjectMapper.Map<List<RoleDto>>(roles));
}
protected override User MapToEntity(CreateUserDto createInput)
{
var user = ObjectMapper.Map<User>(createInput);
return user;
}
protected override void MapToEntity(UpdateUserDto input, User user)
{
ObjectMapper.Map(input, user);
}
protected override IQueryable<User> CreateFilteredQuery(PagedResultRequestDto input)
{
return Repository.GetAllIncluding(x => x.Roles);
}
protected override async Task<User> GetEntityByIdAsync(long id)
{
var user = Repository.GetAllIncluding(x => x.Roles).FirstOrDefault(x => x.Id == id);
return await Task.FromResult(user);
}
protected override IQueryable<User> ApplySorting(IQueryable<User> query, PagedResultRequestDto input)
{
return query.OrderBy(r => r.UserName);
}
protected virtual void CheckErrors(IdentityResult identityResult)
{
identityResult.CheckErrors(LocalizationManager);
}
}
Edit 2:
Your Get method has complex input argument: Get(EntityDto<long> input). HTTP GET verb can't pass it to server, that is why ASP.NET Boilerplate has no choice, it decide to assign POST verb instead. Only parameters like /Get?name=Max&age=22 are allowed for GET - at this case method's signature will be like this: Get(string name, int age).
So you should leave all of it without changes or change signature to flattern variant, enumerating properties of EntityDto<long> as comma separated arguments.
You have to write Http Verb attributes to the interface. Not to the method implementations!
public interface IUserAppService: IApplicationService
{
[HttpGet]
GetUserOutput Get(GetInput input);
[HttpGet]
GetAllOutput GetAll(GetAllInput input);
}
Do it for IAsyncCrudAppService
PS: For the methods come from base, you can new them in your interface.

.NET Core 2.0 AddToRoleAsync - User name is invalid, can only contain letters or digits

Im working with .NET Core 2.0 MVC and Entity Framework with Individual User Accounts. By default, usernames are the same as email address. I used the following in Startup.cs to create the roles
private async Task CreateRoles(IServiceProvider serviceProvider)
{
//adding custom roles
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
string[] roleNames = { "Admin", "User" };
IdentityResult roleResult;
foreach (var roleName in roleNames)
{
//creating the roles and seeding them to the database
var roleExist = await RoleManager.RoleExistsAsync(roleName);
if (!roleExist)
{
roleResult = await RoleManager.CreateAsync(new IdentityRole(roleName));
}
}
//creating a super user who could maintain the web app
var poweruser = new ApplicationUser
{
UserName = Configuration.GetSection("UserSettings")["UserEmail"],
Email = Configuration.GetSection("UserSettings")["UserEmail"]
};
string UserPassword = Configuration.GetSection("UserSettings")["UserPassword"];
var _user = await UserManager.FindByEmailAsync(Configuration.GetSection("UserSettings")["UserEmail"]);
if (_user == null)
{
var createPowerUser = await UserManager.CreateAsync(poweruser, UserPassword);
if (createPowerUser.Succeeded)
{
//here we tie the new user to the "Admin" role
await UserManager.AddToRoleAsync(poweruser, "Admin");
}
}
}
And called it from the Configure method in Startup.cs. The roles added just fine, and the role was added to the admin.
However when I try to add a role to a user programmatically using the method await _userManager.AddToRoleAsync(applicationUser, "Admin"); in my ApplicationUsersController I get the error
User name is invalid, can only contain letters or digits
The .NET Core 2.0 documentation here https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-configuration?tabs=aspnetcore2x indicates that the "#" and/or "." that may be causing the error are included as valid characters by default in AllowedUserNameCharacters. I'm at a loss of other things to try.
Here is my ApplicationUsersController code:
[Authorize(Roles = "Admin")]
public class ApplicationUsersController : Controller
{
private readonly ApplicationDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;
public ApplicationUsersController(UserManager<ApplicationUser> userManager, ApplicationDbContext context)
{
_userManager = userManager;
_context = context;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(string id, [Bind("Id,Name,Email,IsAdmin,ConcurrencyStamp,SecurityStamp")] ApplicationUser applicationUser)
{
if (id != applicationUser.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
applicationUser.DateUpdated = DateTime.Now;
applicationUser.NormalizedEmail = applicationUser.Email.ToUpper();
_context.Update(applicationUser);
await _context.SaveChangesAsync();
if (applicationUser.IsAdmin)
{
var x = await _userManager.AddToRoleAsync(applicationUser, "Admin");
if (!x.Succeeded)
{
string s = "";
}
}
else
{
await _userManager.AddToRoleAsync(applicationUser, "User");
}
}
catch (DbUpdateConcurrencyException)
{
if (!ApplicationUserExists(applicationUser.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(applicationUser);
}

Not updating object on database ASP.net core 2.0

I am having a little issue that I cannot fully comprehend.
On my controller I have:
[HttpPut("{id}")]
public async Task<IActionResult> PutExercise([FromRoute] int id, [FromBody] Exercise exercise)
{
logger.LogInformation("Updating item {ID}", id);
if (!ModelState.IsValid)
return BadRequest(ModelState);
if (id != exercise.ExerciseId)
return BadRequest();
var baseExercise = await repository.GetExercise(id);
if (baseExercise == null)
return NotFound(id);
baseExercise = exercise;
await unitOfWork.CompleteAsync();
return NoContent();
}
but it is NOT updating on the database with the values provided on the PUT body request. Although on memory they are changed, the object is not pushed... But if I set a breakpoint on await unitOfWork.CompleteAsync(); and then modify baseExercise with the debugger, it does update. Why is this happening?
public class UnitOfWork : IUnitOfWork
{
private readonly FitaholicContext context;
public UnitOfWork(FitaholicContext context)
{
this.context = context;
}
public Task CompleteAsync()
{
return context.SaveChangesAsync();
}
}
--
public async Task<Exercise> GetExercise(int id, bool includeRelated = false)
{
if (!includeRelated)
return await context.Exercise.SingleOrDefaultAsync(m => m.ExerciseId == id);
return await context.Exercise
.Include(e => e.Equipments)
.ThenInclude(ee => ee.Equipment)
.Include(e => e.TypeRecord)
.SingleOrDefaultAsync(m => m.ExerciseId == id);
}
You should using async/await in the function:
public async Task CompleteAsync()
{
await context.SaveChangesAsync();
}
You are missing the link between the Repository and the UnitOfWork. In order to get the pattern working, and then persist your data, you should only access the repository's methods through the unit of work, i.e. the UoW should be just a wrapper for all the repositories related to one database, with the extra purpose of maintaining your data consistency.
In the controller your first retrieve baseExercise via repository.GetExercise(id), then you're trying to persist the data with unitOfWork.CompleteAsync(). The problem is that you didn't instruct the uniOfWork to care about the repository behaviour.
That said, it should be a quick fix.
First, set the first part of the link in the UnitOfWork:
public class UnitOfWork : IUnitOfWork
{
private readonly FitaholicContext context;
public UnitOfWork(FitaholicContext context)
{
this.context = context;
}
private Repository _repository;
public Repository repository =>
this._repository ?? (this._repository =
new Repository(this.context));
public Task CompleteAsync()
{
return context.SaveChangesAsync();
}
}
Then the second half, in the Repository:
public class Repository
{
public Repository(FitaholicContext context)
{ }
public async Task<Exercise> GetExercise(int id, bool includeRelated = false)
{ ... }
}
Last step, update PutExercise() in the controller to reflect the logic changes:
[HttpPut("{id}")]
public async Task<IActionResult> PutExercise([FromRoute] int id, [FromBody] Exercise exercise)
{
...
var baseExercise = await this.unitOfWork.repository.GetExercise(id);
...
await this.unitOfWork.CompleteAsync();
return NoContent();
}
If you need to dig a bit deeper into the Repository-UnitOfWork pattern, check the Microsoft Docs.

Manage Identities (EF / Membership) in a WCF service : 'The entity type CustomApplicationUser is not part of the model for the current context'

I would like to know if it's possible to manage users from a WCF service.
For example, my app - which is a client of my WCF service - call a function CreateUser(String login, string password, string email), and my service create this user using EF identity and / or ASP Membership.
I've installed nuget packages : OWIN/EF.Identity/ASP membership.
I've already tried to create classes in my WCF service :
ApplicationDBContext :
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("Scorpion_V1Users")
{
}
}
ApplicationUser :
public class CustomApplicationUser : IdentityUser
{
public CustomApplicationUser()
{
this.Id = Guid.NewGuid().ToString();
}
public CustomApplicationUser(string userName): this()
{
UserName = userName;
}
}
My custom UserStore :
public class CustomUserStore : IUserStore<CustomApplicationUser>, IUserPasswordStore<CustomApplicationUser>, IUserSecurityStampStore<CustomApplicationUser>
{
UserStore<IdentityUser> userStore = new UserStore<IdentityUser>(new CustomApplicationDbContext());
public CustomUserStore()
{
}
public Task CreateAsync(CustomApplicationUser user)
{
var context = userStore.Context as CustomApplicationDbContext;
context.Users.Add(user);
context.Configuration.ValidateOnSaveEnabled = false;
return context.SaveChangesAsync();
}
public void Create(CustomApplicationUser user)
{
var context = userStore.Context as CustomApplicationDbContext;
context.Users.Add(user);
context.Configuration.ValidateOnSaveEnabled = false;
context.SaveChanges();
}
public Task DeleteAsync(CustomApplicationUser user)
{
var context = userStore.Context as CustomApplicationDbContext;
context.Users.Remove(user);
context.Configuration.ValidateOnSaveEnabled = false;
return context.SaveChangesAsync();
}
public Task<CustomApplicationUser> FindByIdAsync(string userId)
{
var context = userStore.Context as CustomApplicationDbContext;
return context.Users.Where(u => u.Id.ToLower() == userId.ToLower()).FirstOrDefaultAsync();
}
public Task<CustomApplicationUser> FindByNameAsync(string userName)
{
var context = userStore.Context as CustomApplicationDbContext;
return context.Users.Where(u => u.UserName.ToLower() == userName.ToLower()).FirstOrDefaultAsync();
}
public Task UpdateAsync(CustomApplicationUser user)
{
var context = userStore.Context as CustomApplicationDbContext;
context.Users.Attach(user);
context.Entry(user).State = EntityState.Modified;
context.Configuration.ValidateOnSaveEnabled = false;
return context.SaveChangesAsync();
}
public void Dispose()
{
userStore.Dispose();
}
public Task<string> GetPasswordHashAsync(CustomApplicationUser user)
{
var identityUser = ToIdentityUser(user);
var task = userStore.GetPasswordHashAsync(identityUser);
SetApplicationUser(user, identityUser);
return task;
}
public Task<bool> HasPasswordAsync(CustomApplicationUser user)
{
var identityUser = ToIdentityUser(user);
var task = userStore.HasPasswordAsync(identityUser);
SetApplicationUser(user, identityUser);
return task;
}
public Task SetPasswordHashAsync(CustomApplicationUser user, string passwordHash)
{
var identityUser = ToIdentityUser(user);
var task = userStore.SetPasswordHashAsync(identityUser, passwordHash);
SetApplicationUser(user, identityUser);
return task;
}
public Task<string> GetSecurityStampAsync(CustomApplicationUser user)
{
var identityUser = ToIdentityUser(user);
var task = userStore.GetSecurityStampAsync(identityUser);
SetApplicationUser(user, identityUser);
return task;
}
public Task SetSecurityStampAsync(CustomApplicationUser user, string stamp)
{
var identityUser = ToIdentityUser(user);
var task = userStore.SetSecurityStampAsync(identityUser, stamp);
SetApplicationUser(user, identityUser);
return task;
}
private static void SetApplicationUser(CustomApplicationUser user, IdentityUser identityUser)
{
user.PasswordHash = identityUser.PasswordHash;
user.SecurityStamp = identityUser.SecurityStamp;
user.Id = identityUser.Id;
user.UserName = identityUser.UserName;
}
private IdentityUser ToIdentityUser(CustomApplicationUser user)
{
return new IdentityUser
{
Id = user.Id,
PasswordHash = user.PasswordHash,
SecurityStamp = user.SecurityStamp,
UserName = user.UserName
};
}
}
and my UserManager :
public class CustomUserManager
{
}
I've created all AspNet tables on my database and added an other EDMX on my WCF project, pointing to them. The newly created connection string is correct (ScorpionV1Users).
<add name="Scorpion_V1Users" connectionString="metadata=res://*/Database.Users.csdl|res://*/Database.Users.ssdl|res://*/Database.Users.msl;provider=System.Data.SqlClient;provider connection string="data source=TANTO;initial catalog=Scorpion_V1;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
When I try
private CustomUserStore _userStore = new CustomUserStore();
...
var user = new CustomApplicationUser { UserName = login, Email = mail };
_userStore.Create(user);
in UserStore,
public void Create(CustomApplicationUser user)
{
var context = userStore.Context as CustomApplicationDbContext;
context.Users.Add(user);
context.Configuration.ValidateOnSaveEnabled = false;
context.SaveChanges();
}
the line
context.Users.Add(user);
throw an error : 'The entity type CustomApplicationUser is not part of the model for the current context'.
I've seen other issues like that, here, or here, but it does not resolve mine aswell.
I think my model to create AspNetUsers is not complete.
What else do I need (classes, nuget package or smt else) to implement it ?
Thanks in advance.
Irrespective of WCF you can use
[PrincipalPermission(SecurityAction.Demand, Role = "usernane")]

Categories