Оптимизация сериализации данных Active Directory для web-api

#c# #asp.net #xml #rest #active-directory

#c# #asp.net #xml #rest #active-directory

Вопрос:

я создал API Active Directory, который экспортирует данные в формате Rest.

Хотя сам по себе код работает очень медленно, я попробовал несколько способов извлечения данных из нашего активного каталога, но скорость процесса практически не изменилась.

В текущем виде на 1000 пользователей требуется почти 15 секунд, чтобы получить их и сериализовать их свойства, а затем вернуть данные.

Итак, мой вопрос в том, может ли кто-нибудь помочь мне выяснить, как оптимизировать этот процесс и сократить время загрузки. Единственное решение, которое я могу придумать прямо сейчас, — это хранить данные непосредственно в оперативной памяти серверов и использовать потоки для их обновления в интервалах.

Вот как это выглядит сейчас:

Класс, который выбирает пользователей:

         public List<ADUserDetail> GetUserFromGroup(String groupName)
    {

        List<ADUserDetail> userlist = new List<ADUserDetail>();
        try
        {
            var context = new PrincipalContext(
                                ContextType.Domain,
                                "domain", @"username", "password");

            using (var group = GroupPrincipal.FindByIdentity(context, groupName))
            {
                var users = group.GetMembers(true);
                int i = 1;

                foreach (UserPrincipal user in users)
                {
                    ADUserDetail userobj = ADUserDetail.GetProp(user);
                    Debug.WriteLine(i   " "   userobj.sAMAccountName);
                    i  ;

                    userlist.Add(userobj);
                }
            }

            return userlist;
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex);
            return userlist;
        }
    }
  

Итак, как вы можете сказать, этот метод захватывает всех пользователей из группы и создает объект ADUserDetail для каждого из них, в конечном итоге возвращая список этих объектов.

Я пробовал разные методы здесь, используя Directorysearcher, но время отклика было почти идентичным UserPrincipal.

ADUserDetail выглядит следующим образом:

         public ADUserDetail()
    {
        _Groups = new List<string>();
    }

    public static ADUserDetail GetProp(UserPrincipal directoryUser)
    {
        return new ADUserDetail(directoryUser);
    }
    private static String GetProperty(UserPrincipal userDetail, String propertyName)
    {
        if (userDetail.GetProperty(propertyName) != null)
        {
            return userDetail.GetProperty(propertyName);
        }
        else
        {
            return string.Empty;
        }
    }
