I've been running into an error on one of my applications that happens a few times a month but has occurred twice this week. When this happens, it's always first thing in the morning when the first user loads the app and begins working (web application, 3-4 internal users) The error originates from this very simple method and once if fails, it will not work until I restart the application pool. Now, I'm querying AD in other ways as well but this is the first AD related method that's called when the users begin work in the morning.
public DomainUser GetDomainUser(string userLoginName)
{
using (PrincipalContext context = new PrincipalContext(ContextType.Domain, this.DomainName))
{
using (UserPrincipal user = UserPrincipal.FindByIdentity(context, userLoginName))
{
// If user is null, the result is not a UserPrinciple
if (user != null)
{
string firstName = user.GivenName;
string middleName = user.MiddleName;
string lastName = user.Surname;
int empId = Convert.ToInt32(user.EmployeeId);
string emailAddr = user.EmailAddress;
string userName = user.SamAccountName;
DateTime? accountExp = user.AccountExpirationDate;
return new DomainUser
{
FirstName = firstName,
MiddleName = middleName,
LastName = lastName,
EmployeeId = empId,
Email = emailAddr,
UserName = userName,
AccountExpiration = accountExp
};
}
return null;
}
}
}
So this question is closely related but my permissions are setup correctly and the code works 99% of the time and will continue to run after an application pool restart.
Stack trace looks something like this:
System.Runtime.InteropServices.COMException (0x80005000): Unknown error (0x80005000)
at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
at System.DirectoryServices.DirectoryEntry.Bind()
at System.DirectoryServices.DirectoryEntry.get_AdsObject()
at System.DirectoryServices.PropertyValueCollection.PopulateList()
at System.DirectoryServices.PropertyValueCollection..ctor(DirectoryEntry entry, String propertyName)
at System.DirectoryServices.PropertyCollection.get_Item(String propertyName)
at System.DirectoryServices.AccountManagement.PrincipalContext.DoLDAPDirectoryInitNoContainer()
at System.DirectoryServices.AccountManagement.PrincipalContext.DoDomainInit()
at System.DirectoryServices.AccountManagement.PrincipalContext.Initialize()
at System.DirectoryServices.AccountManagement.PrincipalContext.get_QueryCtx()
at System.DirectoryServices.AccountManagement.Principal.FindByIdentityWithTypeHelper(PrincipalContext context, Type principalType, Nullable`1 identityType, String identityValue, DateTime refDate)
at System.DirectoryServices.AccountManagement.Principal.FindByIdentityWithType(PrincipalContext context, Type principalType, String identityValue)
at System.DirectoryServices.AccountManagement.UserPrincipal.FindByIdentity(PrincipalContext context, String identityValue)
at ADWrapper.AdSearch.GetDomainUser(String userLoginName)
What could the problem be? Memory leaks? The common pattern is that this happens first thing in the morning when the first user begins using the app.
We had a similar issue. Here was the solution provided by Microsoft. I hope this helps someone.
The DirectoryEntry.Bind function eventually calls into ADsOpenObject (https://learn.microsoft.com/en-us/windows/win32/api/adshlp/nf-adshlp-adsopenobject)
This function has a “router”. The initialization of the router enumerates providers from the registry, such as the “LdapNamespace”. This is located at HKEY_CLASSES_ROOT\CLSID{228D9A82-C302-11cf-9AA4-00AA004A5691}\ProgID.
The other providers, such as WinNT namespace are also enumerated.
In the trace, an error is returned when looking up these registry keys. The error is,
ERROR_KEY_DELETED
1018 (0x3FA)
Illegal operation attempted on a registry key that has been marked for deletion.
This error can be caused by an unload of the user profile that the process is using for its identity.
The Windows User Profile Service forcefully unloads user profiles. This causes problems with the process.
I have seen this with w3wp.exe and dllhost.exe, where the registry profile is unloaded before the process is done.
Here’s a blog we did on the issue for dllhost.exe: https://blogs.msdn.microsoft.com/distributedservices/2009/11/06/a-com-application-may-stop-working-on-windows-server-2008-when-the-identity-user-logs-off/
You may see warnings in the application log with descriptions like this:
Windows detected your registry file is still in use by other applications or services. The file will be unloaded now. The applications or services that hold your registry file may not function properly afterwards.
I think we should try the resolution/workaround in the blog:
Resolution
As a workaround it may be necessary to disable this feature which is the default behavior. The policy setting 'Do not forcefully unload the user registry at user logoff' counters the default behavior of Windows 2008. When enabled, Windows 2008 does not forcefully unload the registry and waits until no other processes are using the user registry before it unloads it.
The policy can be found in the group policy editor (gpedit.msc)
Computer Configuration->Administrative Templates->System-> UserProfiles
Do not forcefully unload the user registry at user logoff
Change the setting from “Not Configured” to “Enabled”, which disables the new User Profile Service feature.
There should not be any side effects from this change.
I was struggling with absolutely same issue for a long time. My solution was actually install feature IIS 6 Metabase Compatiblity. After that no problems. Some articles which helped me are below
http://blogs.msdn.com/b/jpsanders/archive/2009/05/13/iis-7-adsi-error-system-runtime-interopservices-comexception-0x80005000-unknown-error-0x80005000.aspx
http://michaelwasham.com/2011/04/25/annoying-error-using-system-directoryservices-and-iis-app-pools/
Related
Why does the code below work fine when I run my web application localhost but not when I install it to an IIS server?
using (HostingEnvironment.Impersonate())
{
UserPrincipal activeUser = UserPrincipal.Current;
String activeUserSid = activeUser.Sid.ToString();
String activeUserUPN = activeUser.UserPrincipalName;
}
Please don't suggest I stick with HttpContext.Current.User as it doesn't provide access to SID or UPN without additional calls to Active Directory.
The web application will be used by Windows authenticated users from three separate domains, the web server is hosted in a fourth domain. The Application Pool is configured to run under the NetworkService identity and the web app configuration has identity impersonation set to true.
The error message when it runs on IIS is:
Error in Page_Load(): UserPrincipal.Current.
System.InvalidCastException: Unable to cast object of type
'System.DirectoryServices.AccountManagement.GroupPrincipal' to type
'System.DirectoryServices.AccountManagement.UserPrincipal'.
at System.DirectoryServices.AccountManagement.UserPrincipal.FindByIdentity(PrincipalContext
context, IdentityType identityType, String identityValue)
at System.DirectoryServices.AccountManagement.UserPrincipal.get_Current()
at webapp.Details.Default.Page_Load(Object sender, EventArgs e)
EDIT:
Tried both the following and unfortunately get the same error.
UserPrincipal userPrincipal = UserPrincipal.Current;
Response.Write(userPrincipal.Name);
Principal userOrGroup = UserPrincipal.Current;
Response.Write(userOrGroup.Name);
I had a lot of issues deploying UserPrincipal.Current and still don't fully understand why.
I finally ended up using PrincipalSearcher, and created the following function to do what I thought UserPrincipal.Current was doing.
private UserPrincipal GetActiveDirectoryUser(string userName)
{
using(var ctx = new PrincipalContext(ContextType.Domain))
using(var user = new UserPrincipal(ctx) { SamAccountName = userName})
using(var searcher = new PrincipalSearcher(user))
{
return searcher.FindOne() as UserPrincipal;
}
}
And I passed System.Web.HttpContext.Current.User.Identity.Name into that method as the userName.
It seems like need some other method to determine user.
Here description from msdn for property:
"Gets a user principal object that represents the current user under which the thread is running."
So, UserPrincipal.Current returns user under what IIS running.
http://msdn.microsoft.com/en-us/library/system.directoryservices.accountmanagement.userprincipal.aspx
Yes, its because you are disposing of the returned UserPrincipal object due to the multiple using statements. Remove 'ctx' from the using statement, then it becomes the callers responsibility to dispose of the returned object.
I'm relatively new to the MVC world, and am developing an MVC3, C# .Net, EF4 application that uses the default Microsoft membership provider. There are requirements to have some additional fields appended to the membership provider tables, which help make up an extended profile for each user. (The table that holds this data is called Profile, and it has a PK of ProfileId INT and also holds an FK of the UserId from aspnet_Users/aspnet_Membership.)
I want to offer new users the chance to immediately fill out their profile after registration, and so I'm redirecting them to a page where they can enter the values we want for the Profile table right away. Registration works fine. The user is created, as is the Profile record in the database.
Where things go wrong is when I get the user to that Profile editing page, and I try to pull up their data. I'm going into System.Web.Security.Membership.GetUser().ProviderUserKey to get the UserId, and then finding their Profile record based on that.
This did work, and I thought I had it figured out, but I must have made a mistake somewhere and now cannot get it to work correctly. The error message is fairly generic and seems to indicate that you can't find the ProfileId INT value by using the UserId's GUID value.
// GET: /Profile/Entry/
public ActionResult Entry()
{
Guid currentUser = (Guid)System.Web.Security.Membership.GetUser().ProviderUserKey;
Profile profile = db.Profiles.Find(currentUser);
ViewBag.UserId = new SelectList(db.aspnet_Users, "UserId", "UserName", profile.UserId);
return View(profile);
}
Now the error message:
Server Error in '/' Application.
--------------------------------------------------------------------------------
The argument types 'Edm.Int32' and 'Edm.Guid' are incompatible for this operation. Near WHERE predicate, line 1, column 76.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Data.EntitySqlException: The argument types 'Edm.Int32' and 'Edm.Guid' are incompatible for this operation. Near WHERE predicate, line 1, column 76.
Source Error:
Line 127: {
Line 128: Guid currentUser = ( Guid)System.Web.Security.Membership.GetUser().ProviderUserKey;
Line 129: Profile profile = db.Profiles.Find(currentUser);
Line 130: ViewBag.UserId = new SelectList(db.aspnet_Users, "UserId", "UserName", profile.UserId);
Line 131: return View(profile);
Any ideas on how that db.Profiles.Find(currentUser) can be specifically targeted to find the UserId GUID value even though the default/assumed Id column is ProfileId INT?
Find searches a table based on its primary key, which in your case is an int and is not related to the Membership Provider Key (which is a guid). So you cannot use Find.
Instead, use a Linq query, such as this:
Profile profile = db.Profiles.Where(x => x.AspMembershipUserID == currentUser).FirstOrDefault();
I am creating asp.net MVC Application using MVC 3.0. I have 2 users but the DataBase is the same. So, Is it possible to setup two connection strings or even more in web.config? when user login, I redirect it to his database, so then he can use his DataBase.
So major issue here is to find out which user is logged in and use connection string for that user.
I am using default mvc account controller and for example when i want to display welcome message for user in my view i type: if (#User.Identity.Name == "UserName") then some message
So where is the best place to find out which user is logged in and set his connection string in controller or in a view?
Yes, you can have as many connection strings in your web.config file as you want.
But, if you're designing a multi-tenant application than there are better ways of doing it than adding a connection string to web.config file every time a new user signs up.
Probably the best way for you is to have a single database where user-related tables have foreign keys to Users table.
You can learn more about multi-tenant architectures from this Microsoft article.
I agree with Jakub's answer: there are better ways of handling multi-tenancy than having a different database per user.
However, to answer your specific question, there are two options that come to mind:
You can set the connection string to a session variable immediately after login.
Your data access layer can choose the connection string based on the logged in user when it's created. (I'd recommend this over the first option)
To store the connection after login, if you're using the standard ASP.NET MVC Account Controller, look at the LogOn post action:
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
//EXAMPLE OF WHERE YOU COULD STORE THE CONNECTION STRING
Session["userConnectionString"] = SomeClass.GetConnectionStringForUser(model.UserName);
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
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);
}
If you wanted to select the connection string when doing data access, your repository or data access layer will probably have a system for handling that. For instance with Entity Framework Code First, the DbContext constructor allows you to pass in the name of a connection string when you're creating it:
connectionString = SomeClass.GetConnectionStringForUser(model.UserName);
DbContext context = new DbContext(connectionString);
But again, I'd look at other ways of handling multitenancy unless your business dictates that your users have physically separate databases.
you can have multiple connection strings in web.config. Now if you want to use different connection string for different users there must be some criteria for division of users
<appSettings><add key="connectionString" value="Data Source=develope\sqlexpress;Initial Catalog=validation_tdsl;Integrated Security=True;Max Pool Size=1000;Connect Timeout=60000;"></add>
<add key="connectionString1" value="server=MARK\SQLEXPRESS;database=name;integrated security=true;Max Pool Size=1000;Connect Timeout=60000;"></add>
<add key="connectionString2" value="server=name\SQLEXPRESS;database=FM;integrated security=true;Max Pool Size=1000;Connect Timeout=60000;"></add>
and later you can use them like following
Dim con As New SqlConnection(System.Configuration.ConfigurationSettings.AppSettings("connectionString"))
Dim con1 As New SqlConnection(System.Configuration.ConfigurationSettings.AppSettings("connectionString1"))
EDIT : In c# it would be:
SqlConnection con = new SqlConnection(System.Configuration.ConfigurationManager.AppSettings["connectionString"]);
SqlConnection con1 = new SqlConnection(System.Configuration.ConfigurationManager.AppSettings["connectionString1"])
Note: ConfigurationSettings is now obsolete.
Much of my application uses complied queries to retrieve data. In these queries I'll often refer to the current user. I'm noticing that if a user, B, logs in after another user, A, then user B will see user A's information.
I have queries much like this all through out the application
public static Func<DataContext, MyRecord> CurrentUserRecords =
CompiledQuery.Compile<DataContext, MyRecord>(
(DataContext db) =>
(from r in db.MyRecords
where
r.User == User.Current
select r).SingleOrDefault());
User.Current is a static property that changes depending on who's logged in.
public static User Current
{
get { return MyBase<User>.Get((int)(HttpContext.Current.Session["CurrentUserID"] ?? 0)); }
}
When I login for the first time with User A, the above compiled query returns User A's records. It follows that User.Current also returns the proper reference to User A. However, when I log in as User B, the above compiled query still returns User A's records, despite the fact that User.Current is returning a reference to User B.
I ran Profiler for SQL Server, and noticed when the compiled query was executed the generated TSQL referenced User A's ID both times.
So my question is this:
Do compiled queries somehow cache?
If so, what is there life span, and can I control it?
Is referencing a "current user" in a compiled query bad design for an ASP.net application?
Thanks all!
You need to allow a string parameter in the compiled query. Otherwise it will resolve the string's value during .Compile(). Try this:
public static Func<DataContext, string, MyRecord> UserRecordByParam =
CompiledQuery.Compile<DataContext, string, MyRecord>
(
(DataContext db, string UserName) =>
db.MyRecords.Where( r => r.User == UserName ).SingleOrDefault()
);
public static Func<DataContext, MyRecord> CurrentUserRecord =
(DataContext db) => UserRecordByParam(db, User.Current);
i try to put a lock to a static string object to access to cache,, the lock() block executes in my local,but whenever i deploy it to the server, it locks forever. i write every single step to event log to see the process and lock(object) just causes the deadlock on the server. The command right after lock() is never executed as the i dont see an entry in the event log.
below is the code:
public static string CacheSyncObject = "CacheSync";
public static DataView GetUsers()
{
DataTable dtUsers = null;
if (HttpContext.Current.Cache["dtUsers"] != null)
{
Global.eventLogger.Write(String.Format("GetUsers() cache hit: {0}",dtUsers.Rows.Count));
return (HttpContext.Current.Cache["dtUsers"] as DataTable).Copy().DefaultView;
}
Global.eventLogger.Write("GetUsers() cache miss");
lock (CacheSyncObject)
{
Global.eventLogger.Write("GetUsers() locked SyncObject");
if (HttpContext.Current.Cache["dtUsers"] != null)
{
Global.eventLogger.Write("GetUsers() opps, another thread filled the cache, release lock");
return (HttpContext.Current.Cache["dtUsers"] as DataTable).Copy().DefaultView;
}
Global.eventLogger.Write("GetUsers() locked SyncObject"); ==> this is never written to the log, so which means to me that, lock() never executes.
You're locking on a string, which is a generally bad idea in .NET due to interning. The .NET runtime actually stores all identical literal strings only once, so you have little control over who sees a specific string.
I'm not sure how the ASP.NET runtime handles this, but the regular .NET runtime actually uses interning for the entire process which means that interned strings are shared even between different AppDomains. Thus you could be deadlocking between different instances of you method.
What happens if you use:
public static object CacheSyncObject = new object();