Edit action is not doing anything on automapper - .net-core

I´m using Visual Studio 2019, .net core 3.1 and automapper. My Edit action dont edit the record. I´ve seen tutorials but all are just of one action and I need to do a crud. Taking as example a usual edit action I´ve made this:
public class CustomerCountriesController : Controller
{
private readonly ApplicationDbContext _context;
private readonly IMapper _mapper;
public CustomerCountriesController(ApplicationDbContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
// GET: CustomerCountries
public async Task<IActionResult> Index()
{
//CustomerCountries customerCountry = new CustomerCountries();
var customerCountry = await _context.CustomerCountries.ToListAsync();
List<CustomerCountriesDto> countries = _mapper.Map<List<CustomerCountries>,
List<CustomerCountriesDto>>(await _context.CustomerCountries.ToListAsync());
return View(countries);
}
public async Task<IActionResult> Edit(string id)
{
if (id == null)
{
return NotFound();
}
var customerCountries = await _context.CustomerCountries.FindAsync(id);
var model = _mapper.Map<CustomerCountries, CustomerCountriesDto>(customerCountries);
if (customerCountries == null)
{
return NotFound();
}
return View(model);
//return View(customerCountries);
}
// POST: CustomerCountries/Edit/5
// To protect from overposting attacks, 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("CustomerCountry")] CustomerCountries customerCountries)
public async Task<IActionResult> Edit(string customerCountry, CustomerCountriesDto customerCountriesDto)
{
if (customerCountry != customerCountriesDto.CustomerCountry)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
var CustomerCountries = _mapper.Map<CustomerCountriesDto, CustomerCountries>(customerCountriesDto);
_context.Update(CustomerCountries);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CustomerCountriesExists(customerCountriesDto.CustomerCountry))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(customerCountriesDto);
}
public class AutoMapping : Profile
{
public AutoMapping()
{
CreateMap<CustomerCountries, CustomerCountriesDto>();
CreateMap<CustomerCountriesDto, CustomerCountries>();
}
}
public class CustomerCountries
{
[StringLength(50, ErrorMessage = "Longitud máxima para el país: 50")]
public string CustomerCountry { get; set; }
public ICollection<CustomerRegions> CustomerRegions { get; set; }
}
public class CustomerCountriesDto
{
public string CustomerCountry { get; set; }
}
On startup
services.AddAutoMapper(typeof(Startup));
The id of the table is CustomerCounty
Can you tell me the correct way?

I´ve found the solution thanks to an experienced developer that lead me through the issue and corrected my code from time to time (That is acctually really helping guys). Turns out that I was using a field as a PK: CustomerCountry... I wasn´t using and Id, when I changed the model, the update happened
var CustomerCountries = _mapper.Map<CustomerCountriesDto, CustomerCountries>(customerCountriesDto);
var country = _context.CustomerCountries.FirstOrDefault(c => c.Id == CustomerCountries.Id);
country.CustomerCountry = customerCountriesDto.CustomerCountry;
_context.Update(country);
await _context.SaveChangesAsync();

Related

How do I prevent a duplicate entry for a Create/Edit Functionality in ASP.NET Core w/ EF?

I am trying to prevent a user from creating a form with a FirstName, LastName, and DOB that match an entry in the database and editing a form to match an existing entry. If you could also lead me to how I can show an error when this happens, that would be awesome.
My Model:
public class MRegForm
{
public int MRegFormId { get; set; }
[Display(Name = "First Name")]
public string FirstName { get; set; } = string.Empty;
[Display(Name = "Last Name")]
public string LastName { get; set; } = string.Empty;
public DateTime DOB { get; set; }
[I tried Index attribute. It did not work for me. I was able to create new duplicate forms with no issues.
[Index(nameof(FirstName), nameof(LastName), nameof(DOB), IsUnique = true)]
public class MRegForm
{
I also tried this. Same thing.
protected override void OnModelCreating(ModelBuilder modelbuilder)
{
base.OnModelCreating(modelbuilder);
modelbuilder.Entity<MRegForm>()
.HasIndex(x => new { x.FirstName, x.LastName, x.DOB})
.IsUnique();
}
public DbSet<MRegForm> MRegForm { get; set; } = default!;
I think that there is maybe a way to prevent this in the OnPostAsync()
This is my create OnPostAsync():
public async Task<IActionResult> OnPostAsync()
{
MRegForm.CreatorId = UserManager.GetUserId(User);
var isAuthorized = await AuthorizationService.AuthorizeAsync(User, MRegForm, RegFormOperations.Create);
if (isAuthorized.Succeeded == false)
return Forbid();
Context.MRegForm.Add(MRegForm);
await Context.SaveChangesAsync();
return RedirectToPage("./Index");
}
This is my Edit OnPostAsync():
public async Task<IActionResult> OnPostAsync(int id)
{
var mRegForm = await Context.MRegForm.AsNoTracking().SingleOrDefaultAsync(m => m.MRegFormId == id);
if (mRegForm == null)
return NotFound();
MRegForm.CreatorId = mRegForm.CreatorId;
var isAuthorized = await AuthorizationService.AuthorizeAsync(User, MRegForm, RegFormOperations.Update);
if (isAuthorized.Succeeded == false)
return Forbid();
MRegForm.Status = mRegForm.Status; // the Status is the current Status - Do Not Reset
Context.Attach(MRegForm).State = EntityState.Modified;
try
{
await Context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MRegFormExists(MRegForm.MRegFormId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool MRegFormExists(int id)
{
return (Context.MRegForm?.Any(e => e.MRegFormId == id)).GetValueOrDefault();
}
}
You can try to download the entity from the database if exists and make changes to it or creating new one if not exist.
Your clients can always make new MRegForm in the form, but you add or update in the back and. Bether option will be to pass the existing MRegForm to the form and the client see and change all props he need.
public async Task AddOrUpdate(MRegForm input)
{
var mRegForm = await Context.MRegForm
.FirstOrDefaltAsync(x => x.FirstName == input.FirstName && x.LastName == input.LastName && x.DOB == input.YourDate);
if(mRegForm != null)
{
//Make changes on mRegForm
mRegForm.SomeProp = input.SomeProp,
...
}
else
{
var newMRegForm = new MRegForm
{
//Set all props you need
}
await this.Context.AddAsync(newMRegForm );
}
await this.Context.SaveCangesAsync();
}

ASP.NET Unable to resolve service for type, while attempting to activate controller

While I understand there were other questions on this very topic I was having a difficult time understanding the answers and was hoping someone could walk me through how DbContext's work as I feel I might have created a second context when I shouldn't have.
So, as I'm teaching myself more about .NET Core I'm working on turning an old school project into a .NET project which is a simple Dentist office web app where users can sign up for appointments, view their appointments, etc. I was following along with this tutorial to add additional user attributes instead of just username & e-mail as I was trying to grab the current user when creating an appointment.
Before I added this custom attributes using the default IdentityUI I had my project working where a user could register and login, create a basic appointment with their 'username' pick a date and time and once created would display their appointments in a basic table format. My next step was to add the custom user attributes so it would display based on their real-name and not their username which is defaulted to their email.
Following the tutorial I'm not sure if I misunderstood but I created a new Context and IdentityUser which all worked but it has broken my 'Appointments' page giving me the:
InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UserManager`1[Microsoft.AspNetCore.Identity.IdentityUser]' while attempting to activate 'WelchDentistry.Controllers.AppointmentsController'.** error.
Here is my ConfigureServices method as I believe it's an issue in regards to registering the 2 different Contexts.
public void ConfigureServices(IServiceCollection services)
{
/*
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
*/
/*
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
*/
services.AddControllersWithViews();
services.AddRazorPages();
services.AddMvc();
}
Here is the original context
namespace WelchDentistry.Data
{
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<WelchDentistry.Models.Appointment> Appointment { get; set; }
}
}
Here is my controller for my appointments
namespace WelchDentistry.Controllers
{
public class AppointmentsController : Controller
{
private readonly ApplicationDbContext _context;
private readonly UserManager<IdentityUser> _userManager;
public AppointmentsController(ApplicationDbContext context, UserManager<IdentityUser> userManager)
{
_context = context;
_userManager = userManager;
}
// GET: Appointments
public async Task<IActionResult> Index()
{
var user = await _userManager.GetUserAsync(HttpContext.User);
return View(await _context.Appointment.ToListAsync());
}
// GET: Appointments/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var appointment = await _context.Appointment
.FirstOrDefaultAsync(m => m.ID == id);
if (appointment == null)
{
return NotFound();
}
return View(appointment);
}
// GET: Appointments/Create
public IActionResult Create()
{
return View();
}
// POST: Appointments/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("ID, CustomerName, AppointmentTime,CustomerDoctor")] Appointment appointment)
{
if (ModelState.IsValid)
{
_context.Add(appointment);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(appointment);
}
// GET: Appointments/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var appointment = await _context.Appointment.FindAsync(id);
if (appointment == null)
{
return NotFound();
}
return View(appointment);
}
// POST: Appointments/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(int id, [Bind("ID,CustomerName,AppointmentTime,CustomerDoctor")] Appointment appointment)
{
if (id != appointment.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(appointment);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!AppointmentExists(appointment.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(appointment);
}
// GET: Appointments/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var appointment = await _context.Appointment
.FirstOrDefaultAsync(m => m.ID == id);
if (appointment == null)
{
return NotFound();
}
return View(appointment);
}
// POST: Appointments/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var appointment = await _context.Appointment.FindAsync(id);
_context.Appointment.Remove(appointment);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
private bool AppointmentExists(int id)
{
return _context.Appointment.Any(e => e.ID == id);
}
}
}
If more code is needed please ask or you can view on my Github
I appreciate all the help and bare with my as I'm still lost on most of this but slowly learning.
First of all remove your IdentityHostingStartup file in your Identity Area.
Then change your databasecontext to this ( You should introduce your User class ) :
public class ApplicationDbContext : IdentityDbContext<WelchDentistryUser, IdentityRole, string>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Models.Appointment> Appointment { get; set; }
}
And add this codes in your startup file .
services.AddIdentity<WelchDentistryUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
And finally you should use your custom User class in controller.
public class AppointmentsController : Controller
{
private readonly ApplicationDbContext _context;
private readonly UserManager<WelchDentistryUser> _userManager;
public AppointmentsController(ApplicationDbContext context, UserManager<WelchDentistryUser> userManager)
{
_context = context;
_userManager = userManager;
}
}

How to implement async function?

I would like that my function fulfilled asynchronously. I still learn to write the asynchronous code and would like to ask your help. How does such funtion have to be written?
class CustomerController : Controller
{
private ICustomerRepository _customerRepository;
public CustomerController(ICustomerRepository customerRepository)
{
_customerRepository = customerRepository;
}
[HttpGet]
public IActionResult Index()
{
IEnumerable<CustomerViewModel> customers =
_customerRepository.GetAllCustomers().Select(s => new CustomerViewModel
{
CustomerId = s.CustomerId,
Name = s.Name,
Adress = s.Adress
});
return View("Index", customers);
}
}
Interface:
public interface ICustomerRepository
{
IEnumerable<Customer> GetAllCustomers();
}
public class CustomerRepository : ICustomerRepository
{
private StoreDBContext _db;
public CustomerRepository(StoreDBContext db)
{
_db = db;
}
public IEnumerable<Customer> GetAllCustomers()
{
return _db.Customers;
}
}
[HttpGet]
public async Task<IActionResult> Index()
{
IEnumerable<CustomerViewModel> customers =
_customerRepository.GetAllCustomers().Select(s => new CustomerViewModel
{
CustomerId = s.CustomerId,
Name = s.Name,
Adress = s.Adress
});
return View("Index", customers);
}

Unit tests fails after upgrading to .net core 2

Can someone maybe explain to me what this means and why am i getting it.
System.InvalidOperationException : When called from 'VisitLambda',
rewriting a node of type 'System.Linq.Expressions.ParameterExpression'
must return a non-null value of the same type. Alternatively, override
'VisitLambda' and change it to not visit children of this type.
I am getting it from my unit tests I am running the latest .net core 2 with EF core. all my tests were fine till i upgraded then i started getting the error.
The funny thing is, is that when i run the project the line were it fails in the the tests is ok.
This is my Test
[Fact]
public async Task GetUserProfileAsync_Where_Employee_Exist_Test()
{
// Given
var user = TestPrincipal.CreatePrincipalForEmployeeUser();
using (var factory = new TestContextFactory())
using (var context = factory.CreateInMemoryDatabase<ApplicationContext>())
{
this.SetDependencies(context);
var data = EmployeeValueHelper.GetEmployeeValues();
context.AddRange(data);
context.SaveChanges();
var sut = new ProfileService(new DbContextRepository<Data.Models.Employees.Employee>(context), this.userService, this.moqEmploymentStatusService.Object);
// When
// -> this method goes to a service and calls the below FindByIdAsync
var actual = await sut.GetProfileForUserAsync(user);
// Then
Assert.Equal(10, actual.EmployeeId);
}
}
public async Task<Employee> FindByIdAsync(long id)
{
var profile = await this.repository.Set
.Include(_ => _.Address) --> IT FAILS ON THIS LINE, IF I REMOVE THE INCLUDE THEN IT WORKS
.Include(_ => _.EmployeeImage)
.SingleOrDefaultAsync(_ => _.EmployeeId == id);
if (profile == null)
{
return null;
}
return profile;
}
UPDATE
Service Layer
public class ProfileService : GenericService<Employee>, IProfileService
{
private readonly DbContextRepository<Employee> repository;
private readonly IUserService userService;
public ProfileService(DbContextRepository<Employee> repository, IUserService userService)
: base(repository)
{
this.repository = repository;
this.userService = userService;
}
public Task<Employee> GetProfileForUserAsync(ClaimsPrincipal user)
{
var id = this.userService.GetEmployeeId(user);
return id.HasValue ? this.FindByIdAsync(id.Value) : null;
}
public async Task<Employee> FindByIdAsync(long id)
{
var profile = await this.repository.Set
.Include(_ => _.Address)
.Include(_ => _.EmployeeImage)
.SingleOrDefaultAsync(_ => _.EmployeeId == id);
if (profile == null)
{
return null;
}
return profile;
}
}
Employee Model
public class Employee : IValidatableObject
{
[Key]
[Column("pkEmpID")]
public long EmployeeId { get; set; }
[Column("fkCompanyID")]
public long CompanyId { get; set; }
public virtual Company Company { get; set; }
[Display(Name = "lblEmpNumber")]
public string EmpNumber { get; set; }
public virtual IList<Address> Address { get; set; } = new List<Address>();
// WITH SOME EXTRA STUFF NOT NEEDED FOR THIS
}
Repository
public class DbContextRepository<TEntity> : IGenericRepository<TEntity>, IDisposable
where TEntity : class
{
public DbContextRepository(ApplicationContext context)
{
this.Context = context;
this.Set = context.Set<TEntity>();
this.SetWithNoTracking = this.Set.AsNoTracking();
}
public ApplicationContext Context { get; }
public DbSet<TEntity> Set { get; }
public IQueryable<TEntity> SetWithNoTracking { get; }
// WITH SOME EXTRA STUFF NOT NEEDED FOR THIS
}
Hope this will shed more light

Many to many not saving when updating/editing entity

I'm having a problem with my [HttpPost] edit method in my controller, it is not saving the changes made to a userrole, it is strange because the create method is working it is using the same helper methods, this is my code:
viewmodel:
public class UserViewModel
{
public User User { get; set; }
public virtual ICollection<AssignedUserRole> UserRoles { get; set; }
public virtual List<Company> Companies { get; set; }
}
Controller:
[HttpPost]
public ActionResult Edit(UserViewModel userViewModel)
{
if (ModelState.IsValid)
{
var user = userViewModel.User;
user.UserRoles.Clear();
AddOrUpdateRoles(user, userViewModel.UserRoles);
context.Entry(user).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
return View(userViewModel);
}
Helper Method
private void AddOrUpdateRoles(User user, ICollection<AssignedUserRole> assignedUserRoles)
{
foreach (var assignedRole in assignedUserRoles)
{
if (assignedRole.Assigned)
{
var userRole = new UserRole { Id = assignedRole.UserRoleId };
context.UserRoles.Attach(userRole);
user.UserRoles.Add(userRole);
}
}
}
everything in the User object is being updated except for the userrole, I can't find the problem as I am debugging and doing a step through and I can see that the user has the correct/updated roles assigned.
I was able to solve this problem by making the following changes:
private void AddOrUpdateRoles(User user, ICollection<AssignedUserRole> assignedUserRoles)
{
foreach (var assignedRole in assignedUserRoles)
{
if (assignedRole.Assigned)
{
var userRole = context.UserRoles.Find(assignedRole.UserRoleId);
user.UserRoles.Add(userRole);
}
}
}
[HttpPost]
public ActionResult Edit(UserViewModel userViewModel)
{
if (ModelState.IsValid)
{
var user = userViewModel.User;
context.Entry(user).State = EntityState.Modified;
context.Entry(user).Collection(u => u.UserRoles).Load();
user.UserRoles.Clear();
AddOrUpdateRoles(user, userViewModel.UserRoles);
context.SaveChanges();
return RedirectToAction("Index");
}
return View(userViewModel);
}
I had to "Load" the user's userroles otherwise the clearing was doing nothing.

Resources