private ADUserDetail(UserPrincipal directoryUser)
    {
        String domainAddress;
        String domainName;

        _Groups = new List<string>();

        var groups = directoryUser.GetGroups();
        IEnumerable<string> groupNames = groups.Select(x => x.SamAccountName);

        foreach (string name in groupNames)
        {
            _Groups.Add(name);
        }

        _firstName = GetProperty(directoryUser, ADProperties.FIRSTNAME);
        _middleName = GetProperty(directoryUser, ADProperties.MIDDLENAME);
        _lastName = GetProperty(directoryUser, ADProperties.LASTNAME);
        _sAMAccountName = GetProperty(directoryUser, ADProperties.SAMACCOUNTNAME);
        String userPrincipalName = GetProperty(directoryUser, ADProperties.USERPRINCIPALNAME);

        if (!string.IsNullOrEmpty(userPrincipalName))
        {
            domainAddress = userPrincipalName.Split('@')[1];
        }
        else
        {
            domainAddress = String.Empty;
        }

        if (!string.IsNullOrEmpty(domainAddress))
        {
            domainName = domainAddress.Split('.').First();
        }
        else
        {
            domainName = String.Empty;
        }
        _streetAddress = GetProperty(directoryUser, ADProperties.STREETADDRESS);
        _city = GetProperty(directoryUser, ADProperties.CITY);
        _state = GetProperty(directoryUser, ADProperties.STATE);
        _postalCode = GetProperty(directoryUser, ADProperties.POSTALCODE);
        _country = GetProperty(directoryUser, ADProperties.COUNTRY);
        _company = GetProperty(directoryUser, ADProperties.COMPANY);
        _department = GetProperty(directoryUser, ADProperties.DEPARTMENT);
        _homePhone = GetProperty(directoryUser, ADProperties.HOMEPHONE);
        _extension = GetProperty(directoryUser, ADProperties.EXTENSION);
        _mobile = GetProperty(directoryUser, ADProperties.MOBILE);
        _fax = GetProperty(directoryUser, ADProperties.FAX);
        _emailAddress = GetProperty(directoryUser, ADProperties.EMAILADDRESS);
        _title = GetProperty(directoryUser, ADProperties.TITLE);
        _manager = GetProperty(directoryUser, ADProperties.MANAGER);
        _adminDescription = GetProperty(directoryUser, ADProperties.ADMINDESCRIPTION);
        _cn = GetProperty(directoryUser, ADProperties.CONTAINERNAME);
        _company = GetProperty(directoryUser, ADProperties.COMPANY);
        _department = GetProperty(directoryUser, ADProperties.DEPARTMENT);
        _displayName = GetProperty(directoryUser, ADProperties.DISPLAYNAME);
        _distinguishedName = GetProperty(directoryUser, ADProperties.DISTINGUISHEDNAME);
        _homeDirectory = GetProperty(directoryUser, ADProperties.HOMEDIRECTORY);
        _homeDrive = GetProperty(directoryUser, ADProperties.HOMEDRIVE);
        _homeMDB = GetProperty(directoryUser, ADProperties.HOMEMDB);
        _homeMTA = GetProperty(directoryUser, ADProperties.HOMEMTA);
        _info = GetProperty(directoryUser, ADProperties.INFO);
        _mail = GetProperty(directoryUser, ADProperties.EMAILADDRESS);
        _mailNickname = GetProperty(directoryUser, ADProperties.MAILNICKNAME);
        _manager = GetProperty(directoryUser, ADProperties.MANAGER);
        _mDBUseDefaults = GetProperty(directoryUser, ADProperties.MDBUSEDEFAULTS);
        _mobile = GetProperty(directoryUser, ADProperties.MOBILE);
        _name = GetProperty(directoryUser, ADProperties.NAME);
        _neEmployeeNumber = GetProperty(directoryUser, ADProperties.NEEMPLOYEENUMBER);
        _neEdirDn = GetProperty(directoryUser, ADProperties.NEEDIRDN);
        _objectCategory = GetProperty(directoryUser, ADProperties.OBJECTCATEGORY);
        _objectClass =  "Placeholder";;
        _primaryGroupID = GetProperty(directoryUser, ADProperties.PRIMARYGROUPID);
        _proxyAddresses = "Placeholder"; ;
        _sAMAccountType = GetProperty(directoryUser, ADProperties.SAMACCOUNTTYPE);
        _showInAddressBook = "Placeholder"; ;
        _streetAddress = GetProperty(directoryUser, ADProperties.STREETADDRESS);
        _telephoneNumber = GetProperty(directoryUser, ADProperties.TELEPHONENUMBER);
        _title = GetProperty(directoryUser, ADProperties.TITLE);
        _userPrincipalName = GetProperty(directoryUser, ADProperties.USERPRINCIPALNAME);
        _employeeID = GetProperty(directoryUser, ADProperties.EMPLOYEEID);
        _c = GetProperty(directoryUser, ADProperties.COUNTRYNOTATION);
        _postalCode = GetProperty(directoryUser, ADProperties.POSTALCODE);
        _physicalDeliveryOfficeName = GetProperty(directoryUser, ADProperties.PHYSICALDELIVERYOFFICENAME);
        _instanceType = GetProperty(directoryUser, ADProperties.INSTANCETYPE);
        _whenCreated = GetProperty(directoryUser, ADProperties.WHENCREATED);
        _whenChanged = GetProperty(directoryUser, ADProperties.WHENCHANGED);
        _memberOf = "PlaceHolder";//GetProperty(directoryUser, ADProperties.MEMBEROF);
        _directReports = GetProperty(directoryUser, ADProperties.DIRECTREPORTS);
        _userAccountControl = GetProperty(directoryUser, ADProperties.USERACCOUNTCONTROL);
        _codePage = GetProperty(directoryUser, ADProperties.CODEPAGE);
        _countryCode = GetProperty(directoryUser, ADProperties.COUNTRYCODE);
        _adminCount = GetProperty(directoryUser, ADProperties.ADMINCOUNT);
        _logonCount = GetProperty(directoryUser, ADProperties.LOGONCOUNT);
        _legacyExchangeDN = GetProperty(directoryUser, ADProperties.LEGACYEXCHANGEDN);
        _servicePrincipalName = GetProperty(directoryUser, ADProperties.SERVICEPRINCIPALNAME);
        _dSCorePropagationData = "Placeholder";
        _pager = GetProperty(directoryUser, ADProperties.PAGER);
        _homePhone = GetProperty(directoryUser, ADProperties.HOMEPHONE);
        _msExchUserAccountControl = GetProperty(directoryUser, ADProperties.MSEXCHUSERACCOUNTCONTROL);
        _msExchPoliciesIncluded = GetProperty(directoryUser, ADProperties.MSEXCHPOLICIESINCLUDED);
        _msExchRecipientDisplayType = GetProperty(directoryUser, ADProperties.MSEXCHRECIPIENTDISPLAYTYPE);

        if (!String.IsNullOrEmpty(_manager))
        {
            String[] managerArray = _manager.Split(',');
            _managerName = managerArray[0].Replace("CN=", "");
        }
    }
  

