Many to many not saving when updating/editing entity - asp.net

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.

Related

Edit action is not doing anything on automapper

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();

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 keep the values filled in a form and recover after navigating to another controller

I am facing this situation. I have a form that I filled with three values. Then by clicking on a button I'm in another controller and I create an XmlDocument object I recovered via TempData. Once completed action coming back to my form naturally when all data has disappeared. My question is, how do I fill out the form, keep these values, and once the XML created to fill my database with the form data and XML created.
Layout controller
public class LayoutController : Controller
{
[HttpGet]
public ActionResult Create()
{
var Layout = new LayoutModel();
return View(Layout);
}
[HttpPost]
public ActionResult Create(LayoutModel Layout)
{
if (Layout == null)
{
return Content("le LayoutModel est nul");
}
else
{
TempData["DocName"] = Layout.Nom;
if (TempData["xmlAssociated"] != null)
{
Layout.xmlAssociated = (string)TempData["xmlAssociated"];
ManageXML.Models.COracleConn.GetInstance().InsertLayout(Layout);
}
else
{
return Content("On a pas recuperé l'XML");
}
return RedirectToAction("ListOfTopsAndMarges", "Entete_Marge");
}
}
}
XMLRecord controller
public class XMLRecordController : Controller
{
[HttpGet]
public ActionResult HandleForm()
{
var file = new XMLRecord()
{
Records = new List<XMLRecord>(){ new XMLRecord(){ Type="", Contenu="" }}
};
return View(file);
}
[HttpPost]
public ActionResult HandleForm(XMLRecord file)
{
if (file == null)
{
return HttpNotFound();
}
else
{
file.DocName = (string)TempData["DocName"];
string recup = file.GenerateXML(file);
TempData["xmlAssociated"] = recup;
return RedirectToAction("Create", "Layout");
}
}
}
public class LayoutModel
{
public string Nom { get; set; }
public string Type { get; set; }
public string Contenu { get; set; }
public string xmlAssociated {get; set;}
}
I want to retain my Type, my Contenu and my Nom properties even if I am going to another controller
Thnak for your help #Heymega

How to add the UserId to posted data by Logged User in ASP.NET MVC 4

So what I'm doing might seem simple, but I don't know exactly how to do it.
I have already registered and logged in with an account (I'm using the default membership system used in ASP.NET MVC 4) and so I want to do add my UserId to some data I'm inserting to the database.
This is the model of the data I'm inserting:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Reroute.Models
{
public class Request
{
public int RequestId { get; set; }
// I want to add UserId based on my current session
public int UserId { get; set; }
public string OrderNumber { get; set; }
public string TrackingNumber { get; set; }
public string CurrentAddress { get; set; }
public string NewAddress { get; set; }
public string Comment { get; set; }
}
}
And the ActionResult (here's where I supposed I have to make the changes):
[HttpPost]
public ActionResult Create(Request collection)
{
try
{
_db.Requests.Add(collection);
_db.SaveChanges();
//return RedirectToAction("Index");
return Content("Done! Added to DB");
}
catch
{
return View();
}
}
Thanks
use this it gets u the userid ...
Membership.GetUser().ProviderUserKey
You can save the UserId of the authenticated user in Session after logging in:
Session["UserId"] = userId;
or since you are using FormsAuthentication you can either use the UserData property as shown here or do a nice-that-will-do-trick:
public SignInUser(string name, string id) {
// store the userid
FormsAuthentication.SetAuthCookie(name + '|' + id, false);
}
then retrieve the Name and UserId like this:
public int CurrentUserId
{
get
{
var context = HttpContext.Current;
if (context == null) return 0;
return context.Request.IsAuthenticated
? Convert.ToInt32(context.User.Identity.Name.Split('|')[1])
: 0;
}
}
public string CurrentUserName
{
get
{
var context = HttpContext.Current;
if (context == null) return string.Empty;
return context.Request.IsAuthenticated
? context.User.Identity.Name.Split('|')[0]
: string.Empty;
}
}
You can have those method and properties in a class so you have them in one place, I actually do it that way. Now, you can call it in your controller like so:
[HttpPost]
public ActionResult Create(Request collection)
{
try
{
collection.UserId = _authProvider.CurrentUserId;
// if you want to use session, I prefer the FormsAuthentication approach
// you need to do additional check that the Session has not expired (not null)
collection.UserId = Session["UserId"];
_db.Requests.Add(collection);
_db.SaveChanges();
//return RedirectToAction("Index");
return Content("Done! Added to DB");
}
catch
{
return View();
}
}
_authProvider is an instance of the class that has the code I gave above.
This should work.
var loggedInUserName=Thread.CurrentPrincipal.Identity.Name;
var user=Membership.GetUser(loggedInUserName);
var key = user.ProviderUserKey;
T
Assuming your Create also has a GET which is loaded up and used as the model for Create.cshtml, you would just need to set it explicitly in that ActionResult
public ActionResult Create()
{
Result model = new Result();
model.UserId = myUserId;
}
Then in your Create.cshtml you could have a hidden field for it:
#Html.HiddenFor(m => m.UserId)
I would still check in the POST to make sure the user doing the saving is allowed to be saving and hasn't spoofed your hidden field value to somebody completely different.

How do I pass data between controllers and overloaded actions?

I have one controller that takes a username and pass and checks against a database. IF the user is authenticated, I want to call an overloaded action on another controller.
My end goal is to authenticate a user against an old table from a MySQL db (I have this part working). Once the user is authenticated, I would like to be able to "automagically" forward the person to the built in MVC registration page but I would like to populate some fields in the view using data obtained from the first controller (the old databse info).
When I try something like what I have below I get an error about the Register() methods being ambiguous. I've also tried using the [ActionName("Register2")] attribute but then the error returned says it cant find a method named Register2.
public class MigrateAccountController : Controller
{
OldUserRepository oldDb = new OldUserRepository();
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(User u)
{
if (oldDb.isValid(u.username, u.password))
return RedirectToAction("Register", "Account", u);
return View(u);
}
}
public class AccountController : Controller
{
public IFormsAuthenticationService FormsService { get; set; }
public IMembershipService MembershipService { get; set; }
protected override void Initialize(RequestContext requestContext)
{
if (FormsService == null) { FormsService = new FormsAuthenticationService(); }
if (MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
}
public ActionResult Register(User u)
{
return View(u);
}
public ActionResult Register()
{
ViewBag.PasswordLength = MembershipService.MinPasswordLength;
return View();
}
}
First thing you cannot have the same action name on the same controller that is accessible on the same verb. You need to either change the action name or use a different HTTP verb:
public class AccountController : Controller
{
public IFormsAuthenticationService FormsService { get; set; }
public IMembershipService MembershipService { get; set; }
protected override void Initialize(RequestContext requestContext)
{
if (FormsService == null) { FormsService = new FormsAuthenticationService(); }
if (MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
}
[HttpPost]
public ActionResult Register(User u)
{
return View(u);
}
public ActionResult Register()
{
ViewBag.PasswordLength = MembershipService.MinPasswordLength;
return View();
}
}
and in order to pass data between actions, well, if you are using GET, you could pass them as query string parameters when redirecting.
Or IMHO a better way would be not to redirect in this case but simply return the corresponding view by passing it the proper view model:
[HttpPost]
public ActionResult Index(User u)
{
if (oldDb.isValid(u.username, u.password))
{
return View("~/Account/Register.aspx", u);
}
return View(u);
}
You can use the TempData values in this case.

Resources