Posting updated Address to the database MVC 5 - c#

I'm still learning how to use MVC 5, and so far I manage to get custom fields for my user profiles seen here in my manage view page:
http://puu.sh/ddmVY/2533472010.png
The user registers and fills out these fields and they are stored in the same place as the username and password data are stored.
I added these fields right under the ApplicationUser in the IdentityModels.cs as seen here
public class ApplicationUser : IdentityUser
{
// Additional user fields needed for registration
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public int ZipCode { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
}
I want to be able to edit the address so that if someone moves they can update it on their own. I have the method to get the data and put them inside the textboxes in the ChangeAddress view, but when it comes time to update the data I’m not sure how to go about that. I'm not sure how to write that post method Here is what the page looks like below and I was hoping to add this in the method in the ManageController.cs. I've seen other tutorials that have it done on a separate table but not the one from the ApplicationUser.
http://puu.sh/ddn98/96cab8a252.png
My method to display the data in the ChangeAddress view page
// GET: /Manage/ChangeAddress
[HttpGet]
public async Task<ActionResult> ChangeAddress()
{
ApplicationUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
var model = new ChangeAddressViewModel
{
Address = user.Address,
City = user.City,
State = user.State,
ZipCode = user.ZipCode
};
return View(model);
}

You need to pass something in the model that will allow you to identify which user the data belongs to when it comes back. It would be easiest to add a UserId property to the ChangeAddressViewModel.
You pass this back with the other data in the ChangeAddressViewModel record when you make your request to the get. This information then populates your view typically in a form that can be submitted back via POST. You typically put the UserId into a HiddenField so that its present in the form but not shown.
You can then create an update method on your controller that has a [HttpPost] attribute which takes a ChangeAddressViewModel as its input parameter and then you wire up your form so that the submit posts to that update action.
Inside your new update method. locate the desired user using the userId that you have been passed back. Set the various updated values of that user from the values obtained from the ChangeAddressViewModel that was passed in by the POST.
On your DB context, for your user record call SaveCnanges() to update the record in via EF.
There is a step by step tutorial for MVC on asp.net

In the end I manage to help myself and came up with solution to my problem. Posting it here for anyone who has a similar problem to mine.
//
// POST: /Manage/ChangeAddress
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ChangeAddress(ChangeAddressViewModel model)
{
if (ModelState.IsValid)
{
// Get the current application user
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user != null)
{
//grab the details
user.Address = model.Address;
user.City = model.City;
user.State = model.State;
user.ZipCode = model.ZipCode;
// Update the user
var result = await UserManager.UpdateAsync(user);
if (result.Succeeded)
{
return RedirectToAction("Index", new { Message = ManageMessageId.ChangeAddressSuccess });
}
AddErrors(result);
return View(model);
}
}
return View(model);
}

Related

Problem in using Remote Validation in asp.net core 2.2

I'm trying to check uniqueness of Identity PhoneNumber with Remote attribute on DTO or Model in Api controller but seems not working and the sqlException get triggered
My AuthController is inside of Api Folder but I try with a separate ValidatorController inside of default Controllers folder
I used the convention of Remote attribute that has advised in Microsoft documentations
// My Dto
namespace Imah.Api.API.Dto.Requests
{
public class PasswordlessLoginRequest
{
public User User { get; set; }
[Required(ErrorMessage = "the phone number is empty")]
[Remote(action: "IsPhoneNumberExist", controller: "Auth")]
public string PhoneNumber { get; set; }
}
}
// User Model
public class User : IdentityUser<int>, ITrackable
{
[Remote(action: "IsPhoneNumberExist", controller: "Auth")]
public override string PhoneNumber { get; set; }
}
// AuthController
[AllowAnonymous]
public class AuthController : ApiControllerBase
{
public AuthController(AuthService authService, SignInManager<User> signInManager,
IConfiguration config, UserManager<User> userManager,
IMapper mapper, INotificationService notifyService, IUserRepository repo)
{
}
[HttpPost("register")]
public async Task<IActionResult> Register(
PasswordlessLoginRequest userForRegisterDto)
{
var user = new User { PhoneNumber = userForRegisterDto.PhoneNumber };
RegisterParams registerParams = new RegisterParams
{
User = user,
};
var code = await _authService.Register(registerParams);
var userToReutrn = _mapper.Map<UserForListDto>(user);
if (code != null)
{
// Send authCode to user
_notifyService.Notify(user, code);
return Ok(userToReutrn);
}
return BadRequest("Error");
}
public IActionResult IsPhoneNumberExist([Bind(Prefix = "User.PhoneNumber")]string PhoneNumber)
{
return Json(data: $"A user phone number {PhoneNumber} already exists.");
//var user = _repo.GetUserByPhone(PhoneNumber).Result;
//if (user != null)
//{
// return Json(data: $"A user phone number {PhoneNumber} already exists.");
//}
//else
//{
// return Json(data: true);
//}
}
}
This isn't a problem with Remote Validation. The problem is you're not completely understanding what Remote Validation is, or does.
Remote validation is a client-side validation technique that invokes an action method on the server to perform validation.
A common example of remote validation is to check whether a username is available in applications where such names must be unique when the user submits the data, and the client-side validation is performed.
Remote validation is performed in the background when a user is filling out a model where a property has the Remote attribute in a view. The user doesn’t have
to click the submit button and then wait for a new view to be rendered and be returned a possible error message explaining that the entered username is taken. Instead, it does a lightweight Ajax request to the server to validate if the username is taken and then returns a true/false value that the client-side jQuery validator will use to show the user they have entered a duplicate username.

