Tentative d'accès à un appdomain déchargé lors de L'utilisation du système.DirectoryServices

Nous avons implémenté un fournisseur D'adhésion qui s'authentifie auprès D'Active Directory et qui utilise System.DirectoryServices. Lors de l'utilisation de ce fournisseur D'adhésion dans un ASP.Net application MVC 3 sur Visual Studio 2010 avec webdev server, nous obtenons parfois (1 fois sur 6) une exception lors de la connexion à l'application.

System.IO.FileNotFoundException: Could not load file or assembly 'System.Web' or one of its dependencies. The system cannot find the file specified.
File name: 'System.Web' 
at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
at System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
at System.Reflection.RuntimeAssembly.LoadWithPartialNameInternal(AssemblyName an, Evidence securityEvidence, StackCrawlMark& stackMark)
at System.DirectoryServices.AccountManagement.UnsafeNativeMethods.IADsPathname.Retrieve(Int32 lnFormatType)
at System.DirectoryServices.AccountManagement.ADStoreCtx.LoadDomainInfo()
at System.DirectoryServices.AccountManagement.ADStoreCtx.get_DnsDomainName()
at System.DirectoryServices.AccountManagement.ADStoreCtx.GetGroupsMemberOfAZ(Principal p)
at System.DirectoryServices.AccountManagement.UserPrincipal.GetAuthorizationGroupsHelper()
at System.DirectoryServices.AccountManagement.UserPrincipal.GetAuthorizationGroups()

=== Pre-bind state information ===
LOG: DisplayName = System.Web (Partial)
WRN: Partial binding information was supplied for an assembly:
WRN: Assembly Name: System.Web | Domain ID: 2
WRN: A partial bind occurs when only part of the assembly display name is provided.
WRN: This might result in the binder loading an incorrect assembly.
WRN: It is recommended to provide a fully specified textual identity for the assembly,
WRN: that consists of the simple name, version, culture, and public key token.
WRN: See whitepaper http://go.microsoft.com/fwlink/?LinkId=109270 for more information and common solutions to this issue.
Calling assembly : HibernatingRhinos.Profiler.Appender, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0774796e73ebf640.

L'ensemble appelant était HibernatingRhinos.Profiler.Appender donc après avoir désactivé le profileur dans log4net config nous sommes arrivés au réel exception:

System.AppDomainUnloadedException: Attempted to access an unloaded appdomain. (Except   at System.StubHelpers.StubHelpers.InternalGetCOMHRExceptionObject(Int32 hr, IntPtr pCPCMD, Object pThis)
at System.StubHelpers.StubHelpers.GetCOMHRExceptionObject(Int32 hr, IntPtr pCPCMD, Object pThis)
at System.DirectoryServices.AccountManagement.UnsafeNativeMethods.IADsPathname.Retrieve(Int32 lnFormatType)
at System.DirectoryServices.AccountManagement.ADStoreCtx.LoadDomainInfo()
at System.DirectoryServices.AccountManagement.ADStoreCtx.get_DnsDomainName()
at System.DirectoryServices.AccountManagement.ADStoreCtx.GetGroupsMemberOfAZ(Principal p)
at System.DirectoryServices.AccountManagement.UserPrincipal.GetAuthorizationGroupsHelper()
at System.DirectoryServices.AccountManagement.UserPrincipal.GetAuthorizationGroups()

L'exception est toujours lancée à la même méthode, mais pour l'instant nous ne sommes pas en mesure de la reproduire comme cela se produit au hasard, mais environ 1 fois sur 6. Nous n'obtenons cependant pas l'exception lors de l'utilisation D'IIs au lieu du serveur Web Visual Studio 2010 intégré.

Cela a probablement quelque chose à voir avec les conditions de course lors de l'utilisation de plusieurs appdomains dans le contexte de Visual Studio webdev, mais ce n'est que deviner. Nous aimerions vraiment savoir quelle est la cause du problème car nous ne voulons pas avoir ces exceptions dans un environnement de production.

Nous avons trouvé 2 cas similaires mais personne n'a trouvé de solution réelle:

Http://our.umbraco.org/forum/developers/extending-umbraco/19581-Problem-with-custom-membership-and-role-provider

Http://forums.asp.net/t/1556949.aspx/1

Mise à Jour 18-05-2011

La plus petite quantité de code (en asp.net mvc) pour reproduire l'exception, où userName est votre nom de connexion Active Directory.

using System.DirectoryServices.AccountManagement;
using System.Web.Mvc;

namespace ADBug.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            string userName = "nickvane";
            var principalContext = new PrincipalContext(ContextType.Domain);

            UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(
                principalContext,
                IdentityType.SamAccountName,
                userName);

            if (userPrincipal != null)
            {
                PrincipalSearchResult<Principal> list = userPrincipal.GetAuthorizationGroups();
            }

            return View();
        }
    }
}

Hélas, l'exception se produit toujours au hasard, donc pas de bug entièrement reproductible.

61
demandé sur nickvane 2011-05-05 13:03:26

6 réponses

Voici ce qui fonctionne pour moi (. Net 4):

Au lieu de ceci:

principalContext = new PrincipalContext(ContextType.Domain)

Créez également le contexte principal avec la chaîne de domaine:

Par exemple

principalContext = new PrincipalContext(ContextType.Domain,"MYDOMAIN")

Il devrait être fixé dans 4.5. Voir commentaire, n'a pas encore été corrigé, mais l'ajout du deuxième argument fonctionne toujours comme solution de contournement.

