Add method - MVC - working with data entry into two tables - c#

I have two classes:
**User
UseP where** User have its id (PK) and UserP has the id (FK) of User.
I'm working on MVC 4. Using Fluent API.
The classes are mapped within the project to connect to the database. My project is split into controllers, services, models and views.
I have a User screen that uses a UserPr field.
How can I instantiate it in class without being the way down? (that way does not work because of the relationship, otherwise work):
**CLASS USER.CS**
public string Login { get { return this.UserP.login } set { login = value }}
inclusion screen to add the User of items, I should also change the status of the screen, this status is within UserP.
Controller
public ViewResultBase Add(User model)
{
if (this.ModelState.IsValid)
{
try
{
this.Service.SaveUserP(model);
return this.SuccessView(true);
}
catch (ValidationException exception)
{
base.AddValidationErrors(exception);
return base.PartialView(model);
}
}
else
{
return base.PartialView(model);
}
}
Service
public void SaveUserP(User item)
{
//save fields from User (Running ok)
base.context.Usuario.Add(item);
//Attempt to save the login and their status in UserP
foreach (var userP in base.context.UserP.Where(x => x.IdUser == item.Id))
{
item.login = userP.Login;
userP.ParticipantType = 3;
base.context.UsersP.Add(userP);
}
}
I've tried that way, but I could not.
If the item.login is only working because login stand as [notmapped] in User.cs
To summarize: In the inclusion screen (User) I have a field that should bring (UserP) login.
When I trigger the method of inclusion, he should save the fields and save the User log in using UsuarioP IdUsuario as key and also changing the status of login (ParticipantType = 3)
The error I get:
Invalid column login (because really there is no login in User)
Time to debug it includes only the User fields, and even through the foreach.
I do not know how to operate, can help me? What if I have not been clear, I put more details

You will need to save the User input first before your foreach loop.
base.context.SaveChanges();

Related

ASP Identity Role Does Not Exist Error