Getting current logged on user in web api controller

I am trying to retrieve the current logged on user details in a Web API controller to create a user profile page.
I have got the method created but the user keeps returning null in the code was shown below. This works fine in my MVC controller but in Web API controller it throws an error.
[HttpGet]
public IActionResult userProfile()
{
if (ModelState.IsValid)
{
var user = _userManager.Users.First(x => x.Email == User.Identity.Name);
return Ok(new UserViewModel
{
Id = user.Id,
UserName = user.Email,
FirstName = user.FirstName,
LastName = user.LastName
});
}
else
{
return BadRequest(ModelState);
}
}
UPDATE
[HttpGet]
[Authorize(Roles = "Client, Administrator")]
public IActionResult userProfile()
{
string baseUrl = "https://localhost:5001";
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(baseUrl);
var contentType = new MediaTypeWithQualityHeaderValue("application/json");
client.DefaultRequestHeaders.Accept.Add(contentType);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", HttpContext.Session.GetString("token"));
UserViewModel userModel = new UserViewModel();
HttpResponseMessage response = client.GetAsync("https://localhost:5001/api/UserAPI").Result;
string stringData = response.Content.ReadAsStringAsync().Result;
userModel = JsonConvert.DeserializeObject<UserViewModel>(stringData);
return View(userModel);
}
Error Message:
System.InvalidOperationException: 'Sequence contains no elements
An API is stateless meaning that there is no concept of logged in users or sessions. This is because each request is unique, separate and holds all the information required to provide a response.
An API has no way of knowing who is sending a request, there can be 10k people all sending requests at the same time, so who exactly is "logged in"?
So, if you want to load a user profile then send the userID as a parameter, something like:
[HttpGet]
public IActionResult userProfile(string userEmail)
{
if ( !string.IsNullOrEmpty (userEmail) ) ..... etc etc
{
var user = _userManager.Users.First(x => x.Email == userEmail);
return Ok(new UserViewModel
{
Id = user.Id,
UserName = user.Email,
FirstName = user.FirstName,
LastName = user.LastName
});
}
}
As a side note, if you don't have any input parameters or the input is a primitive type such as string or int, then ModelState.IsValid won't do anything. Only use ModelState.IsValid if you have a model class populated with the right rules.
in my case I could actually replace the string with a class
public class UserProfileRetrieveModel
{
[Required]
public string UserEmail { get;set; }
}
then I can do :
[HttpGet]
public IActionResult userProfile(UserProfileRetrieveModel model)
{
if (ModelState.IsValid)
{
var user = _userManager.Users.First(x => x.Email == model.UserEmail);
//etc
}
else
{
return BadRequest(ModelState);
}
}
--- after question updated
so it looks like you have a client application and from that you call the API.
everything I said above still applies, simply populate the data you have before calling the API.
Example:
public IActionResult userProfile()
{
//removed code we don't care about
string userEmail = "";// get your user email here in the MVC controller
//populate it in the api url.
//you might need to URL encode it since it will contain dots and #
string apiUrl = "https://localhost:5001/api/UserAPI/{userEmail}";
HttpResponseMessage response = client.GetAsync(apiUrl).Result;
}
you don't need to work out anything in the API, just pass everything you need to the API, from the MVC controller.
Now, all this aside, you have massive async issues. that part needs work although it's not related to the question.
Im presuming you're on WebAPI 2.
Try the following:
var user = _userManager.GetUser(HttpContext.User);
or
var user = _userManager.FindById(User.Identity.GetUserId());
Instead of .First() change it to .FirstOrDefault()
From the error message It seems you are trying to retrieve an element from an empty sequence. So check whether you have the the data or not.And also try to use .FirstOrDefault().