All property declarations in AdUserdetail looks like this:

     [DataMember]
    private String _firstName;
    [DataMember]
    private String _middleName;
    [DataMember]
    private String _lastName;
  

Followed by:

     public String FirstName
    {
        get { return _firstName; }
    }

    public String MiddleName
    {
        get { return _middleName; }
    }

    public String LastName
    {
        get { return _lastName; }
    }
  

Все свойства объявляются таким образом и сериализуются с помощью элемента данных.

Третий класс, на который здесь намекают, — это класс ADProperties . Однако это всего лишь класс констант, облегчающий отслеживание имен свойств, это выглядит так:

     public const String ADMINDESCRIPTION = "adminDescription";
    public const String FIRSTNAME = "givenName";
    public const String MIDDLENAME = "initials";
  

и так далее.

Затем все это вызывается контроллером API следующим образом:

     public HttpResponseMessage Get()
    {
        ActiveDirectorySearcher ADSearcher = new ActiveDirectorySearcher();

        var Users = ADSearcher.GetUserFromGroup("NameofGroup");

        if (Users != null)
        {
            return Request.CreateResponse(HttpStatusCode.OK, Users);
        }

        return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Users found");
    }
  

Итак, вот как это выглядит, наш AD довольно большой, с более чем 30 тыс. пользователей, и в некоторых запланированных работах требуется, чтобы этот API отвечал до 5 тыс. пользователей, в его текущем состоянии на это уходит почти 70 секунд, и я хочу сократить это время до менее 10 секунд. Но пока мои попытки были бесплодными. На данный момент подавляющее большинство времени обработки происходит в процессе сериализации.

Ответ №1:

Узким местом был лямбда-запрос в:

 IEnumerable<string> groupNames = groups.Select(x => x.SamAccountName);
  

Замена этого сокращает время загрузки в 10 раз для большинства вызовов. Самые тяжелые загрузки, которые ранее занимали 132 секунды, теперь занимают 17.

Я знаю, что это не полный ответ, поскольку я не могу сказать, почему этот лямбда-запрос был настолько неэффективным. Но это, безусловно, было виновато во времени загрузки.