How to delete a SimpleMembership user? - asp.net-mvc

In my ASP.NET MVC app using Forms Authentication (via SimpleMembership), how do I delete a user/account?
The WebSecurity class doesn't expose DeleteUser. On a lark, I tried:
WebSecurity.InitializeDatabaseConnection(
"MyDbConnection", "Users", "Id", "UserName", autoCreateTables: true);
new SimpleMembershipProvider().DeleteUser(userName, true);
but that complains that I haven't initialized the SimpleMembership provider. In any event, I would very much appreciate some sample code that shows how to delete a user. Thanks!
Bob

PussInBoots is absolutely correct, although this always throws a foreign key constraint violation for me if the deleted user has been added to any roles. I'm sure this was inferred by PussInBoots' "//TODO: Add delete logic here" comment, but I will typically clean up role memberships first like this:
[HttpPost]
public ActionResult Delete(string userName, FormCollection collection)
{
try
{
// TODO: Add delete logic here
if (Roles.GetRolesForUser(userName).Count() > 0)
{
Roles.RemoveUserFromRoles(userName, Roles.GetRolesForUser(userName));
}
((SimpleMembershipProvider)Membership.Provider).DeleteAccount(userName); // deletes record from webpages_Membership table
((SimpleMembershipProvider)Membership.Provider).DeleteUser(userName, true); // deletes record from UserProfile table
return RedirectToAction("Index");
}
catch
{
return View(userName);
}
}

You probably need something like this:
//
// GET: /Members/Delete?userName=someuser
public ActionResult Delete(string userName)
{
var user = context.UserProfiles.SingleOrDefault(u => u.UserName == userName);
return View(user);
}
//
// POST: /Members/Delete?userName=someuser
[HttpPost]
public ActionResult Delete(string userName, FormCollection collection)
{
try
{
// TODO: Add delete logic here
((SimpleMembershipProvider)Membership.Provider).DeleteAccount(userName); // deletes record from webpages_Membership table
((SimpleMembershipProvider)Membership.Provider).DeleteUser(userName, true); // deletes record from UserProfile table
return RedirectToAction("Index");
}
catch
{
return View(userName);
}
}

What happens if you just do Membership.DeleteUser(username,true). You might get a little prompt for adding a using directive on Membership. If you have it configured properly, you shouldn't need to be creating new SimpleMembershipProvider instance.
If you create it on the fly like that, you'll need to set connections on that object and configure it programmatically(it has no clue about the connection you created above). Usually people do that in web.config, but if you created the app using the forms authentication template, then you should have that taken care of automatically.
Your provider my have this bug for which is discussed and solved here: Membership.DeleteUser is not deleting all related rows of the user

I was getting the exception System.NotSupportedException from Membership.DeleteUser when running my unit tests. The problem was the app.config had the "DefaultProvider" set to "ClientAuthenticationMembershipProvider", which as you can see here is "not used by this class".
The fix was to update my app.config to match my web.config and properly configure the default provider:
<membership>
<providers>
<clear />
<add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="Crelate.Properties.Settings.DatabaseMembershipServicesConnection" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="true" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
</providers>
</membership>

Hey just wanted to post this for anyone running into ObjectContext state issues after following PussInBoots example, because I had the same problem...
If you are accessing additional user data you will need to remove that user from the data context using:
context.Users.Remove(user);
Rather than:
((SimpleMembershipProvider)Membership.Provider).DeleteUser(userName, true);
This will keep you EF context up to date and remove the user from the DB.

Related

Unable to retrieve metadata for 'MyService.Entities.Email'. The type initializer for 'System.Data.Entity.Internal.AppConfig' threw an exception