I am trying to create a page in my MVC .NET CORE application in which a user can change the roles of other users. At this point the view and model bindings all work great, but when actually trying to save the roles back to the database I am getting an error saying that the roles don't exists. My approach is this:
Get list of roles from the model.
Delete existing roles for specified user.
Add roles back using list from model.
Do this all in a transaction in case of error.
My code in the controller is as follows:
var user = await _userManager.FindByIdAsync(id);
if (user == null)
{
return View("Error");
}
using (_context.Database.BeginTransaction())
{
var removeResult = await _userManager.RemoveFromRolesAsync(user, await _userManager.GetRolesAsync(user));
if (!removeResult.Succeeded)
{
return View("Error");
}
var addResult = await _userManager.AddToRolesAsync(user, model.MemberRoles);
if (!addResult.Succeeded)
{
return View("Error");
}
}
Where model.MemberRoles as a List<string> of roles. The strange part is is that this process is failing on _userManager.RemoveFromRolesAsync, even though I am passing the existing roles of the user directly into the function. I have tried ditching the UserManager class and going with ef but had no luck. I also verified that the roles in the model match what is in the database. Is there anything obviously wrong that would cause this to fail? Thanks.
Edit
Here is the error I am running into:
InvalidOperationException: Role ADMINISTRATOR does not exist.
Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`4.<AddToRoleAsync>d__32.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
And here are the roles defined in my database:
When you try to add a user to a role, it first tries to find the role. In Sql Profiler, you can see that it is actually searching by the NormalizedName field rather than the Name field.
exec sp_executesql N'
SELECT TOP(2) [r].[Id], [r].[ConcurrencyStamp], [r].[Name], [r].[NormalizedName]
FROM [AspNetRoles] AS [r]
WHERE [r].[NormalizedName] = #__normalizedRoleName_0',N'#__normalizedRoleName_0 nvarchar(256)',#__normalizedRoleName_0=N'ADMINISTRATOR'
The solution is to set the NormalizedName field to the Uppercase of the Name field when you create the Roles:
So in the SeedData class:
/* Create Roles */
IEnumerable<UserRole> allRoles = Enum.GetValues(typeof(UserRole)).Cast<UserRole>();
foreach (UserRole role in allRoles)
{
if (!await roleManager.RoleExistsAsync(role.ToString()))
{
await roleManager.CreateAsync(new IdentityRole {
Name = role.ToString(),
NormalizedName = role.ToString().ToUpper()
});
}
}
I'm actually surprised you're not getting an InvalidCastException instead, as it's not possible to directly cast string[] to List<string>. Not really sure why you're doing it that way in the first place, since you could just as easily just call .ToList(), instead of .ToArray(), and call it a day, if you want a list. Or, since the return type of GetRolesAsync is IList<string>, anyways, you don't even really need to do that. Right now, that's the only problem I see with your code, though.

Not able to retrieve OriginalValues in Entity Framework 5

I am writing a asp.net mvc4 app and I am using entity framework 5. Each of my entities have fields like EnteredBy, EnteredOn, LastModifiedBy and LastModifiedOn.
I am trying to auto-save them by using the SavingChanges event. The code below has been put together from numerous blogs, SO answeres etc.
public partial class myEntities : DbContext
{
public myEntities()
{
var ctx = ((IObjectContextAdapter)this).ObjectContext;
ctx.SavingChanges += new EventHandler(context_SavingChanges);
}
private void context_SavingChanges(object sender, EventArgs e)
{
ChangeTracker.DetectChanges();
foreach (ObjectStateEntry entry in
((ObjectContext)sender).ObjectStateManager
.GetObjectStateEntries
(EntityState.Added | EntityState.Modified))
{
if (!entry.IsRelationship)
{
CurrentValueRecord entryValues = entry.CurrentValues;
if (entryValues.GetOrdinal("LastModifiedBy") > 0)
{
HttpContext currContext = HttpContext.Current;
string userName = "";
DateTime now = DateTime.Now;
if (currContext.User.Identity.IsAuthenticated)
{
if (currContext.Session["userId"] != null)
{
userName = (string)currContext.Session["userName"];
}
else
{
userName = currContext.User.Identity.Name;
}
}
entryValues.SetString(
entryValues.GetOrdinal("LastModifiedBy"), userName);
entryValues.SetDateTime(
entryValues.GetOrdinal("LastModifiedOn"), now);
if (entry.State == EntityState.Added)
{
entryValues.SetString(
entryValues.GetOrdinal("EnteredBy"), userName);
entryValues.SetDateTime(
entryValues.GetOrdinal("EnteredOn"), now);
}
else
{
string enteredBy =
entry.OriginalValues.GetString(entryValues.GetOrdinal("EnteredBy"));
DateTime enteredOn =
entry.OriginalValues.GetDateTime(entryValues.GetOrdinal("EnteredOn"));
entryValues.SetString(
entryValues.GetOrdinal("EnteredBy"),enteredBy);
entryValues.SetDateTime(
entryValues.GetOrdinal("EnteredOn"), enteredOn);
}
}
}
}
}
}
My problem is that entry.OriginalValues.GetString(entryValues.GetOrdinal("EnteredBy")) and entry.OriginalValues.GetDateTime(entryValues.GetOrdinal("EnteredOn")) are not returning the original values but rather the current values which is null. I tested with other fields in the entity and they are returning the current value which were entered in the html form.
How do I get the original value here?
I think the problem may be that you are using the instance provided by the model binder as the input to your controller method, so EF does not know anything about that entity and its original state. Your code may look like this:
public Review Update(Review review)
{
_db.Entry(review).State = EntityState.Modified;
_db.SaveChanges();
return review;
}
In that case, EF knows nothing about the Review instance that is being saved. It is trusting you and setting it as modified, so it will save all of its properties to the database, but it does not know the original state\values of that entity.
Check the section named Entity States and the Attach and SaveChanges Methods of this tutorial. You can also check the first part of this article, that shows how EF does not know about the original values and will update all properties in the database.
As EF will need to know about the original properties, you may first load your entity from the database and then update its properties with the values received in the controller. Something like this:
public Review Update(Review review)
{
var reviewToSave = _db.Reviews.SingleOrDefault(r => r.Id == review.Id);
//Copy properties from entity received in controller to entity retrieved from the database
reviewToSave.Property1 = review.Property1;
reviewToSave.Property2 = review.Property2;
...
_db.SaveChanges();
return review;
}
This has the advantage that only modified properties will be send and updated in the database and that your views and view models don't need to expose every field in your business objects, only those that can be updated by the users. (Opening the door for having different classes for viewModels and models\business objects). The obvious disadvantage is that you will incur an additional hit to the database.
Another option mentioned in the tutorial I referenced above is for you to save the original values somehow (hidden fields, session, etc) and on save use the original values to attach the entity to the database context as unmodified. Then update that entity with the edited fields. However I would not recommend this approach unless you really need to avoid that additional database hit.
Hope that helps!
I was running into a similar problem when trying to audit log the Modified values of an Entity.
It turns out during the post back the ModelBinder doesn't have access to the original values so the Model received is lacking the correct information. I fixed my problem by using this function which clones the current values, relods the object, and then reset the current values.
void SetCorrectOriginalValues(DbEntityEntry Modified)
{
var values = Modified.CurrentValues.Clone();
Modified.Reload();
Modified.CurrentValues.SetValues(values);
Modified.State = EntityState.Modified;
}
You can gain access to the DbEntityEntry though the change tracker, or the entry function from your context.

How to setup temp storage area in Silverlight for user id

Our currently logged in user info is inside the PBM project while our search class is inside web.
so we can not access utiltiy class from web
we will be need of to write loggedin user info in temporary storage of silverlight-
This comes into play when I want to display only those records that the user has entered.
Here in this code I can do that with a search as well:
// search by user logged in has to be done - userid 1 hard coded
pSearchQuery = pSearchQuery.Where(item => item.AddedBy == 1);
How do I go about setting up this storage area in Silverlight? Note security is not a major concern in this app.
Here is code how I display patients. the user can do a search by first or last name or gender. here I would add filter to ensure that user sees only his/her patients.
namespace PBM.Web.Classes
{
public class Search
{
public static IQueryable GetSearchQueryPatient(IQueryable pSearchQuery, Patient pPatient)
{
if (!string.IsNullOrEmpty(pPatient.FirstName))
{
pSearchQuery = pSearchQuery.Where(item => item.FirstName.Contains(pPatient.FirstName)) ;
}
if (!string.IsNullOrEmpty(pPatient.LastName))
{
pSearchQuery = pSearchQuery.Where(item => item.LastName.Contains(pPatient.LastName));
}
if (pPatient.Gender.HasValue && pPatient.Gender.Value > 0)
{
pSearchQuery = pSearchQuery.Where(item => item.Gender.Value == pPatient.Gender.Value);
}
// search by user logged in has to be done - need to write user loggedin to a silverlight storage area and compare here to Addedby which is what we call the user owner of the patient record. current set to 1.
pSearchQuery = pSearchQuery.Where(item => item.AddedBy == 1);
pSearchQuery = pSearchQuery.OrderBy(item => item.FirstName).ThenBy(item => item.LastName);
return pSearchQuery;
You could store your user id in the session... this should be accessable from booth worlds... or should take a look at the html bridge for silverlight.
http://www.silverlight.net/learn/overview/working-with-javascript/html-bridge-(silverlight-quickstart)
Or google for some Javascript/Silverlight Interop...
Don't know if I understand you correct, if not please provide some additional information or add code example...

How to display specific options in the menu bar just to the user with the admin role?

I am developing a web application which shows an admin a different menu bar than it shows the normal user. I mean if the user is the admin, he will see some options related to the settings of the system itself.
I created the menu bar as a user control (ascx) and I defind the following method in its code-behind:
public bool DisplayAdminOnlyMenuItems
{
get { return menuItem1ToHide.Visible; }
set
{
menuItem1ToHide.Visible = value;
}
}
then in the in the site.master page, I put the menu bar user control and set the DisplayAdminMenuItems as false
and in the code-behind, I use the following logic:
if(user.username == "John"){
MenuBar1.DisplayAdminOnlyMenuItems = true;
}
Everything works well and fine. Now, I got new requirements which complicates my life a little bit.
I have the three tables in my database which structure as following:
User table: Name, Username, Department (Username is the primary key)
Roles table: RoleID, RoleName (RoleID is the primary key)
UserRole table: UserRoleID, Username, RoleID (UserRoleID is the primary key)
I have three roles: Admin, Organizer and User
I want to display the Admin menu just for the user who has Admin role, so how can I do that?
if(user.username == "John"){
MenuBar1.DisplayAdminOnlyMenuItems = true;
}
The approach you used above is not right because it is not scalable. What if there are two admins? Will you put an OR condition for each admin?
Right approach is your new requirement which is based on Role rather than users.
When you get a user check it's role and display UI based on role
if(user.RoleName == "Admin"){
MenuBar1.DisplayAdminOnlyMenuItems = true;
}
I am assuming you can get RoleName if you have UserName
What you should do is add a new method or two to your user class to perform this logic so that you don't have references to roles spread throughout your code and so that future changes can be accommodated.
For example, if you get a request next week to add a super admin role and this role should also be able to have access to the admin role functionality without the user being assigned the admin role, you will have a hard time updating your code.
However, if you add a method like this to your user class:
public const string ADMIN_ROLE = "ADMIN";
public const string SUPER_ADMIN_ROLE = "SUPERADMIN";
public bool HasAdminAuthority() {
// Assuming roles are not indexed by name
foreach (string sRole in this.Roles) {
switch (sRole.ToUpper()) {
case ADMIN_ROLE:
return true;
case SUPER_ADMIN_ROLE:
return true;
}
}
return false;
}
Another useful method to add is something like:
public bool IsInRole(string sRoleToFind) {
foreach (string sRole in this.Roles) {
if (sRoleToFind.Equals(sRole, StringComparison.InvariantCultureIgnoreCase) {
return true;
}
}
return false;
}
Then you can use one of these methods in your UI logic to control the behavior (I would strongly recommend the first one).
Also note that in both of the above examples, if the roles in the user are in dictionaries, for example, the loops can be replaced by .ContainsKey method calls on the dictionary.
You can use the LoginView Control.
<asp:LoginView ID="lgvAdmins" runat="server">
<asp:rolegroup roles="Administrators">
<contenttemplate>
...controls...
</contenttemplate>
</rolegroup>
<asp:rolegroup roles="Users">
<contenttemplate>
...controls...
</contenttemplate>
</rolegroup>
</asp:LoginView>
I think you need to enable the RoleManager in your web.config though. You can also specify controls for anonymous and other normal logged-in users. Or you could use the Application_BeginRequest to get the user's roles at that point.

MVC 2.0 C# ModelUpdate wont update

I'm having trouble getting a ModelUpdate or TryModelUpdate to work in my code.
I'm using the default Role Manager and Login system created by MVC when running the ASP.Net configuration tool. What I'm trying to do is add another column to the Users table so I can record if my users are also customers. So I want to record their CustomerID there.
I used the ADO Entity Data Model to generate all my model code based off my database. The code it created for the field I want to update is here:
public string CustomerID
{
get
{
return this._CustomerID;
}
set
{
this.OnCustomerIDChanging(value);
this.ReportPropertyChanging("CustomerID");
this._CustomerID = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, true);
this.ReportPropertyChanged("CustomerID");
this.OnCustomerIDChanged();
}
}
private string _CustomerID;
partial void OnCustomerIDChanging(string value);
partial void OnCustomerIDChanged();
In my controller Im trying to update the CustomerID field with this code:
var userToUpdate = dbu.aspnet_Users.First(u => u.UserName == User.Identity.Name);
UpdateModel(userToUpdate, new string[] { "CustomerID"}, txtID);
dbu.SaveChanges();
But I get an error saying the overload method has some invalid arguments.
I get that the issue is in assigning txtID to CustomerID based off the error, but whats the correct way to do it?
If I need to add more info please let me know.
I figured it out. Apparantly ModelUpdate won't let me pass in custom data and it needs to be passed in from the Form Collection. So using UpdateModel(userToUpdate, new string[] {"CustomerID"}, form.ToValueProvider()); worked.

Categories

Resources