For the life of me I cannot figure out how to simply add roles to my MVC 5 application. This was such a breeze in MVC 4.
The tables are there out of the box,
AspNetRoles, which has an Id and Name ("Admin", "User", etc...)
AspNetUsers, which has an Id and other user fields
AspNetUserRoles - this table has a UserId and RoleId, which is mapped to the tables above.
I want to be able to check to see if a signed in user has the "admin" role or just a "user" role.
Here is what I have tried:
if (User.IsInRole("Admin"))
{
//do something
}
The value always come back false.
I also tried:
var UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context));
var userRole = UserManager.GetRoles(user.GetUserId());
if (userRole[0] == "Admin")
{
// do something
}
This throws error :
"Index was out of range. Must be non-negative and less than the size
of the collection."
Is there a simple method that can get the role for the current logged in user? I have seen references to using Role Manager, but I have been unable to get that to work. I believe that was for Identity 1.0
Thanks
The error is telling you that userRole is empty, as even an index of 0, is not "less than the size of the collection". That means, the user in question does not belong to any roles.
The methodology for this is really not much different than it ever has been. The role must exist, first, and the user needs to be associated with that role. As a result, the first step is to populate your roles:
db.Roles.Add(new Role { Name = "Admin" });
You can use RoleManager for this, but I find that to be overkill when you can just utilize the context directly.
Then:
UserManager.AddToRole(userId, "Admin");
That's literally all there is to it. If you like, you can override Seed in Migrations\Configuration.cs to seed some users/roles into the database automatically. For roles, that would just look like:
context.Roles.AddOrUpdate(
r => r.Name,
new Role { Name = "Admin" },
...
);
I had a similar problem. Using MySQL and I had this in my config file:
<providers>
<provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6, Version=6.9.6.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d"></provider>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
changed to:
<providers>
<provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6, Version=6.9.6.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d"></provider>
</providers>
Related
In an MVC5 application I am using Windows Authentication and wanted to use our Active Directory Groups as roles as this is strictly and intranet application. I am using the WindowsTokenRoleProvider as so:
<roleManager defaultProvider="WindowsProvider" enabled="true" cacheRolesInCookie="false">
<providers>
<add name="WindowsProvider" type="System.Web.Security.WindowsTokenRoleProvider" applicationName="/" />
</providers>
</roleManager>
I have tried a few other variations of that config including using the cookie cache, but the end result is that it takes ~20 seconds per group check. I check role like this:
User.IsInRole(#"Domain\UserName")
Is there something I have completely missed in setting this up? I can't believe it would be normal for authentication to take 20 seconds per check. The user I am checking is in ~50 groups, but I didn't think that would be enough to slow it down so much.
So best solution at this point seems to be this stackoverflow question
I will probably play around with how the roles get checked/added to see if I can pinpoint the issue, but I wouldn't expect this slowness from only being in 50 groups.
UPDATE:
So while actually enumerating my groups I found my user was in over 400 groups which might explain why it took so long. I still don't under stand why the IsInRole method on user would call GetRolesForUser instead of just calling IsUserInRole directly, but this makes things exponentially faster
UPDATE 2:
Old answer was deleted so here is my class:
public class CustomWindowsTokenRoleProvider : WindowsTokenRoleProvider
{
public override string[] GetRolesForUser(string username)
{
List roles = null;
string key = String.Concat(username, ":", base.ApplicationName);
Cache cache = HttpContext.Current.Cache;
if (cache[key] != null)
{
roles = new List(cache[key] as string[]);
}
if (roles == null)
{
roles = new List();
// AppSettings.APPLICATION_GROUPS is an IEnumerable that returns just the groups that my application is interested in checking against
foreach (string role in AppSettings.APPLICATION_GROUPS)
{
if (base.IsUserInRole(username, role));
{
roles.Add(role);
}
}
cache.Insert(key, roles.ToArray(), null, DateTime.Now.AddHours(1), Cache.NoSlidingExpiration);
}
return roles.ToArray();
}
}
I am currently using ADFS authentication mechanism to authenticate the user. In that case I am setting authenticationmode as None instead of forms authentication. After the user loggedIn successfully the claims object will provide the role data associated with the loggedIn user so in that case how the sitemap roles attribute will be able to pick up the role from the claims object. Can you explain me how the securityTrimmingEnabled property will be used.
I used the custom class ADFSRoleProvider.cs which inherits the RoleProvider class and overridden the method GetRolesForUser method but the method is not invoked unless I am setting the
<authentication mode="Forms"/>
and this in turn is also not able to interact with the roles attribute mentioned in the siteMapNode node.
The main issue is after the user logins in successfully using the ADFS authentication mechanism how will the sitemap role attribute know about the role of the loggedIn User.
Could please provide some code sample and help regarding the above mentioned issue.
Are you sure that a custom role provider is necessary? The IClaimsPrincipal object provides roles for the user, it takes your claims of type ClaimTypes.Role.
It could be that your issue is caused by some inconsistencies in the securityTrimming implementation. Years ago I had to write my own sitemap provider to correctly handle the trimming.
public class XmlSiteMapDefaultProvider : XmlSiteMapProvider
{
public override bool IsAccessibleToUser( HttpContext context, SiteMapNode node )
{
if ( node.Roles.Count > 0 )
{
foreach ( string role in node.Roles )
if ( role == "*" &&
context.User != null &&
context.User.Identity != null &&
context.User.Identity.IsAuthenticated
)
return true;
else
{
if ( context.User != null )
if ( context.User.IsInRole( role ) )
return true;
}
return false;
}
return true;
}
}
Just register it in the web.config as your SiteMapProvider:
<siteMap enabled ="true" defaultProvider="XmlSiteMapDefaultProvider">
<providers>
<add name="XmlSiteMapDefaultProvider" type="XmlSiteMapDefaultProvider" siteMapFile="Web.sitemap" securityTrimmingEnabled="true" />
</providers>
</siteMap>
i started a site based on asp.net MVC 3 and MySql
i got the membership to work with the MySQL .NET connector
so with the default application you get with a new project of mvc 3 i have a working register form and a working login form
but... how do i add more fields to the registration form?
i know how to add them my model and to the page.. but how do i make the membership keep this new data ill get for the user?
will i have to make the columns in the database by myself? or does membership knows how to create them automaticly somehow?
i only want 3 more fields for the registration...
thanks
take a look at your AccountModels.cs file. It contains
public class RegisterModel
{
// User name, Email Adress, Password, Password confirmation already there
// you can add something like below
[Required]
[Display(Name = "Nickname")]
public string Nickname { get; set; }
}
Once you have a new property in your model you need to update the view. In the Views > Account > Register.cshtml you should add
<div class="editor-label">
#Html.LabelFor(m => m.Nickname )
</div>
<div class="editor-field">
#Html.PasswordFor(m => m.Nickname )
#Html.ValidationMessageFor(m => m.Nickname )
</div>
When you're done with that you need to update the registration logic to use your new property. Go to AccountController and find
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
MembershipCreateStatus createStatus;
Membership.CreateUser(model.UserName, model.Password, model.Email, null, null, true, null, out createStatus);
if (createStatus == MembershipCreateStatus.Success)
{
FormsAuthentication.SetAuthCookie(model.UserName, false /* createPersistentCookie */);
//
// this would be a good place for you to put your code to do something with model.Nickname
//
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", ErrorCodeToString(createStatus));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
If you want to persist that information to Users ASP.NET Profile, you need this in Web.config
<profile>
<providers>
<clear />
<add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/" />
</providers>
<properties>
<add name="Nickname" defaultValue="False" type="System.String" />
</properties>
</profile>
Then in your code - you can do
var userProfile = ProfileBase.Create(model.UserName);
to get/set your properties in Profile
I suggest you this solution.
Create a table called UserDetails
Add a field that point to the UserId
You're done.
Now you need some class to retrieve thoses fields based on the UserId. You could also implement your own MembershipProvider and add a method like GetUserDetail() which return an object containing your extra fields.
This is based on an official ASP.NET article : http://www.asp.net/web-forms/tutorials/security/membership/storing-additional-user-information-cs
Suppose you want to create fields for Age and Gender. you can first of all create UI for registration in the corresponding Registration view. it means creating input fields for Age and Gender. then in the AccountController go to Register view and add Age and Gender fields in ApplicationUser object (that is already declared). And there you go.
Detailed information on this can be found http://blog.falafel.com/customize-mvc-5-application-users-using-asp-net-identity-2-0/
I don't want to have the security question and answer feature that ASP.Net Membership Provider gives, but I DO want to enable a lost/forgotten password page.
This page would be where a user would enter his/her email address and an email would be sent to that address if the user was registered for them to reset their password via a link sent to that registered email address
I've created the custom table to track such requests, the random key assigned to the request as well as an expiry date on the request. However in writing the code to actually reset the password, I realised that there doesn't seem to be a method that does something like ResetPassword(email, newPassword) without needing to use the Security Q&A bit (which I don't have).
Is there any way to simply reset a user's password via a built in Membership function?
If not, how would I need to get this done?
Thanks in advance for any help given.
-Nissan
What I ended up doing was the following
public string ResetPassword(string email)
{
var m_userName = Membership.GetUserNameByEmail(email);
var m_user = Membership.GetUser(m_userName);
return m_user.ResetPassword();
}
then I added a new method to use this value to change the password
public bool ChangeLostPassword(string email, string newPassword)
{
var resetPassword = ResetPassword(email);
var currentUser = Membership.GetUser(Membership.GetUserNameByEmail(email), true);
return currentUser.ChangePassword(resetPassword, newPassword);
}
Why don't you change this options in web.config?
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="false"
in
<membership>
<providers>
<clear/>
<add name="AspNetSqlMembershipProvider" ...
..........
I don't know what I am missing, but I added Profile properties in the Web.config file but cannot access Profile.Item in the code or create a new profile.
I had the same problem today, and learned a lot.
There are two kinds of project in Visual Studio -- "Web Site Projects" and "Web Application Projects." For reasons which are a complete mystery to me, Web Application Projects cannot use Profile. directly... the strongly-typed class is not magically generated for you from the Web.config file, so you have to roll your own.
The sample code in MSDN assumes you are using a Web Site Project, and they tell you just to add a <profile> section to your Web.config and party on with Profile.property, but that doesn't work in Web Application Projects.
You have two choices to roll your own:
(1) Use the Web Profile Builder. This is a custom tool you add to Visual Studio which automatically generates the Profile object you need from your definition in Web.config.
I chose not to do this, because I didn't want my code to depend on this extra tool to compile, which could have caused problems for someone else down the line when they tried to build my code without realizing that they needed this tool.
(2) Make your own class that derives from ProfileBase to represent your custom profile. This is easier than it seems. Here's a very very simple example that adds a "FullName" string profile field:
In your web.config:
<profile defaultProvider="SqlProvider" inherits="YourNamespace.AccountProfile">
<providers>
<clear />
<add name="SqlProvider"
type="System.Web.Profile.SqlProfileProvider"
connectionStringName="sqlServerMembership" />
</providers>
</profile>
In a file called AccountProfile.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Profile;
using System.Web.Security;
namespace YourNamespace
{
public class AccountProfile : ProfileBase
{
static public AccountProfile CurrentUser
{
get { return (AccountProfile)
(ProfileBase.Create(Membership.GetUser().UserName)); }
}
public string FullName
{
get { return ((string)(base["FullName"])); }
set { base["FullName"] = value; Save(); }
}
// add additional properties here
}
}
To set a profile value:
AccountProfile.CurrentUser.FullName = "Snoopy";
To get a profile value
string x = AccountProfile.CurrentUser.FullName;
Web Application Projects can still use the ProfileCommon object but only at runtime. The code for it is just not generated in the project itself but the class is generated by ASP.Net and is present at runtime.
The simplest way to get to object is to use a dynamic type as demonstrated below.
In the Web.config file declare the profile properties:
<profile ...
<properties>
<add name="GivenName"/>
<add name="Surname"/>
</properties>
Then to access the properties:
dynamic profile = ProfileBase.Create(Membership.GetUser().UserName);
string s = profile.GivenName;
profile.Surname = "Smith";
To save changes to profile properties:
profile.Save();
The above works fine if you are comfortable using dynamic types and don't mind the lack of compile-time checking and intellisense.
If you use this with ASP.Net MVC you have to do some additional work if you pass the dynamic profile object to your views since the HTML helper methods don't play well with "model" objects that are dynamic. You will have to assign profile properties to statically typed variables before passing them to HTML helper methods.
// model is of type dynamic and was passed in from the controller
#Html.TextBox("Surname", model.Surname) <-- this breaks
#{ string sn = model.Surname; }
#Html.TextBox("Surname", sn); <-- will work
If you create a custom profile class, as Joel described above, ASP.Net will still generate the ProfileCommon class but it will inherit from your custom profile class. If you don't specify a custom profile class ProfileCommon will inherit from System.Web.Profile.ProfileBase.
If you create your own profile class make sure that you don't specify profile properties in the Web.config file that you've already declared in your custom profile class. If you do ASP.Net will give a compiler error when it tries to generate the ProfileCommon class.
Profile can be used in Web Application Projects too.
The properties can be defined in Web.config at design time or programmatically. In Web.config:
<profile enabled="true" automaticSaveEnabled="true" defaultProvider="AspNetSqlProfileProvider">
<providers>
<clear/>
<add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="TestRolesNProfiles"/>
</providers>
<properties>
<add name="FirstName"/>
<add name="LastName"/>
<add name ="Street"/>
<add name="Address2"/>
<add name="City"/>
<add name="ZIP"/>
<add name="HomePhone"/>
<add name="MobilePhone"/>
<add name="DOB"/>
</properties>
</profile>
or Programmatically, create the profile section by instantiating a ProfileSection and creating individual properties using ProfilePropertySettings and ProfilePropertySettingsColletion, all of which are in System.Web.Configuration Namespace.
To use those properties of the profile, use System.Web.Profile.ProfileBase Objects. The profile properties cannot be accessed with profile. syntax as mentioned above, but can be easily done by instantiating a ProfileBase and using SetPropertyValue("PropertyName") and GetPropertyValue{"PropertyName") as follows:
ProfileBase curProfile = ProfileBase.Create("MyName");
or to access the profile of current user:
ProfileBase curProfile = ProfileBase.Create(System.Web.Security.Membership.GetUser().UserName);
curProfile.SetPropertyValue("FirstName", this.txtName.Text);
curProfile.SetPropertyValue("LastName", this.txtLname.Text);
curProfile.SetPropertyValue("Street", this.txtStreet.Text);
curProfile.SetPropertyValue("Address2", this.txtAdd2.Text);
curProfile.SetPropertyValue("ZIP", this.txtZip.Text);
curProfile.SetPropertyValue("MobilePhone", txtMphone.Text);
curProfile.SetPropertyValue("HomePhone", txtHphone.Text);
curProfile.SetPropertyValue("DOB", txtDob.Text);
curProfile.Save();
When you create a new Web site project in Visual Studio then the object that is returned from Profile will be (automatically) generated for you. When you create a Web application project or an MVC project, you will have to roll your own.
This probably sounds more difficult than it is. You need to do the following:
Create a database using aspnet_regsql.exe This tool is installed along with the .NET framework.
Write a class that derives from ProfileGroupBase or install the Web Profile Builder (WPB) that can generate the class for you from the definition in Web.Config. I have been using WPB for a while and up until now it has done what is expected of it. If you have a lot of properties, using WPB can save quite a bit of time.
Make sure the connection to the database is properly configured in Web.Config.
Now you are set to create an instance of your profile class (in the controller)
You will probably need the profile property values in your views. I like to pass the profile object itself along to the view (not individual properties).
If you are using a web application project, you cannot access the Profile object at design-time out-of-the-box. Here is a utility that supposedly does it for you: http://weblogs.asp.net/joewrobel/archive/2008/02/03/web-profile-builder-for-web-application-projects.aspx. Personally, that utility caused an error in my project so I ended up rolling my own profile class to inherit from ProfileBase. It was not hard to do at all.
MSDN walkthrough for creating a custom class (a.k.a. Joel's method):
http://msdn.microsoft.com/en-us/magazine/cc163624.aspx
I was also running through the same issue. But instead of creating a class which inherits from ProfileBase, I used the HttpContext.
Specify properties in web.config file as follows : -
Now, write the following code : -
Compile and run the code. You will get following output: -
The Web Profile Builder worked great for me. The class it generated has a lot more in it than as described by Joel's post. Whether or not its actually needed or useful I dont know.
Anyway for those looking for an easy way to generate the class, but not wanting to have an external build tool dependency you can always
use the web profile builder
delete all trace of it!
keep using the generated Profile class
OR (untested but may just work)
create a web site project
create your element
snap the generated class and copy it over to your web project project
if this second approach does work can someone let me know for future reference
Just want to add to Joel Spolsky's answer
I implemented his solution, working brilliantly btw - Cudos!
For anyone wanting to get a user profile that's not the logged in user I used:
web.config:
<connectionStrings>
<clear />
<add name="LocalSqlConnection" connectionString="Data Source=***;Database=***;User Id=***;Password=***;Initial Catalog=***;Integrated Security=false" providerName="System.Data.SqlClient" />
</connectionStrings>
and
<profile defaultProvider="SqlProvider" inherits="NameSpace.AccountProfile" enabled="true">
<providers>
<clear/>
<add name="SqlProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="LocalSqlConnection"/>
</providers>
And then my custom class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Profile;
using System.Web.Security;
namespace NameSpace
{
public class AccountProfile : ProfileBase
{
static public AccountProfile CurrentUser
{
get
{
return (AccountProfile)
(ProfileBase.Create(Membership.GetUser().UserName));
}
}
static public AccountProfile GetUser(MembershipUser User)
{
return (AccountProfile)
(ProfileBase.Create(User.UserName));
}
/// <summary>
/// Find user with matching barcode, if no user is found function throws exception
/// </summary>
/// <param name="Barcode">The barcode to compare against the user barcode</param>
/// <returns>The AccountProfile class with matching barcode or null if the user is not found</returns>
static public AccountProfile GetUser(string Barcode)
{
MembershipUserCollection muc = Membership.GetAllUsers();
foreach (MembershipUser user in muc)
{
if (AccountProfile.GetUser(user).Barcode == Barcode)
{
return (AccountProfile)
(ProfileBase.Create(user.UserName));
}
}
throw new Exception("User does not exist");
}
public bool isOnJob
{
get { return (bool)(base["isOnJob"]); }
set { base["isOnJob"] = value; Save(); }
}
public string Barcode
{
get { return (string)(base["Barcode"]); }
set { base["Barcode"] = value; Save(); }
}
}
}
Works like a charm...
Great post,
Just a note on the web.config
if you dont specify the inherit attribute in the profile element
you will need to specify each indiviudal profile property inside the profile
element on the web.config as below
<properties>
<clear/>
<add name="property-name-1" />
<add name="property-name-2" />
..........
</properties>