83
répondu TofuMaster 2015-06-11 03:32:11

Nous l'avons résolu dans le code en réessayant L'appel à Getautorizationgroups mais avec un sleep entre les deux. Il résout notre problème, mais je ne suis pas tout à fait heureux avec elle.

private PrincipalSearchResult<Principal> GetAuthorizationGroups(UserPrincipal userPrincipal, int tries)
{
    try
    {
        return userPrincipal.GetAuthorizationGroups();
    }
    catch (AppDomainUnloadedException ex)
    {
        if (tries > 5)
        {
            throw;
        }
        tries += 1;
        Thread.Sleep(1000);
        return GetAuthorizationGroups(userPrincipal, tries);
    }
}

Si nous obtenons l'exception, alors 1 nouvelle tentative est apparemment suffisante.

1
répondu nickvane 2011-05-18 12:42:42

Cette solution est vraiment lente, et lorsque, par exemple, lorsque vous l'utilisez dans une application web, Getautorizationgroups est appelé très souvent, ce qui rend le site très lent. J'ai travaillé implémenté la mise en cache som à la place, ce qui rend beaucoup plus rapide après la première fois. Je suis également en train de réessayer, car l'exception se produit toujours.

D'abord, je remplace la méthode GetRolesForUser et implémente la mise en cache.

    public override string[] GetRolesForUser(string username)
    {
        // List of Windows groups for the given user.
        string[] roles;

        // Create a key for the requested user.
        string cacheKey = username + ":" + ApplicationName;

        // Get the cache for the current HTTP request.
        Cache cache = HttpContext.Current.Cache;
        // Attempt to fetch the list of roles from the cache.
        roles = cache[cacheKey] as string[];
        // If the list is not in the cache we will need to request it.
        if (null == roles)
        {
            // Allow the base implementation to load the list of roles.
            roles = GetRolesFromActiveDirectory(username);
            // Add the resulting list to the cache.
            cache.Insert(cacheKey, roles, null, Cache.NoAbsoluteExpiration,
                Cache.NoSlidingExpiration);
        }

        // Return the resulting list of roles.
        return roles;
    }

Le GetRolesFromActiveDirectory ressemble à ceci.

    public String[] GetRolesFromActiveDirectory(String username)
    {            
        // If SQL Caching is enabled, try to pull a cached value.);));
        if (_EnableSqlCache)
        {
            String CachedValue;
            CachedValue = GetCacheItem('U', username);
            if (CachedValue != "*NotCached")
            {
                return CachedValue.Split(',');
            }
        }

        ArrayList results = new ArrayList();
        using (PrincipalContext context = new PrincipalContext(ContextType.Domain, null, _DomainDN))
        {
            try
            {                    
                UserPrincipal p = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, username);

                var tries = 0;
                var groups = GetAuthorizationGroups(p, tries);

                foreach (GroupPrincipal group in groups)
                {
                    if (!_GroupsToIgnore.Contains(group.SamAccountName))
                    {
                        if (_IsAdditiveGroupMode)
                        {
                            if (_GroupsToUse.Contains(group.SamAccountName))
                            {
                                results.Add(group.SamAccountName);
                            }
                        }
                        else
                        {
                            results.Add(group.SamAccountName);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                throw new ProviderException("Unable to query Active Directory.", ex);
            }
        }
        // If SQL Caching is enabled, send value to cache
        if (_EnableSqlCache)
        {
            SetCacheItem('U', username, ArrayListToCSString(results));
        }

        return results.ToArray(typeof(String)) as String[];
    }

Le Dernier la méthode est Getathorizationgroups et cela ressemble à ceci.

    private PrincipalSearchResult<Principal> GetAuthorizationGroups(UserPrincipal userPrincipal, int tries)
    {
        try
        {
            return userPrincipal.GetAuthorizationGroups();
        }
        catch(FileNotFoundException ex)
        {
            if (tries > 5) throw;

            tries++;
            Thread.Sleep(1000);

            return GetAuthorizationGroups(userPrincipal, tries);
        }
        catch (AppDomainUnloadedException ex)
        {
            if (tries > 5) throw;

            tries++;
            Thread.Sleep(1000);

            return GetAuthorizationGroups(userPrincipal, tries);
        }
    }

J'ai découvert que la mise en cache des rôles le rend beaucoup plus rapide. Espérons que cela aide quelqu'un. Acclamation.

1
répondu noshitsherlock 2011-07-12 09:01:09

J'ai rencontré le même problème lors de l'utilisation de ActiveDirectoryMembershipProvider. Pour moi, cela se passait quand j'ai appelé L'adhésion.ValidateUser() pour la première fois et le framework essayait de créer le fournisseur.

J'ai remarqué que mon ordinateur de développement temporaire N'avait pas Visual Studio 2010 SP1 installé donc je l'ai installé et cela a résolu le problème pour moi.

1
répondu Magnus Lindhe 2012-01-12 14:38:27

J'ai eu le même problème, et j'ai trouvé la réponse dans ce poste travaille. Semble être un problème avec le constructeur PrincipalContext qui ne prend Qu'un ContextType comme paramètre. Je sais que ce post est vieux, mais je pensais le lier pour n'importe qui dans le futur:)

1
répondu BlackBeak 2017-05-23 11:54:53

Accédez à la section Propriétés du projet / onglet web / serveurs et cochez la case Authentification NTML.

Ceci est requis pour que Cassini (VS Development Server) utilise L'authentification Windows.

-1
répondu user1230898 2012-02-24 14:09:25