I am trying out the new Visual Studio 11 Beta with MVC 4.0 and EF 4.3.1.
When adding a controller I get the following message "Unable to retrieve metadata for 'MyService.Entities.Email'. The type initializer for 'System.Data.Entity.Internal.AppConfig' threw an exception"
Repro
I have a basic a solution which has an Entities project (MyService.Entities) and and MVC project (MyService.Website).
In the Entities project I have the bare bones for code first with a class for "Email" and a class for the DbContext. The two classes look like:
EntitiesDb
namespace MyService.Entities
{
public class EntitiesDb : DbContext
{
public EntitiesDb(string nameOrConnectionString)
: base(nameOrConnectionString)
{ }
public EntitiesDb()
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Sets up the defaults for the model builder
// Removes the metadata being written to the database, This is being depreciated anyhow
modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
// Sets the table names so that they are named as a none plural name
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
// Creates the database from scratch when it builds
base.OnModelCreating(modelBuilder);
}
public DbSet<Email> Emails { get; set; }
}
}
Email
namespace MyService.Entities
{
public class Email
{
public Email()
{
}
[Key, DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.Identity)]
public int EmailId { get; set; }
public string Subject { get; set; }
public string BodyHTML { get; set; }
}
}
I have referenced the MyService.Entities project in the MyService.Website MVC project and set the Connection String to fit my Database name
<add name="EntitiesDb" connectionString="Data Source=localhost;Integrated Security=SSPI;Initial Catalog=EntitiesDb;MultipleActiveResultSets=True" providerName="System.Data.SqlClient" />
When I go to create the MVC scaffolding by doing the following:
Right click on Controllers folder > Add > Controller
Set controller name to be EmailController
Set Template to Controller with read/write actions and views, using Entity Framework
Set Model class to Email(MyService.Entities)
Set Data context class to EntitiesDb (MyService.Entities)
Set Views as Razor
It shows a message of
"Unable to retrieve metadata for 'MyService.Entities.Email'. The type initializer for 'System.Data.Entity.Internal.AppConfig' threw an exception"
The confusing thing is that it worked initially and then I started to add some more entities and Properties into the database and then it wouldn't allow me. I've now scaled right back and it is still doing it.
I have tried a few things after looking on the forums:
Checking the Connection string is correct in MVC Project Adding a
Connection string in the Entities Project
Reinstalled Visual Studio 11 Beta in case any updates have affected it
Checking that the references match (they are both EntityFramework
Version 4.3.1.0)
Dropping the database to rebuild (Now I don't have one ;o))
Is this a bug in VS11? Am I missing a reference to the metadata somewhere?
Any help much appreciated.
EDIT:
By un-installing the EntitiesFramework and re-installing the NuGet package fixed the problem.
Remove this section from webconfig
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
<providers>
</providers>
</entityFramework>

.Net Membership Provider not catching email duplications

I am relatively new to .Net/MVC3, and am working on a C#, MVC3, EF4 application that makes use of the default membership provider. From my reading, it should automatically catch duplicate emails, but it does not seem to do so, and I'm not certain why. What I really need to figure out is where to look to see if the right pieces are in place, and for the reason(s) why it might not do that validation check (it seems to do most/all of the others, like duplicate user names, or invalid password formats, etc. with only duplicate email not getting caught.)
Customizations include adding new users to a specific role, and redirecting to a 'first time' welcome page.
Here is the code:
// POST: /Account/Register
[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 */);
Roles.AddUserToRole(model.UserName, "Registered");
//return RedirectToAction("Index", "Home");
return RedirectToAction("Acceptance", "Account");
}
else
{
ModelState.AddModelError("", ErrorCodeToString(createStatus));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Here are the (untouched) validation methods:
#region Status Codes
private static string ErrorCodeToString(MembershipCreateStatus createStatus)
{
// See http://go.microsoft.com/fwlink/?LinkID=177550 for
// a full list of status codes.
switch (createStatus)
{
case MembershipCreateStatus.DuplicateUserName:
return "User name already exists. Please enter a different user name.";
case MembershipCreateStatus.DuplicateEmail:
return "A user name for that e-mail address already exists. Please enter a different e-mail address.";
etc.
By default, only usernames must be unique. If you want unique email addresses as well, then you must set that in the Web.config entry for the MembershipProvider.
something like
<membership>
<providers>
<add name="AspNetSqlMembershipProvider" [...] requiresUniqueEmail="true" [...] />
</providers>
</membership>
The only thing that comes to mind is making sure that model.Email has a value. Other thing you can check is look at the default membership provider tables in SQL server to check which values are being stored.

AuthorizeRole="Admin" not working in mvc3 app?

I have membership provider setup in an mvc3 application.
I ported it from the local sql express app_data/aspnetdb.mdf to a local server 2008 instance
(this is currently working fine for logins/etc, and [Authorize] SomeMethod()
I recently added 1 new aspnet_roles ("Admin") and 1 new entry in the aspnet_UsersInRoles assoc. w/my username.
Role Table:
ApplicationId RoleId RoleName LoweredRoleName Description
3F96CA96-CCB3-4780-8038-AF3CCE0BD4F2 9B5B798D-E56E-4144-A12C-7C8945FCB413 Admin admin Administrator
UsersInRoles:
UserId RoleId
58974159-E60E-4185-AD00-F8024C7C5974 9B5B798D-E56E-4144-A12C-7C8945FCB413
Q1: Why does the following code not let me into this controller action?
[Authorize]
[Authorize(Roles = "Admin")] // !!! This is keeping me out...commented out I can get in but I'm set as "Admin" and have tried lower case "admin" as well.
public ActionResult SomeMethod()
{}
Q1a: Is there another a role provider connection string that I have to setup in the web config to get this to work?
Can you please post parts of your web.config regarding membership and role providers. This sounds like your application name isn't set properly.
<roleManager enabled="true">
<providers>
<clear/>
<add name="AspNetSqlRoleProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="ApplicationServices" applicationName="Your Application Name here" />
</providers>
</roleManager>
Edit
Your application name isn't set. It needs to be set to something and it must match the application name in your database table: aspnet_Applications
If you web config has applicationName="TestApp" /> then your database table also needs to have TestApp in the aspnet_Applications table
Make sure that your ApplicationId is the same you are probably using a different applicationId, check here for more info
Try the below code in order to pin point your problem :
public ActionResult SomeMethod() {
if (!User.IsInRole("Admin"))
throw new SecurityException("User is not an admin.");
}
If you get an exception, it means that there is problem with your Membership Provider or you're not in admin role or your applicationid was configured different as suggested below.
These are some of the possibilities.

Adding Claims-based authorization to MVC 3

I have an MVC app that I would like to add claims-based authorization to. In the near future we will use ADFS2 for federated identity but for now we will used forms auth locally.
Has anyone seen a tutorial or blog post about the best way to use WIF without an external identity provider?
I have seen the following but it is a year old now and I think there should be an easier solution:
http://geekswithblogs.net/shahed/archive/2010/02/05/137795.aspx
You can use WIF in MVC without an STS.
I used the default MVC2 template, but it should work with MVC 3 too.
You need to:
1- Plug WIF 's SessionAuthenticationModule (web.config)
< add name="SessionAuthenticationModule" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
2- Wherever you authenticate your users, create a ClaimsPrincipal, add all required claims and then create a SessionSecurityToken. This is the LogOn Action in the AccountController created by MVC:
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
var cp = new ClaimsPrincipal();
cp.Identities.Add(new ClaimsIdentity());
IClaimsIdentity ci = (cp.Identity as IClaimsIdentity);
ci.Claims.Add(new Claim(ClaimTypes.Name, model.UserName));
SessionSecurityToken sst = FederatedAuthentication
.SessionAuthenticationModule
.CreateSessionSecurityToken(cp,
"MVC Test",
DateTime.
UtcNow,
DateTime.
UtcNow.
AddHours
(1),
true);
FederatedAuthentication.SessionAuthenticationModule.CookieHandler.RequireSsl = false;
FederatedAuthentication.SessionAuthenticationModule.AuthenticateSessionSecurityToken(sst, true);
//FormsService.SignIn(model.UserName, model.RememberMe);
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
I just added the required lines and left everything else the same. So some refactoring might be required.
From there on, your app will now receive a ClaimsPrincipal. All automatically handled by WIF.
The CookieHandler.RequiresSsl = false is only because it's a dev machine and I'm not deploying on IIS. It can be defined in configuration too.
WIF is designed to use a STS so if you don't want to do that, then you essentially have to re-invent the wheel as per the article.
When you move to ADFS, you will pretty much have to re-code everything.
Alternatively, have a look at StarterSTS, This implements the same kind of aspnetdb authentication that you need but allows WIF to do the heavy lifting. Then when you migrate to ADFS, you simply have to run FedUtil against ADFS and it will all work without any major coding changes.
(BTW, there is a MVC version - a later implementation - here).

Why is the HttpContext.Cache count always zero?

I set up a few pages with OutputCache profiles and confirmed that they are being cached by using multiple browsers and requests to retrieve the page with a timestamp which matched across all requests. When I try to enumerate the HttpContect.Cache it is always empty.
Any ideas what is going on here or where I should be going for this information instead?
Update:
It's not client cache because multiple browsers are seeing the same response. Here is a bit of code to explain what's happening.
Web.Config caching settings
<system.web>
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<clear/>
<add name="StaticContent" duration="1200" varyByParam="none"/>
<add name="VaryByParam" duration="1200" varyByParam="*"/>
</outputCacheProfiles>
</outputCacheSettings>
</caching>
...
</system.web>
**Action Method With Caching
[OutputCache(CacheProfile = "StaticContent")]
public ActionResult Index()
{
return View(new CollaborateModel());
}
Code to enumerate the cache, yep it's rough, an this is defined in a controller action method
var sb = new StringBuilder();
foreach (KeyValuePair<string, object> item in HttpContext.Cache)
{
sb.AppendFormat("{0} : {1}<br />", item.Key, item.Value.ToString());
}
ViewData.Add("CacheContents", sb.ToString());
The HttpContext.Cache is where the count is always null, even though the cache seems to be working fine.
That's probably because the page has been cached downstream on the client browser and not on the server.
Instead of using the HttpCache I ended up rolling my own caching model for holding datasets in my Data Access layer. If I was looking up the AD profile of a given username and converting it to a DTO then I just put that profile in a rolling collection of profile DTOs that I would check before polling AD for the information.

Resources