I have a controller with 2 actions containing identical code. But i'm having trouble reusing it from a separate function, without resorting to returning null and checking return values. The actions are for logging in and for resending a two factor toke.
2 actions (Login and Resend)
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginModel model)
{
if (!ModelState.IsValid) return View();
var user = await userManager.FindByNameAsync(model.UserName);
if(!(user != null && !await userManager.IsLockedOutAsync(user)))
{
ModelState.AddModelError("", "Invalid UserName or Password");
return View();
}
if(!await userManager.CheckPasswordAsync(user, model.Password))
{
ModelState.AddModelError("", "Invalid UserName or Password");
return View();
}
// can this be improved?
var r = await SendTwoFactor(user);
if(r != null) return r; // error or redirect or no two factor?
return RedirectToAction("Index");
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Resend(ResendModel model)
{
var result = await HttpContext.AuthenticateAsync(IdentityConstants.TwoFactorUserIdScheme);
if (!result.Succeeded)
{
return RedirectToAction(nameof(Login)); // expired
}
var user = await userManager.FindByIdAsync(result.Principal.FindFirstValue("sub"));
if (user == null)
{
return RedirectToAction(nameof(Login)); // invalid
}
await HttpContext.SignOutAsync(IdentityConstants.TwoFactorUserIdScheme);
// can this be improved?
var r = await SendTwoFactor(user);
if(r != null) return r; // error or redirect or no two factor?
...
return RedirectToAction("...");
}
then the re-used code looks something like this:
SendTwoFactor
private async Task<IActionResult> SendTwoFactor(IdentityUser user)
{
if(!await userManager.GetTwoFactorEnabledAsync(user)) return null;
var d = ...;
if(!d)
{
ModelState.AddModelError("", "... d");
return View();
}
// determine preferred two factor method
// send two factor token
return RedirectToAction("...");
}
Does anyone have a good pattern to improve this?
This would be my aproach
First of all, we need to define interface with the 'common' elements inside FooModel and Foo2Model that would be used in common code like this:
public interface iFoo
{
int Id { get; set; }
string Product_name{ get; set; }
}
public class Foo : iFoo
{
public string OtherThings { get; set; }
public int Id { get; set; }
public string Product_name { get; set; }
}
public class Foo2 : iFoo
{
public string OtherThings2 { get; set; }
public int Id { get; set; }
public string Product_name_nv { get; set; }
}
Then we can make a private function like this:
private async Task<IActionResult> identicalFunction<T>(T model) where T : iFoo
{
c = ...;
if(c)
{
d = ...;
if(!d)
{
ModelState.AddModelError("", "... d");
return View();
}
... // a lot more code similar code that can exit early
return RedirectToAction("...");
}
// Note that I included the last return inside this function
return RedirectToAction("...");
}
And then change
// identical code
return RedirectToAction("...");
into:
return await identicalFunction(model);
Related
I don't Want Modified UserPassword when it's null or empty ,I search for long time but it's doesn't work
Would you give me some advice ?
public async Task<IActionResult> Edit(int ID, [Bind("UserId,UserPassword,Username,Fullname,Nickname,Sex,Status,Email,Tel,Mobile,Address,Province,City,PostCode,UserType")] User user)
{
if (ID != user.UserId)
{
return NotFound();
}
if (ModelState.IsValid)
{
if (string.IsNullOrEmpty(user.UserPassword))
{
context.Attach(user);
context.Entry(user).Property("UserPassword").IsModified = false;
}
else
{
user.UserPassword = main.Md5(user.UserPassword);
}
context.Update(user);
await context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(user);
}
Here is cshtml
enter image description here
Try the below code:
context.Update(user);
context.Entry<User>(user).Property(x => x.UserPassword).IsModified = false;
context.SaveChanges();
The Update method marks all the properties as modified, you should change the state of IsModified after it.
You can refer to https://github.com/dotnet/efcore/issues/6849
Instead of receiving user in your action , create a viewmodel and use that for update your user info.
Example:
public class UserViewModel {
public string UserId { get; set; }
public string UserPassword { get; set;}
... // other properties
}
public async Task <IActionResult> Edit(int ID, UserViewModel viewModel) {
if (ID != viewModel.UserId) {
return NotFound();
}
if (ModelState.IsValid) {
var user = context.Users.FindAsync(viewModel.UserId);
user.Password = !string.IsNullOrEmpty(viewModel.Password) ? main.Md5(viewModel.Password) : user.Password;
// update other properties here
await context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(user);
}
i have a model
public partial class TalentVendorShots
{
public int Id { get; set; }
public string Email { get; set; }
public string One { get; set; }
public string Two { get; set; }
public string Three { get; set; }
public string Four { get; set; }
public string Five { get; set; }
public string Six { get; set; }
public string Seven { get; set; }
public string Eight { get; set; }
public string Nine { get; set; }
public string Ten { get; set; }
}
and basic controllers
[Route("api/[controller]")]
[ApiController]
public class TalentVendorShotsController : ControllerBase
{
private readonly champagneDatabase _context;
public TalentVendorShotsController(champagneDatabase context)
{
_context = context;
}
// GET: api/TalentVendorShots
[HttpGet]
public async Task<ActionResult<IEnumerable<TalentVendorShots>>> GetTalentVendorShots()
{
return await _context.TalentVendorShots.ToListAsync();
}
// GET: api/TalentVendorShots/5
[HttpGet("{id}")]
public async Task<ActionResult<TalentVendorShots>> GetTalentVendorShots(int id)
{
var talentVendorShots = await _context.TalentVendorShots.FindAsync(id);
if (talentVendorShots == null)
{
return NotFound();
}
return talentVendorShots;
}
// PUT: api/TalentVendorShots/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTalentVendorShots(int id, TalentVendorShots talentVendorShots)
{
if (id != talentVendorShots.Id)
{
return BadRequest();
}
_context.Entry(talentVendorShots).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TalentVendorShotsExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/TalentVendorShots
[HttpPost]
public async Task<ActionResult<TalentVendorShots>> PostTalentVendorShots(TalentVendorShots talentVendorShots)
{
_context.TalentVendorShots.Add(talentVendorShots);
await _context.SaveChangesAsync();
return CreatedAtAction("GetTalentVendorShots", new { id = talentVendorShots.Id }, talentVendorShots);
}
// DELETE: api/TalentVendorShots/5
[HttpDelete("{id}")]
public async Task<ActionResult<TalentVendorShots>> DeleteTalentVendorShots(int id)
{
var talentVendorShots = await _context.TalentVendorShots.FindAsync(id);
if (talentVendorShots == null)
{
return NotFound();
}
_context.TalentVendorShots.Remove(talentVendorShots);
await _context.SaveChangesAsync();
return talentVendorShots;
}
private bool TalentVendorShotsExists(int id)
{
return _context.TalentVendorShots.Any(e => e.Id == id);
}
}
}
all of this works fine. i get information from the database fine. now i want to make a post to the table via uri. no body.for example
/api/TalentVendorShots/id=1,email=testemail should create a new record with id of 1 and email of testemail. how can i accomplish this?
The basic rule is, You should use POST if the action is not idempotent. Though you can pass the query parameters and no body to POST. But It would not make sense in this scenario. Basically query parameters are used to get/filter information.
Similar way many Web API testing tools like ARC, Swagger, and PostMan (chrome extension does not allow, but standalone application allows) does not allow to send body with the GET request. Though you can send the body in GET requests.
I am learning ASP .NET Core and I am trying to use repository pattern to clean up my controllers. The way I thought this out was:
create a repository interface containing the basic methods for a repository
implement the previously created the interface
create a model based repository interface
create a model-based repository extending the basic repository created at step 2 and implemented the model-based interface
create a wrapper class which contains the context and all the application repositories and inject it in every controller
Unfortunately the Complete of 'Edit' method causes a DbConcurrencyException which I have tried to solve using this. using the previous solution causes an InvalidOperationException as one of the properties is read-only.
For some code:
public class User : IdentityUser
{
[PersonalData]
[DisplayName("First Name")]
[Required(ErrorMessage = "The first name is required!")]
[StringLength(30, MinimumLength = 3, ErrorMessage = "The first name must be between 3 and 30 characters long!")]
public string firstName { get; set; }
[PersonalData]
[DisplayName("Last Name")]
[Required(ErrorMessage = "The last name is required!")]
[StringLength(30, MinimumLength = 3, ErrorMessage = "The last name must be between 3 and 30 characters long!")]
public string lastName { get; set; }
[PersonalData]
[DisplayName("CNP")]
[Required(ErrorMessage = "The PNC is required!")]
[StringLength(13, MinimumLength = 13, ErrorMessage = "The last name must 13 digits long!")]
[RegularExpression(#"^[0-9]{0,13}$", ErrorMessage = "Invalid PNC!")]
public string personalNumericalCode { get; set; }
[PersonalData]
[DisplayName("Gender")]
[StringRange(AllowableValues = new[] { "M", "F" }, ErrorMessage = "Gender must be either 'M' or 'F'.")]
public string gender { get; set; }
public Address address { get; set; }
}
public class Medic : User
{
[DisplayName("Departments")]
public ICollection<MedicDepartment> departments { get; set; }
[DisplayName("Adiagnostics")]
public ICollection<MedicDiagnostic> diagnostics { get; set; }
[PersonalData]
[DisplayName("Rank")]
[StringLength(30, MinimumLength = 3, ErrorMessage = "The rank name must be between 3 and 30 characters long!")]
public string rank { get; set; }
}
public class MedicController : Controller
{
private readonly IUnitOfWork unitOfWork;
public MedicController(IUnitOfWork unitOfWork)
{
this.unitOfWork = unitOfWork;
}
// GET: Medic
public async Task<IActionResult> Index()
{
return View(await unitOfWork.Medics.GetAll());
}
// GET: Medic/Details/5
public async Task<IActionResult> Details(string id)
{
if (id == null)
{
return NotFound();
}
Medic medic = await unitOfWork.Medics.FirstOrDefault(m => m.Id == id);
if (medic == null)
{
return NotFound();
}
return View(medic);
}
// GET: Medic/Create
public IActionResult Create()
{
return View();
}
// POST: Medic/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("rank,firstName,lastName,personalNumericalCode,Id,gender,Email")] Medic medic)
{
if (ModelState.IsValid)
{
unitOfWork.Medics.Add(medic);
await unitOfWork.Complete();
return RedirectToAction(nameof(Index));
}
return View(medic);
}
// GET: Medic/Edit/5
public async Task<IActionResult> Edit(string id)
{
if (id == null)
{
return NotFound();
}
Medic medic = await unitOfWork.Medics.Get(id);
if (medic == null)
{
return NotFound();
}
return View(medic);
}
// POST: Medic/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(string id, [Bind("rank,firstName,lastName,Id,personalNumericalCode,gender,Email")] Medic medic)
{
if (id != medic.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
var saved = false;
while (!saved)
{
try
{
unitOfWork.Medics.Update(medic);
await unitOfWork.Complete();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
if (!MedicExists(medic.Id))
{
return NotFound();
}
else
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is Medic)
{
var proposedValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
foreach (var property in proposedValues.Properties)
{
var proposedValue = proposedValues[property];
var databaseValue = databaseValues[property];
proposedValues[property] = proposedValue;
// TODO: decide which value should be written to database
// proposedValues[property] = <value to be saved>;
}
// Refresh original values to bypass next concurrency check
entry.OriginalValues.SetValues(databaseValues);
}
else
{
throw new NotSupportedException(
"Don't know how to handle concurrency conflicts for "
+ entry.Metadata.Name);
}
}
}
}
}
return RedirectToAction(nameof(Index));
}
return View(medic);
}
// GET: Medic/Delete/5
public async Task<IActionResult> Delete(string id)
{
if (id == null)
{
return NotFound();
}
Medic medic = await unitOfWork.Medics.FirstOrDefault(m => m.Id == id);
if (medic == null)
{
return NotFound();
}
return View(medic);
}
// POST: Medic/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(string id)
{
Medic medic = await unitOfWork.Medics.Get(id);
unitOfWork.Medics.Remove(medic);
await unitOfWork.Complete();
return RedirectToAction(nameof(Index));
}
private bool MedicExists(string id)
{
return unitOfWork.Medics.Any(e => e.Id == id);
}
}
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
protected readonly ApplicationDbContext context;
public Repository(ApplicationDbContext context)
{
this.context = context;
}
public void Add(TEntity entity)
{
context.Set<TEntity>().AddAsync(entity);
}
public void AddRange(IEnumerable<TEntity> entities)
{
context.Set<TEntity>().AddRangeAsync(entities);
}
public bool Any(Expression<Func<TEntity, bool>> predicate)
{
return context.Set<TEntity>().Any(predicate);
}
public async Task<IEnumerable<TEntity>> Find(Expression<Func<TEntity, bool>> predicate)
{
return await context.Set<TEntity>().Where(predicate).ToListAsync();
}
public async Task<TEntity> FirstOrDefault(Expression<Func<TEntity, bool>> predicate)
{
return await context.Set<TEntity>().FirstOrDefaultAsync(predicate);
}
public async Task<TEntity> Get(string id)
{
return await context.Set<TEntity>().FindAsync(id);
}
public async Task<IEnumerable<TEntity>> GetAll()
{
return await context.Set<TEntity>().ToListAsync();
}
public void Remove(TEntity entity)
{
context.Set<TEntity>().Remove(entity);
}
public void RemoveRange(IEnumerable<TEntity> entities)
{
context.Set<TEntity>().RemoveRange(entities);
}
public TEntity SingleOrDefault(Expression<Func<TEntity, bool>> predicate)
{
return context.Set<TEntity>().SingleOrDefault(predicate);
}
public void Update(TEntity entity)
{
context.Set<TEntity>().Update(entity);
}
}
public class MedicRepository : Repository<Medic>, IMedicRepository
{
public MedicRepository(ApplicationDbContext _context) : base(_context) { }
//TODO: add medic repository specific methods
}
public class UnitOfWork : IUnitOfWork
{
private readonly ApplicationDbContext _context;
public IMedicRepository Medics { get; private set; }
public IPatientRepository Patients { get; private set; }
public IReceptionistRepository Receptionists { get; private set; }
public IDiagnosticRepository Diagnostics { get; private set; }
public IMedicationRepository Medications { get; private set; }
public IMedicineRepository Medicine { get; private set; }
public ILabTestRepository LabTests { get; private set; }
public ILabResultRepository LabResults { get; private set; }
public UnitOfWork(ApplicationDbContext context)
{
_context = context;
Medics = new MedicRepository(_context);
Patients = new PatientRepository(_context);
Receptionists = new ReceptionistRepository(_context);
Diagnostics = new DiagnosticRepository(_context);
Medications = new MedicationRepository(_context);
Medicine = new MedicineRepository(_context);
LabTests = new LabTestRepository(_context);
LabResults = new LabResultRepository(_context);
}
public async Task<int> Complete()
{
return await _context.SaveChangesAsync();
}
public void Dispose()
{
_context.Dispose();
}
}
Thanks!
There are a lot of things to notice. But I will only point to the biggest one. DbContext or ApplicationDbContext classes are not meant to be long lived and cross spanned. I am guessing ApplicationDbContext is a singleton. Which is a long lived object and is shared among different classes and also may be threads. This is exactly the design pattern you should avoid. In terms of Microsoft -
Entity Framework Core does not support multiple parallel operations being run on the same DbContext instance. Concurrent access can result in undefined behavior, application crashes and data corruption. Because of this it's important to always use separate DbContext instances for operations that execute in parallel.
This page here describes the problem - https://learn.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext#avoiding-dbcontext-threading-issues
In short, use scoped dbcontext.
If you are learning, I would say implement it yourself and change the implementations of your classes. Create and dispose contexts when you need them. Do not keep long lived contexts.
If you just need a repository, you can use this package, I use it for myself - https://github.com/Activehigh/Atl.GenericRepository
I managed to fix this out. The concurrency exception was thrown because I was creating users (which were inheriting IDentityUser) without using a UserManager<User>. After inspecting the database fields, I have discovered that the Identityuser related fields (like email, username, etc..) were empty. This was due to me only adding information for the class which inherited IDentityUser.
I used this code for admin panel to changed password of customer by manager,I did not get any Error. but password not changed,
i got my coed from this
[HttpPost]
[ValidateAntiForgeryToken]
public virtual ActionResult ChangeUserPassword(SetPasswordViewModel model,string userId)
{
if (ModelState.IsValid)
{
//var result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword);
ApplicationUser appUser = db.Users.Find(userId);
var result = UserManager.ChangePasswordAsync(appUser, model.NewPassword);
if (result.IsCompleted)
{
var user = UserManager.FindById(User.Identity.GetUserId());
//var user = db.Users.Find(userId);
if (user != null)
{
//await SignInManager<,>.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
return RedirectToAction("Index", new { Message = ManageController.ManageMessageId.SetPasswordSuccess });
}
// AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
}
and in controller for change use this
public async Task<IdentityResult> ChangePasswordAsync(ApplicationUser Appuser, string newPassword)
{
var store = this.Store as Microsoft.AspNet.Identity.IUserPasswordStore<ApplicationUser,string>;
if (store == null)
{
var errors = new string[]
{
"Current UserStore doesn't implement IUserPasswordStore"
};
return IdentityResult.Failed(errors);
// return Task.FromResult(new IdentityResult(errors) { Succeeded = false });
}
var newPasswordHash = this.PasswordHasher.HashPassword(newPassword);
await store.SetPasswordHashAsync(Appuser, newPasswordHash);
await store.UpdateAsync(Appuser);
//return await Task.FromResult<IdentityResult>(IdentityResult.Success);
return IdentityResult.Success;
}
}
whats my mistake?
after update answer i use this method instead
[HttpPost]
public async Task<IdentityResult> ChangePasswordAsync(ApplicationUser appuserId, string newPassword)
{
ApplicationDbContext db = new ApplicationDbContext();
//var userr =await db.Users.FirstOrDefaultAsync(x => x.Id == appuserId.Id);
var newPasswordHash = this.PasswordHasher.HashPassword(newPassword);
db.Entry(Users).State = EntityState.Modified;
if (appuserId != null) appuserId.PasswordHash = newPasswordHash;
db.SaveChanges();
return IdentityResult.Success;
}
but had the same problem again
in IdentityModels.cs i had
public DbSet<CommonAction> CommonActions { get; set; }
public DbSet<PublicContent> PublicContents { get; set; }
public DbSet<MainSubject> MainSubjects { get; set; }
public DbSet<EducationLevel> EducationLevels { get; set; }
public DbSet<TimePeriod> TimePeriods { get; set; }
public DbSet<HelpType> HelpTypes { get; set; }
public DbSet<FinancialSupport> FinancialSupports { get; set; }
public DbSet<LinksStatistic> LinksStatistics { get; set; }
public DbSet<SlideOfCommonAction> SlideOfCommonActions { get; set; }
in normal model IdentityModel User is not register as DbSet
()
You're not awaiting ChangePasswordAsync, you're just checking if it's completed or not. Which may result in the View being returned before the password has been changed.
Then you should try to use async/await all the way, if possible. That means that your action should be async as well.
[HttpPost]
[ValidateAntiForgeryToken]
public virtual async Task<ActionResult> ChangeUserPassword(SetPasswordViewModel model,string userId)
{
if (ModelState.IsValid)
{
//var result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword);
ApplicationUser appUser = db.Users.Find(userId);
// await the result.
var result = await UserManager.ChangePasswordAsync(appUser, model.NewPassword);
if (result.Succeeded) // Or whatever you're expecting.
{
var user = UserManager.FindById(User.Identity.GetUserId());
//var user = db.Users.Find(userId);
if (user != null)
{
//await SignInManager<,>.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
return RedirectToAction("Index", new { Message = ManageController.ManageMessageId.SetPasswordSuccess });
}
// AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
}
Update after comments:
The entity type DbSet`1 is not part of the model for the current context
db.Entry(Users).State = EntityState.Modified;
This is when EF cannot connect your model to an entity in the context. Without seeing the code, it's hard to say exactly why. But it could be that your're missing a DbSet in your context.
Have you configured your UserManager correctly? Does the other UserManager actions work?
Try debugging and see where it crashes.
I have methods that are returning objects, the problem is I don't have good idea how to verify errors and return it correctly in MVC View
public async Task<object> GetUser()
{
//....
if (responseMessage.IsSuccessStatusCode)
{
return await responseMessage.Content.ReadAsAsync<User>();
}
if (responseMessage.StatusCode == HttpStatusCode.BadRequest)
{
return await responseMessage.Content.ReadAsAsync<Error>();
}
}
return null;
}
Now in my controller I'm getting data and trying to return correct type
var httpResult = await GetUser();
if (httpResult.GetType() == typeof (Error))
{
ModelState.AddModelError("", (httpResult as Error).ErrorDescription);
return View(model);
}
if (httpResult.GetType() == typeof (User))
{
var user = httpResult as User;
return View(User);
}
I don't like my ifs and logic, any better solution?
You can try something like this. I used this pattern successfully in the past and present.
[DataContract]
public class OperationResult<T>
{
[DataMember]
public List<Error> Errors { get; set; }
[DataMember]
public T ResultObject { get; set; }
[DataMember]
public bool Success { get; private set; }
public OperationResult(List<Error> errors)
{
Errors = errors;
}
public OperationResult(T resultObject)
{
ResultObject = resultObject;
Success = true;
}
public OperationResult()
{
}
}
You return this type from your methods and you check the Success flag on return, if false you read the Errors property and you can populate the ModelState or return a specialized view model etc.