Editing a model in asp.net mvc

I'm developing a simple site using ASP.NET MVC 5 and I have some issue with editing a model.
Say, the model is as follows:
public class Model
{
public string Login { get; set; }
public string Password { get; set; }
}
I have two different user roles, for example, Admin and User. Now I'd like User to be able to edit only Login, whilst Admin should be able to edit both properties. I'm using pretty standard edit approach, something like this:
[HttpGet]
public ActionResult Edit(int id)
{
return View(FindModel(id));
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Model model)
{
if (ModelState.IsValid)
{
UpdateModel(model);
return RedirectToAction("Index");
}
return View(model);
}
The ideas I have:
I can show the Password text field only if a user is Admin so that User won't be able to change it. However, as far as I know, MVC binding uses the id (or name, probably) of an element to bind it to a model, so User will be able to manually create a field for password modifying HTML directly and then send the data to the controller.
I can create two different Models for Admin and User, say UserModel and AdminModel, two different actions in controller, and depending on the role of a user, I will create different submit buttons for User and Admin. So, User will click on 'EditUser' button and UserModel won't even content Password, therefore making it not so easy to forge the password.
I can check if the pass was changed and the user is User, an error will be shown.
Other solution is to simply divide editing into two actions: edit login and edit password separately.
Could anyone give any ideas as to how to solve this and whether or not I need to protect the application from such forgery?
There are a LOT of way to do this. To keep the code DRY I would do something like:
public class EditLoginVM
{
public bool CanEditPassword { get; set; }
public string Login { get; set; }
public string Password { get; set; }
}
In the view:
#if (Model.CanEditPassword )
{
// Show Password Textbox blah blah :)
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(EditLoginVM model)
{
if (ModelState.IsValid)
{
var updateModel = new Model();
updateModel.Login = model.Login;
if (/* user is admin, don't use EditLoginVM.IsAdmin!!!! */)
{
model.Password = model.Password;
}
UpdateModel(model);
return RedirectToAction("Index");
}
model.CanEditPassword = /* reset it just in case of overposting */;
return View(model);
}

ViewModel loses data on post

I'm trying to create a complex ViewModel that has both an Index Page and Create Page of Company Notes all within the Details Page of a Company, and would like some guidance as to whether I'm doing this properly.
My problem at the moment is that when I create a new Company Note, it doesn't have any information in the object beyond the EditorFor fields I include in my cshtml - it loses all the data in the ViewModel.
I have a Company model and CompanyController, and in my Details action, I populate all the notes that are relevant to the company, and a form to allow users to add a new note.
My Company and CompanyNotes model are very simple:
public class Company
{
public int CompanyID { get; set; }
// bunch of fields related to the company
public virtual ICollection<CompanyNote> CompanyNotes { get; set; }
}
public class CompanyNote
{
public int CompanyNoteID { get; set; }
public DateTime Date { get; set; }
public string Note { get; set; }
public Company Company { get; set; }
}
I have a ViewModel that looks like this:
public class CompanyViewModel
{
public Company Company { get; set; }
// List of all notes associated with this company
public IEnumerable<CompanyNote> CompanyNotes { get; set; }
// A CompanyNote object to allow me to create a new note:
public CompanyNote CompanyNote { get; set; }
}
This is my Details action, which populates the company record, gets a list of related notes, and displays a create form with a new, empty object:
public ActionResult Details(int id = 0)
{
var viewModel = new CompanyViewModel();
viewModel.Company = db.Companies.Find(id);
if (viewModel.Company == null)
{
return HttpNotFound();
}
viewModel.CompanyNotes = (from a in db.CompanyNotes
where a.Company.CompanyID.Equals(id)
select a).OrderBy(x => x.Date);
viewModel.CompanyNote = new CompanyNote
{
Date = System.DateTime.Now,
Company = viewModel.Company
};
return View(viewModel);
}
This is my CreateNote action in my CompanyController. (Should I split this out into a separate partial view? What would be the benefit?)
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateNote(CompanyViewModel companyViewModel)
{
CompanyNote companyNote = companyViewModel.CompanyNote;
if (ModelState.IsValid)
{
db.CompanyNotes.Add(companyNote);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(companyViewModel);
}
Finally, here's a simplified version of detail.cshtml:
#model Project.ViewModels.CompanyViewModel
// My company detail display is here, removed for sake of berevity
#using (Html.BeginForm("CreateNote", "Company"))
{
#Html.EditorFor(model => model.CompanyNote.Date)
#Html.TextAreaFor(model => model.CompanyNote.Note})
<input type="submit" value="Create" />
}
When I post, my CreateNote action has a companyViewModel that is basically empty, with the exception of companyViewModel.CompanyNote.Date and companyViewModel.CompanyNote.Note, which are the fields in my form - all the other data in the ViewModel is null, so I'm not sure how to even include a reference back to the parent company.
Am I even on the right path here?
Thanks,
Robbie
When I post, my CreateNote action has a companyViewModel that is
basically empty, with the exception of
companyViewModel.CompanyNote.Date and
companyViewModel.CompanyNote.Note, which are the fields in my form -
all the other data in the ViewModel is null, so I'm not sure how to
even include a reference back to the parent company.
That's perfectly normal behavior. Only information that is included in your form as input fields is sent to the server when you submit the form and this is the only information you could ever hope the model binder be able to retrieve.
If you need the CompanyNotes collection in your HttpPost action simply query your backend, the same way you did in your GET action. You could do this by passing the company ID as a hidden field:
#Html.HiddenFor(model => model.Company.CompanyID)
So the idea is to only include as input fields in your form information that the user is supposed to somehow modify. For all the other information, well, you've already have it in your backend so all you have to do is hit it to get it.
Contrary to classic WebForms, there's no longer any notion of ViewState in ASP.NET MVC. It is much closer to the stateless nature of the HTTP protocol.

ViewModel update - changing properties before saving

After trying around the whole day with model bindings, without results, i decided to ask here.
I have got an asp.net razor view where a user (aka Seller) can edit his user details. Furthermore the user should be able to change his password.
I made a ViewModel:
public class EditSellerViewModel
{
public Seller Seller { get; set; }
public ChangePasswordModel ChangePasswordModel { get; set; }
}
My view has two forms which result in two "Submit" buttons. In my action i check which button was clicked. If the "Passwords" form has been submitted, i want to set the new Password in the Seller entity (that actually works) and SaveChanges() which does not change anything in the database (and does not throw any exception). It simply does nothing.
Furthermore if the "Seller Detail" form was submitted, i want to save the sellers data. But TryUpdateModel is always false, even if i use the second parameter which enables the prefix for ViewModels.
[HttpPost]
public ActionResult EditUser(string btnSubmit, FormCollection formValues, EditSellerViewModel editSellerViewModel)
{
int uid = baseFunc.GetIdForUsername(User.Identity.Name);
var seller = bmDBCont.SellerSet.Single(s => s.Id == uid);
if (btnSubmit == "saveSellerPassword")
{
seller.Password = editSellerViewModel.ChangePasswordModel.NewPassword;
bmDBCont.ObjectStateManager.ChangeObjectState(seller, System.Data.EntityState.Modified);
bmDBCont.SaveChanges(); //<-- does nothing
}
if (TryUpdateModel(seller, "Seller")) //<-- never true
{
bmDBCont.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.Titles = CommonListsProvider.GetTitles();
ViewBag.Countries = CommonListsProvider.GetCountries();
return View(editSellerViewModel);
}
Here some debug info screenshots:
formcollection with seller form submitted
formcollection with password form submitted
Please can anyone help me?
See the documentation about TryUpdateModel, its says "Updates the specified model instance using values from the controller's current value provider and a prefix."
The Prefix to use when looking up values in the value provider.
Try use TryUpdateModel(seller) simple method without the "prefix" parameter.
if(TryUpdateModel(seller))
http://msdn.microsoft.com/en-us/library/dd493137(v=vs.108).aspx

Categories