UserProfile changes does not save ASP.NET MVC - asp.net

I want to make it possible for user to change his profile details(except UserId,Username and password(password changed in another view))
so in my method when I make it like that
public void SaveUser(UserProfile user)
{
context.Entry(user).State = System.Data.EntityState.Modified;
context.SaveChanges();
}
I get an error Store update, insert, or delete statement affected an unexpected number of rows (0). After that I have surfed the stackoverflow, where someone suggested to make it another way, so i edited my SaveUser method
public void SaveUser(UserProfile user)
{
var objectContext = ((System.Data.Entity.Infrastructure.IObjectContextAdapter
)context).ObjectContext;
try
{
objectContext.SaveChanges();
}
catch (OptimisticConcurrencyException)
{
objectContext.Refresh(RefreshMode.ClientWins, context.UserProfiles);
objectContext.SaveChanges();
}
}
after these changes I don't get this error anymore, however my profile details are not changed.
My HttpPost Edit method(HttpGet retrieves correct information)
[HttpPost]
public ActionResult Edit(UserProfile user)
{
if (ModelState.IsValid)
{
repository.SaveUser(user);
return View(user);
}
return View();
}
In my Edit view I have only LabelFor's and EditorFor's for everything(except Id,Username and Password) and Submit button.
Trying to solve that all day long and have not succeeded yet.

Most likely your user's primary key UserId property value is 0 because you don't use the UserId in your view. You are then trying to update an entity with key value 0 that doesn't exist in the database and causes the exception.
Put a hidden form field into your view that carries the user's UserId between client and server back and forth:
#Html.HiddenFor(model => model.UserId)
Normally if you use default routing you should have a route http://www.mysite.com/{controller}/{action}/{id}. If the url is then http://www.mysite.com/User/Edit/3 MVC tries to bind the value 3 to a property with name id (upper or lower case don't matter). Your Update should work then. But I guess your primary key property in User is not Id, but something else - like UserId. In that case the model binder can't find your key property and leaves its value as default (= 0).
Therefore as an alternative to the hidden field you could give the user's key property the name Id or specialize the route to use UserId as parameter.

Related

How do I get data from a related user in a subclass of ParseObject?

