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);
}
Related
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);
}
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.
I have been thinking about this for the past few days and was wondering what everyone else's take is on it. I want to implement some content that is based on what role the user belongs to. Our applications uses HTTP headers to receive role information. In MVC should I have the controller handle the role detection, and then pass a boolean value into the viewmodel.
Viewmodel:
public class ProductViewModel
{
public Product MyProduct { get; set; }
public bool IsAdmin { get; set; }
public bool IsSuperAdmin { get; set; }
}
Controller:
public class ProductController : Controller
{
public ActionResult Info()
{
//get product information here
ProductViewModel pvm = new pvm();
pvm.Product = theProduct;
pvm.IsAdmin = checkAdminAccess(); //This would return a bool if they had access
pvm.IsSuperAdmin = checkSuperAdminAccess();//This would return a bool if they had access
return View(pvm);
}
}
View:
#if(Model.IsAdmin == true || Model.IsSuperAdmin == true)
{
//Render Content for admin (Either directly or via Partial View)
}
#if(Model.IsSuperAdmin == true)
{
//Render Superadmin content (Either directly or via Partial View)
}
//Implement other Product Stuff Here
Or would it be better to create a partial view and handle the logic in the view. This seems like there is less work involved.
#if (Request.Headers.AllKeys.Contains("role")){
ViewBag.Role = Request.Headers["role"].ToString();
}
...
#if(ViewBag.Role == "Admin" || ViewBag.Role == "SuperAdmin")
{
#Html.Partial("AdminPartial")//Or Render Code Directly?
if(ViewBag.Role == "SuperAdmin")
{
#Html.Partial("SuperAdminPartial")//Or Render Code Directly?
}
}
//Implement other Product Stuff Here
Is there any added benefits to doing it one way or another, should I be rendering the content directly or using a partial view or does it not matter? Or am I missing some other way that this should be done?
You can use the AuthorizeAttribute to restrict access/use of controller actions:
[Authorize(Roles = "Admin, Customer")]
public class ProductController : Controller
{
public ActionResult Info()
{
//get product information here
ProductViewModel pvm = new pvm();
return View(pvm);
}
[Authorize(Roles = "Admin")]
public ActionResult EditInfo()
{
//get product information here
EditProductViewModel epvm = new epvm();
return View(epvm);
}
}
This is the most basic way to restrict access by callers to an action method. However, with a large application with many roles/controllers/actions, annotating and keeping track of all attributes can become difficult.
There is product called Fluent Security that allows security to be specified in a central place, and gets rid of the issue mentioned previously.
There is also the issue of the single responsibility principle, look at Darin's answer to this question - he explains it quite well.
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
I have an ASP.NET MVC 3 application with buyer and seller functionalities. The buyer and seller 'security' databases are separated and there are different membership providers (with different requirements) for each. The MVC application is broken into areas, with one for buyers and one for sellers (with the addition of some common stuff). Everything is working fine right now as I have different login screens for buyers and sellers. A requirement has changed whereby I need to use the same login screen (and reset password screen) for both buyers and sellers.
Ideally I would like the process to flow as follows:
Buyer or seller enters their email address and password
Upon form submission a common controller action is called
Action calls logic to look in buyer table. If this email address is present, redirect to logon action in buyer account controller
Else, if email address is present in seller table, redirect to logon action in seller account controller
In theory this should work fine. However, I am aware that it is not possible to redirect to another action using a POST request.
What I would like to know is:
Is there any reason not to change my buyer/seller logon actions to use GET rather than POST given that they will not be called anywhere else in my code? It seems like a hack, but I don't know why...
Can you think of a better way to achieve what I am trying to achieve?
Thanks in advance,
JP
You should have a single LoginController. It can have just one Authorize(string email, string password) action. Where you do "the split" is by creating a MembershipProviderFactory (or whatever you want to name it) that returns an IMembershipProvider interface to your controller. The factory can return the correct type of MembershipProvider by querying the databases.
public class SellerMembershipProvider : IMembershipProvider
{
public bool Authorize(string email, string password)
{
// validate seller
}
}
public class BuyerMembershipProvider : IMembershipProvider
{
public bool Authorize(string email, string, password)
{
// validate buyer
}
}
public class MembershipProviderFactory
{
public IMembershipProvider Create(string email)
{
if(/* email is in seller database*/)
{
return new SellerMembershipProvider();
}
else
{
return new BuyerMembershipProvider();
}
}
}
public class LoginController : Controller
{
private readonly MembershipProviderFactory _providerFactory = new MembershipProviderFactory();
public ActionResult Authenticate(string email, string password)
{
IMembershipProvider provider = _providerFactory.Create(email);
if(provider.Authorize(email, password))
{
return View("MyAccount");
}
else
{
return View("Login");
}
}
}