Запрос в Active Directory из MVC приводит к: Попытке получить доступ к незагруженному appdomain. (Исключение из HRESULT: 0x80131014)
У меня проблема с использованием С# на .Net 4 в веб-приложении MVC, где, когда я запрашиваю Active Directory, я часто получаю сообщение об ошибке: Попытка получить доступ к незагруженному appdomain. (Исключение из HRESULT: 0x80131014).
Странная вещь, что она будет работать безупречно какое-то время, а затем она только начнет происходить, а затем просто исчезнет снова.
Я сделал несколько изменений в функции, чтобы заставить ее работать, но все они, похоже, терпят неудачу. Мне интересно, если я что-то делаю неправильно, или если есть лучший способ сделать это.
Вот моя текущая функция, которая будет принимать loginId и PrincipalContext. LoginId может быть либо пользователем DisplayName i.e "John Smith", либо DOMAINNAME\josmi. По умолчанию используются первые 2 буквы их первого имени, а затем первые 3 буквы их фамилии. Там есть чек, если это не так. Эта часть, если она прекрасна.
public List<ADGroup> GetMemberGroups(string loginId, PrincipalContext principalContext, int tries = 0)
{
var result = new List<ADGroup>();
try
{
var samAccountName = "";
if (loginId.Contains(" "))
{
var fName = loginId.Split(Char.Parse(" "))[0].ToLower();
var sName = loginId.Split(Char.Parse(" "))[1].ToLower();
if (sName.Trim().Length == 2)
samAccountName = string.Format("{0}{1}", fName.StartsWith(".") ? fName.Substring(0, 4) : fName.Substring(0, 3), sName.Substring(0, 2));
else
samAccountName = string.Format("{0}{1}", fName.StartsWith(".") ? fName.Substring(0, 3) : fName.Substring(0, 2), sName.Substring(0, 3));
}
else
samAccountName = loginId.Substring(loginId.IndexOf(@"\") + 1);
var authPrincipal = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, samAccountName);
if (authPrincipal == null)
throw new Exception(string.Format("authPrincipal is null for loginId - {0}", loginId));
var firstLevelGroups = authPrincipal.GetGroups();
AddGroups(firstLevelGroups, ref result);
}
catch
{
if (tries > 5)
throw;
tries += 1;
System.Threading.Thread.Sleep(1000);
GetMemberGroups(loginId, principalContext, tries);
}
return result;
}
private void AddGroups(PrincipalSearchResult<Principal> principal, ref List<ADGroup> returnList)
{
foreach (var item in principal)
{
if (item.GetGroups().Count() > 0)
AddGroups(item.GetGroups(), ref returnList);
returnList.Add(new ADGroup(item.SamAccountName, item.Sid.Value));
}
}
Эта функция вызывается следующим образом:
MembershipGroups = ad.GetMemberGroups(user.SamAccountName, new PrincipalContext(ContextType.Domain));
Ошибка, которую я получаю:
System.AppDomainUnloadedException: Попытка получить доступ к разгруженной AppDomain. (Исключение из HRESULT: 0x80131014) в System.StubHelpers.StubHelpers.InternalGetCOMHRExceptionObject(Int32 hr, intPtr pCPCMD, объект pThis) при System.StubHelpers.StubHelpers.GetCOMHRExceptionObject(Int32 hr, intPtr pCPCMD, объект pThis) при System.DirectoryServices.AccountManagement.UnsafeNativeMethods.IADsPathname.Retrieve(Int32 lnFormatType) в System.DirectoryServices.AccountManagement.ADStoreCtx.LoadDomainInfo() в System.DirectoryServices.AccountManagement.ADStoreCtx.get_UserSuppliedServerName() в System.DirectoryServices.AccountManagement.ADDNLinkedAttrSet.BuildPathFromDN(String dn) при System.DirectoryServices.AccountManagement.ADDNLinkedAttrSet.MoveNextPrimaryGroupDN() в System.DirectoryServices.AccountManagement.ADDNLinkedAttrSet.MoveNext() в System.DirectoryServices.AccountManagement.FindResultEnumerator 1.MoveNext()
at
System.DirectoryServices.AccountManagement.FindResultEnumerator
1.System.Collections.IEnumerator.MoveNext()
Ответы
Ответ 1
глядя на отражатель в System.DirectoryServices.AccountУправление внутренним классом "UnsafeNativeMethods" реализовано в собственном коде, поэтому UserSuppliedServerName на один уровень - это все, что я могу продолжать, не глядя на виртуальную машину CLR (откровенно говоря, я не уверен даже, как сделайте это) Кажется, что node не может вернуть свою основную группу, поэтому, возможно, рассмотрите другие реализации, после того, как немного googling ive столкнется с ними, что может помочь
-
Active Directory и вложенные группы это может быть многообещающим heres примером кода.
public IList<string> FindUserGroupsLdap(string username)
{
// setup credentials and connection
var credentials = new NetworkCredential("username", "password", "domain");
var ldapidentifier = new LdapDirectoryIdentifier("server", 389, true, false);
var ldapConn = new LdapConnection(ldapidentifier, credentials);
// retrieving the rootDomainNamingContext, this will make sure we query the absolute root
var getRootRequest = new SearchRequest(string.Empty, "objectClass=*", SearchScope.Base, "rootDomainNamingContext");
var rootResponse = (SearchResponse)ldapConn.SendRequest(getRootRequest);
var rootContext = rootResponse.Entries[0].Attributes["rootDomainNamingContext"][0].ToString();
// retrieve the user
string ldapFilter = string.Format("(&(objectCategory=person)(sAMAccountName={0}))", username);
var getUserRequest = new SearchRequest(rootContext, ldapFilter, SearchScope.Subtree, null);
var userResponse = (SearchResponse)ldapConn.SendRequest(getUserRequest);
// send a new request to retrieve the tokenGroups attribute, we can not do this with our previous request since
// tokenGroups needs SearchScope.Base (dont know why...)
var tokenRequest = new SearchRequest(userResponse.Entries[0].DistinguishedName, "(&(objectCategory=person))", SearchScope.Base, "tokenGroups");
var tokenResponse = (SearchResponse)ldapConn.SendRequest(tokenRequest);
var tokengroups = tokenResponse.Entries[0].Attributes["tokenGroups"].GetValues(typeof(byte[]));
// build query string this query will then look like (|(objectSid=sid)(objectSid=sid2)(objectSid=sid3))
// we need to convert the given bytes to a hexadecimal representation because thats the way they
// sit in ActiveDirectory
var sb = new StringBuilder();
sb.Append("(|");
for (int i = 0; i < tokengroups.Length; i++)
{
var arr = (byte[])tokengroups[i];
sb.AppendFormat("(objectSid={0})", BuildHexString(arr));
}
sb.Append(")");
// send the request with our build query. This will retrieve all groups with the given objectSid
var groupsRequest = new SearchRequest(rootContext, sb.ToString(), SearchScope.Subtree, "sAMAccountName");
var groupsResponse = (SearchResponse)ldapConn.SendRequest(groupsRequest);
// loop trough and get the sAMAccountName (normal, readable name)
var userMemberOfGroups = new List<string>();
foreach (SearchResultEntry entry in groupsResponse.Entries)
userMemberOfGroups.Add(entry.Attributes["sAMAccountName"][0].ToString());
return userMemberOfGroups;
}
private string BuildHexString(byte[] bytes)
{
var sb = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
sb.AppendFormat("\\{0}", bytes[i].ToString("X2"));
return sb.ToString();
}
Это больше для информационных целей
Ответ 2
Эта проблема совпадает с Определите, находится ли пользователь в группе AD для приложения .NET 4.0
Кажется, что ошибка в ADSI была решена с помощью исправления. Windows 7 с пакетом обновления 1 (SP1) и Windows Server 2008 R2 с пакетом обновления 1 (SP1) не включает исправление, поэтому его необходимо будет вручную развернуть на компьютерах разработчиков и в серверных средах.
http://support.microsoft.com/kb/2683913
Ответ 3
Я не знаю, как здесь передается PrincipalContext
, но одна вещь, которую я заметил в своем собственном коде и исследование, когда у меня была эта ошибка, у меня было:
PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Domain);
UserPrincipal oUserPrincipal = UserPrincipal.FindByIdentity(oPrincipalContext , strUserName);
Где strUserName
был какой-то пользователь, т.е. DOMAIN\johndoe
Я вызывал этот код (который был в отдельной функции) и возвращал объект UserPrincipal
как up
и передавал его:
using (PrincipalSearchResult<Principal> result = up.GetGroups())
{
// do something with result, here
}
result
не будет равно NULL, но после того, как я проверил это условие, я проверил, есть ли result.Count() > 0
, и когда он потерпит неудачу (иногда - хотя я мог бы повторно создать условия, когда это произойдет, щелкнув на определенной вкладке в моем приложении, которая вызвала этот код, даже несмотря на то, что один и тот же код вызывался при загрузке моего приложения и не вызывал проблем). Свойством Message
в result
было Attempted to access an unloaded appdomain. (Exception from HRESULT: 0x80131014)
.
Я нашел в аналогичное сообщение этому, что все, что мне нужно было сделать, это указать домен в моем PrincipalContext
. Поскольку я не мог жестко закодировать свой код, поскольку мы перемещаем наш код между средами Dev, Test и Production, где у них есть разные домены для каждого из них, я смог указать его как Environment.UserDomainName
:
PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Domain, Environment.UserDomainName);
Это избавило меня от этой ошибки.
Ответ 4
Вы можете внести некоторые записи, чтобы сузить проблему. Этот Thread.Sleep
не похож на что-то, что нужно в веб-приложении:)
Если вы получаете исключения, возможно, вы можете обрабатывать их по-разному.
Я считаю, что ваш AppDomain перерабатывается, пока AD делает свой вуду. Добавление журнала в Application_End
также может дать некоторые подсказки.
Ответ 5
попробовать
public List<ADGroup> GetMemberGroups(string loginId, PrincipalContext principalContext, int tries = 0)
{
var result = new List<ADGroup>();
bool Done = false;
try
{
var samAccountName = "";
if (loginId.Contains(" "))
{
var fName = loginId.Split(Char.Parse(" "))[0].ToLower();
var sName = loginId.Split(Char.Parse(" "))[1].ToLower();
if (sName.Trim().Length == 2)
samAccountName = string.Format("{0}{1}", fName.StartsWith(".") ? fName.Substring(0, 4) : fName.Substring(0, 3), sName.Substring(0, 2));
else
samAccountName = string.Format("{0}{1}", fName.StartsWith(".") ? fName.Substring(0, 3) : fName.Substring(0, 2), sName.Substring(0, 3));
}
else
samAccountName = loginId.Substring(loginId.IndexOf(@"\") + 1);
var authPrincipal = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, samAccountName);
if (authPrincipal == null)
throw new Exception(string.Format("authPrincipal is null for loginId - {0}", loginId));
var firstLevelGroups = authPrincipal.GetGroups();
AddGroups(firstLevelGroups, ref result);
Done = true;
}
catch
{
if (tries > 5)
throw;
tries += 1;
}
if ( ( !Done) && (tries < 6) )
{
System.Threading.Thread.Sleep(1000);
result = GetMemberGroups(loginId, principalContext, tries);
}
return result;
}
private void AddGroups(PrincipalSearchResult<Principal> principal, ref List<ADGroup> returnList)
{
if ( principal == null )
return;
foreach (var item in principal)
{
if (item.GetGroups().Count() > 0)
AddGroups(item.GetGroups(), ref returnList);
returnList.Add(new ADGroup(item.SamAccountName, item.Sid.Value));
}
}
Когда происходит исключение, вы снова вызывали функцию из блока catch (в зависимости от значения попыток), но отбрасывали ее возвращаемое значение - так что даже если работа второй/третьей... вы вернули пустой результат оригинальный вызывающий.
Я изменил это, поэтому результат больше не будет отбрасываться...
Во второй функции вы никогда не проверяли главный параметр для null перед запуском foreach... Я тоже изменил это...
И я удалил рекурсию из catch catch catch (хотя я действительно не уверен, имеет ли это изменение какой-либо реальный эффект).