I'm working in VisualStudio on a Xamarin project.
I have a ParseObject subclass. It has a field "SentBy" that links to a ParseUser. It's constructed like this:
[ParseClassName("Beacon")]
public class Stuff_ParseBeacon : ParseObject
{
public Stuff_ParseBeacon() { }
[ParseFieldName("sentBy")]
public ParseUser SentBy
{
get { return GetProperty<ParseUser>(); }
set { SetProperty(value);}
}
}
I'm trying to include a parameter that doesn't need to be in the ParseClass on the server, which gets the data stored under "phoneNumber" from the linked user. So, like this:
public string SentByPhoneNumber
{
get
{
return SentBy.Get<string>("phoneNumber");
}
}
But I keep getting the error that there's no such key in that ParseUser--which is false, because all my ParseUsers store a phone number.
What am I doing wrong?
(BTW, in case it matters: I'm trying to use SentByPhoneNumber as a bindable property)
I don't think this is the best way to do it, but this is what I ended up doing.
I just added a phoneNumber field to the Beacon class, and it gets directly filled by the user who creates the Beacon--the user who gets stored in the SentBy field.
The problem with this is that the won't automatically update if I change the phoneNumber on the SentBy user.
This is pretty much exactly what I didn't want to do in my question.
So I'm sure there's a better way to refer to a field on a stored user than manually putting that data in the referring class at the time of creation, but it's what I've had to do to move on.

Is there a better way to implement role based access in ASP.NET framework?

Basically I've spent the last few days trying to figure out how to add simple Admin and Member roles onto a website I'm developing for a friend. (I am using ASP.NET Framework 5.2.7.0). I know that Microsoft has a nice role based access feature built in which allows you to put something like [Authorize Role=("Admin") at the top of the controller; however I have not been able to get it to work at all and most of the resources I've found are for ASP.NET Core.
I've tried modifying my web.config file to enable the role based access (and hopefully migrate the roles and such to my database). But since I've been unable to figure any of this out, I've tried going a more hacky route. (**I am not an advanced programmer, I've been doing this for about a year now and am in no way a pro). This is what I've basically come up with in my attempt to verify if a user is an admin (which also didn't work).
[Authorize]
public class AdminController : Controller
{
private LDSXpressContext db = new LDSXpressContext();
public ActionResult AdminPortal()
{
IsAdmin();
return View();
}
private ActionResult IsAdmin()
{
string name = User.Identity.Name;
//The User.Identity.Name stores the user email when logged in
var currentUserObject = db.accounts.Where(x => x.clientEmail == name);
Account currentUser = new Account();
foreach (var user in currentUserObject)
{
//I loop through the results, even though only one user should
//be stored in the var CurrentUserObject because it's the only
//way I know how to assign it to an object and get its values.
currentUser = user;
}
if (currentUser.role == 2) //the number 2 indicates admin in my db
{
return null;
}
else
{
//Even when this is hit, it just goes back and returns the
//AdminPortal view
return RedirectToAction("Index", "Home");
}
}
}
Now I'm nearly positive that is is NOT a very secure way to check if a signed in user is an admin, but I was hoping that it would at least work. My idea was when someone attempted to access the AdminPortal, the IsAdmin method would run and check if the user is an admin in the database. If they are, then it returns null and the AdminPortal view is displayed, if they are not an Admin, then they are redirected to the Index view on the home page. However, the AdminPortal page is always displayed to any user and this doesn't seem to work either. I've even stepped into the code and watched it run over the return RedirectToAction("Index", "Home"); action, but then it jumps back to the AdminPortal method and just returns the AdminPortal view. So my question is:
1) If anyone happens to have experience with Role Based access in ASP.NET Framework, I would love some tips on how to get it set up
or,
2) If all else fails and I need to use my hacky method, why does it continue to return the AdminView even when the user is not an admin.
**Note: I know I could create a function that returns true or false if the user is an Admin or not, and then have an if/else statement in the AdminPortal controller that will return on view for true and another for false, however I don't want to have to implement that onto every ActionMethod, it'd be nice to keep it down to one line, or just the [Authorize Role="Admin] above the controller if possible.
Thank you guys so much for any help provided, I've been trying to research and fix this for days now and decided to reach out and ask the community!
At a minimum, you'll want to make some adjustments to what you're doing:
[Authorize]
public class AdminController : Controller
{
public ActionResult AdminPortal()
{
if(IsAdmin())
{
return View();
}
return RedirectToAction("Index", "Home");
}
private bool IsAdmin()
{
bool isAdmin = false;
using(LDSXpressContext db = new LDSXpressContext())
{
string name = User.Identity.Name;
//The User.Identity.Name stores the user email when logged in
// #see https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.singleordefault
var currentUser = db.accounts.SingleOrDefault(x => x.clientEmail.Equals(name, StringComparison.OrdinalIgnoreCase));
// If the email doesn't match a user, currentUser will be null
if (currentUser != null)
{
//the number 2 indicates admin in my db
isAdmin = currentUser.role == 2;
}
}
return isAdmin;
}
}
First off, DbContext instances are meant to used, at most, per the lifetime of an HTTP request. Moving it from the class / controller level and placing it within a using block makes sure that it's properly disposed.
Next, your IsAdmin function really just needs to return a true/false value based on your lookup, and then the AdminPortal action can decide what to do with that result.
Since email seems to be a unique field in your table, use the SingleOrDefault or FirstOrDefault LINQ extension to fetch a single matching record. Which one you use is up to you, but if it's truly a unique value, SingleOrDefault makes more sense (it will throw an exception if more than one row matches). Using the StringComparison flag with the String.Equals extension method makes your search case-insensitive. There are a few culture-specific versions of that, but ordinal matching is what I would normally use, here.
Implementing some version of the Identity framework is a bit too long for an answer here, but it's possible to implement a claims-based authentication scheme without too much work. That's something that probably needs a separate answer, though.

Default value for ViewModel

I'm making a simple Search page in MVC with some filters in it. The filters are represented by properties in my ViewModel. My ViewModel is binded to a GET form in the cshtml so my filter will appears in the querystrings and the user will be able to bookmark his search.
What I want to do is to assign a default value to some of my filters.
My (simplified) ViewModel :
public class SearchViewModel
{
//Filter I want to set a default value to
public OrganizationType? OrganizationType {get; set;}
//Results of the search
public IEnumerable<ItemViewModel> Items {get; set;}
}
I'd like to set a default value for OrganizationType. I can't simply set it in the constructor of SearchViewModel because it depends on the current user :
public void InitViewModel(SearchViewModel vm)
{
vm.OrganizationType = _someLogic.GetDefaultValue(_currentUser);
}
First solution was simply to check if OrganizationType is null, then assign a default value :
public ActionResult Search(SearchViewModel vm)
{
if(vm.OrganizationType == null)
vm.OrganizationType = _someLogic.GetDefaultValue(_currentUser);
return View(vm);
}
But this solution doesn't work as a null value corresponds to an empty filter and it's a choice that the user can make. So I can't override it.
The second solution I tried was to specify that the default value of the controller should be null in the Search action :
public ActionResult Search(SearchViewModel vm = null)
{
if (vm == null)
{
vm = new SearchViewModel();
InitViewModel(vm);
}
...
return View(vm);
}
But in practice, the variable vm is never null, so the default values are never setted.
I also tried having two Action, one wihout a ViewModel where I instanciate a new ViewModel with the default values and then call the second action :
public ActionResult Search()
{
var vm = new SearchViewModel();
InitViewModel(vm);
//Simply call the second action with the initizalied ViewModel
return Search(vm);
}
public ActionResult Search(SearchViewModel vm)
{
...
return View(vm);
}
But it doesn't work because there is now an ambiguity between the two action, and asp.net doesn't know which one to choose.
So in summary, I'd like to find a way to set a default value for a ViewModel, without setting it in the constructor and overriding user choices.
Another way to say it, how can I distinguish an "empty" ViewModel from one where some values are binded from the form.
Any idea ?
Ok I think I found a solution to my own problem...
I can use the ModelState property of the controler to check it the ViewModel is empty or was binded from the form :
public ActionResult Search(SearchViewModel vm = null)
{
if (ModelState.Count == 0)
{
InitViewModel(vm);
}
...
return View(vm);
}
So if ModelState.Count equals to 0 it means that user didn't change any filters. So the form is empty and we can bind our default values. As soon as the user will change one of the filters or submit the request, the ModelState.Count will be greater than 0 so we shouldn't set the default value. Otherwise we would override an user choice.
The logic of what you're doing is a little iffy. Generally speaking, if a value is nullable then null is the default value. However, it seems that you're trying to make a distinction here between whether the value is null because it's not set or null because the user explicitly set it to null. This type of semantic variance is usually a bad idea. If null has a meaning, then it should always carry that meaning. Otherwise, your code becomes more confusing and bugs are generally introduced as a result.
That said, you can't count on ModelState having no items. I've honestly never played around with ModelState enough in scenarios where there's not post data, but it's possible there's some scenario where there's no post data and yet ModelState may have items. Even if there isn't, this is an implementation detail. What if Microsoft does an update that adds items to ModelState in situations where it previously had none. Then, your code breaks with no obvious reason why.
The only thing you can really count on here is whether the request method is GET or POST. In the GET version of your action, you can reasonably assume that the user has made no modifications. Therefore, in this scenario, you can simply set the value to whatever you like without concern.
In the POST version of your action, the user has made some sort of modification. However, at this point, there is no way to distinguish any more whether the value is null because it is or because the user explicitly wanted it to be. Therefore, you must respect the value as-is.

asp.net mvc Serverside validation no return data

I'm building a validation form in my application. In that form there are two buttons. One to accept and one to reject. When the user press reject the rejection reason field must be provided. I check this serverside.
I first check what button is pressed and then if the field is empty I add a moddel error to the modelstate. But, because all fields in the form are readonly, those are not posted back to the server and therefor when I return the view back to usern there is no data. I'm probably missing something obvious, but cant find what to do. (I know I can make all fields in my form hidden, but due to the large amount of fields this would be really ugly)
This is my code.
[HttpPost]
public virtual ActionResult Validate(string action, Record dto) {
if(action == Global.Accept) {
ciService.Store(dto);
return RedirectToAction("Index", "Ci");
} else {
if(string.IsNullOrEmpty(dto.RejectionReason)) {
ModelState.AddModelError("RejectionReason", "REQUIRED!!!!");
return View("Validate", dto);
}
ciService.Reject(dto);
return RedirectToAction("Index", "Ci");
}
}
You need to recreate the model from the database and then change it to match whatever changes are posted in dto. Then use that combined model in the view.
Instead of passing the DTO back from the browser, I would use a hidden HTML field or a querystring parameter containing the ID that identifies the DTO. Then your POST action method would look something like:
[HttpPost]
public virtual ActionResult Validate(string action, int id)
{
// reload the DTO using the id
// now you have all the data, so just process as you did in your question
if (action == Global.Accept) { ... }
...
}
Your GET method might look something like the following then...
[HttpGet]
public virtual ActionResult Validate(int id)
{
// load the DTO and return it to the view
return View();
}
In this way you have all the data you need within your POST action method to do whatever you need.
You need to have hidden fields corresponding to each property displayed in UI.
e.g.,
#Html.LabelFor(m=>m.MyProperty) - For Display
#Html.Hiddenfor(m=>m.MyProperty) - ToPostback the value to server
If I understand right, the problem is because you don't use input.
To solve your problem insert some input hidden in your form with the value you need to be passed to the controller
#Html.HiddenFor(model => model.Myfield1)
#Html.HiddenFor(model => model.Myfield2)
that should fix the values not passed back to your actions
If you don't need these fields on the server side, simply create a new ViewModel
RecordValidateViewModel and this contains only the fields in it that need to be validated. The model binder will populate then and you will have validation only on the fields in that model rather than all the other fields you don't seem to want there.
If you need them to validate, then post them back to the server. Its not 'ugly' if hidden.

How can i hold the properties of model between the actions(asp.net mvc)

Here's my model:
public class MyModel
{
public int BaseTypeField { set; get; }
public MyType UserTypeField { set; get; }
}
In the first action, i passed a MyModel to the view normally:
public ActionResult Action1()
{
MyModel model = new MyModel();
//do something with model.UserTypeField
return View(model);
}
In Action1View i can easily modify the model.BaseTypeField with HtmlHelper, but I dont wanna modify model.UserTypeField in this view(neither can i store it in HiddenFor).
Then Action1View submit the model to another action:
public ActionResult Action2(MyModel model)
{
//model.UserTypeField is lost here
return View();
}
Here comes the problem: how can i hold/save the model.UserTypeField except for something like Session??
Well, if you don't want to use session state, then your only option is to pass the information to the client and have him pass it back with his request. One way you could do this would be with a cookie. Another might be to use a hidden form field. You would include the field in your response to Action1, and the browser would automatically submit it in the request to Action2 (assuming you're using a form POST to call the action).
You have a number of options to preserve state across controller actions:
Store it in a Hidden input element in the View (though I appreciate that you say you can't, and there are plenty of good reasons why that might be the case).
Store it in Session State.
Store it in your application database (but then, you may as well use Session State).
Store it in a cookie. You can create a HttpCookie and add it to HttpContext.Current.Response.Cookies in Action1 and read it from HttpContext.Current.Request.Cookies in Action2.
If you only have a small amount of data and have no reason to use Session State elsewhere, I'd probably go for the cookie option. But Session State is there for precisely this kind of purpose. Don't be afraid to use it if it's the right thing.
Each action should have a parameter that has only properties for fields which you would like to accept from the request. The rest of the object should be loaded from the data store again. In other words, don't have Action2 take a property that takes in the whole model as it will allow your consumers to inadvertently alter more properties than they should be able to.
This may seem like a lot of work to do on every step, but you will save yourself many headaches by not having to do all of the validation for all the fields which you do not want changed. It is also easy to load the rest of the model from the data store if you wrap it up in a function.
TempData[] is intended to hold items between actions, but it does use the Session. If keys are not marked using Keep, then they are removed once the next Action is executed.
If you wanted to avoid Session fullstop, then you would have to serialize your object and send it to the client in the view (in a hidden form variable for example) and then deserialize it back into Action2.
If you wanted to use TempData (which would be simplest unless you can't use session for some reason), the syntax would just be:
public ActionResult Action1()
{
MyModel model = new MyModel();
//do something with model.UserTypeField
TempData["UserTypeField"] = model.UserTypeField;
return View(model);
}
public ActionResult Action2(MyModel model)
{
model.UserTypeField = TempData["UserTypeField"];
return View();
}

Resources