четверг, 10 октября 2013 г.

Вызов веб-сервиса MDS из SharePoint 2013. Имперсонализация.

Веб-сервис обычно MDS настраивается при включенной имперсонализации, и метод аутентификации - windows. Однако из SharePoint вызов кода веб-сервиса происходит от имени IUSR. Чтобы заставить веб-сервис выполняться от имени той учетной записи, под которой пользователь зашел на SharePoint необходимо сделать следующие 2 вещи:

1. Включить службу  Claims To Windows Token Service
2. В коде сделать логон под требуемой учеткой.

Служба стартуется из Центра администрирования SharePoint 2013

System Settings -> Manage Services on server -> Claims To Windows Token Service -> Start

 



Далее необходимо добавить в проект утилиту (взято отсюда)

 
   public class Utility
    {
        public static WindowsIdentity GetWindowsIdentityFromClaimsToken()
        {
            WindowsIdentity wi = null;

            // Run this portion of code as application pool account, so that C2WTS service is called as this account
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                // Get the UPN value of the user from the UPN claim type
                Microsoft.IdentityModel.Claims.IClaimsIdentity identity = ((Microsoft.IdentityModel.Claims.ClaimsIdentity)Thread.CurrentPrincipal.Identity as Microsoft.IdentityModel.Claims.IClaimsIdentity);
                string upn = null;
                foreach (Microsoft.IdentityModel.Claims.Claim claim in identity.Claims.Where(claim => StringComparer.Ordinal.Equals(ClaimTypes.Upn, claim.ClaimType)))
                {
                    upn = claim.Value;
                }

                if (upn == null)
                {
                    throw new Exception(string.Format("Cannot Impersonate {0} since he doesn't have a UPN in his claims", Thread.CurrentPrincipal.Identity.Name));
                }

                // Get a WindowsIdentity from the UPN of the user by calling C2WTS service
                try
                {
                    wi = Microsoft.IdentityModel.WindowsTokenService.S4UClient.UpnLogon(upn);
                }
                catch (System.Exception ex)
                {
                    throw new Exception(string.Format("Impersonation failed. Message: {0}", ex.Message));
                }
            });

            return wi;
        }
    }


Далее можно вызывать методы веб-сервиса:

   
            using (WindowsImpersonationContext ctxt = Utility2.GetWindowsIdentityFromClaimsToken().Impersonate())
            {
                ServiceReference.ServiceClient = client = new ServiceReference.ServiceClient();
                var resultOptions = new MetadataResultOptions();
                resultOptions.Models = ResultType.Identifiers;
                var criteria = new MetadataSearchCriteria();
                criteria.SearchOption = SearchOption.UserDefinedObjectsOnly;

                OperationResult result;
                var models = _client.MetadataGet(new International(), resultOptions, criteria, out result); 
            }

пятница, 10 мая 2013 г.

От чего проваливаются проекты.



Проект по миграции SharePoint 2007 до 2010, может затянулся и на год.

Реплика руководителя Отдела  управления проектами Василия Дайчасовского.

"Так как информация о нашем Проект Миграции и особенно о его результатах дошли до самого верха, то нам предстоит объяснить, как же все так вышло, что нам потребовалось более года на натягивание нового дизайна, перенос 4х сервисов и устранения тьмы наших же ошибок."

Мнение ведущего аналитика проекта.

1.    Проект по миграции портала изначально был поставлен за рамки методологии Microsoft по миграции портала. Что предполагало определённые риски, которые не были учтены.
2.    Не было до конца согласованно и подписано ТЗ.
3.    Отсутствие у Заказчика детальных требований к проекту.
4.    Отсутствие у Заказчика ТЗ на существующие модули системы, созданные ранее текущего проекта.
5.    С менеджером проекта со стороны Заказчика были изначально зафиксированы некие договорённости по реализации проекта. После чего со стороны Заказчика произошла смена менеджера проекта, что повлекло за собой:
a.    Большой срок введения в проект нового менеджера проекта Заказчика;
b.    Формирование нового видения результатов проекта со стороны Заказчика;
c.    Формирование новых требования для Исполнителя по проекту со стороны Заказчика;

6.    Бессрочный этап тестирования системы Заказчиком.

Мнение начальника отдела внедрения Александра Внедряйкина.

1.    Отсутствовал проект по миграции портала, была работа в рамках годовой технической поддержки.
2.    В рамках техподдержки была первоначально достигнута договоренность провести миграцию портала поверх старого «как есть» - т.е. стандартными средствами по стандартной методологии Microsoft, а все то, что не перенеслось автоматически, должно было перейти в дополнительный проект.
3.    На каком то этапе переговоров первоначальная договоренность была изменена и было решение не мигрировать портал, а создать новый, в котором создать 4 сервиса и перенести несколько библиотек документов в ручную. На данную работу мы подписались с условием, что работы будут осуществляться по ТЗ.
4.    ТЗ на создание портала было написано и согласовано с менеджером проекта Заказчика. Т.к. договора на проект по миграции не было, а была работа в рамках техподдержки, то ТЗ не было официальным образом утверждено.
5.    Работы были выполнены по ТЗ в течении запланированного срока, но официальной сдачи проекта не было (т.к. и официального проекта не было)
6.    Произошла смена руководителя проекта со стороны заказчика и уволился наш сейлз менеджер ( Петр Торгашов). В итоге все договоренности неожиданно похерились
7.    Новый менеджер проекта со стороны Заказчика стал требовать функционал не по согласованному ТЗ, а в полном соответствии с функционалом старого портала (по которому не было ни документации ни кодов), интерпретировав понятие «как есть» в свою пользу.
8.    Мы каким то образом на это подписались, поскольку единственным человеком, который был в курсе договоренностей оставался я, а я на данном этапе в переговорах не участвовал.
9.    А уж далее начался полный бред и история, леденящая душу

понедельник, 21 января 2013 г.

Вопросы по T-SQL (для подготовки). Часть 1

Вопрос. Перечислете стандарты SQL в хронологическом порядке
Ответ.
SQL-86
SQL-89
SQL-92
SQL:1999
SQL:2003
SQL:2006
SQL:2008
SQL:2011

Вопрос. В чем различия между SQL и T-SQL?
SQL - это стандартный язык запросов. T-SQL - это расширенный диалект SQL, испльзующийся в продукте MS SQL SERVER.

Вопрос. На каких двух математичкеских дисциплинах основана реляционная модель?
Ответ. 1) Теория отношений, 2) Логика предикатов.

Вопрос. Назовите два аспекта в которых T-SQL отходит от реляционной модели.
Ответ. Отношение представляет собой набор неупорядоченных и уникальных структурированных записей. 1)  T-SQL таблица может не иметь, ключа идентифицирующего записи. 2) T-SQL позволяет указывать порядковые номера позиций столбцов в ORDER BY.

Вопрос. Почему не в полне корректно использовать терминиы "поле" и "запись" в реляционной модели? Какие термины корректны?
Ответ. Потому что "поле" и "record" описывают физические объекты. Правильная терминология "столбец" и "строка".

Вопрос. Почему некорректно использовть термин "значения NULL" или "NULL-значения"?
Ответ.  Потому что NULL не является значением, это просто пометка о том, что значение не определено.

Вопрос. Укажите декларативном англоподобном языке прядок следования предложеий в команде SELECT
Ответ.
1. SELECT; 2. FROM; 3. WHERE; 4. GROUP BY; 5. HAVING; 6. ORDER BY

Вопрос. Укажите логический прядок обработки предложений в команде SELECT
Ответ.
1. FROM; 2. WHERE; 3. GROUP BY; 4. HAVING; 5. SELECT; 6. ORDER BY

Вопрос. Укажите способы задания алиасов столбцов в предложении SELECT
Ответ.
1) <поле> <алиас>
2) <поле> AS <алиас>
3) <алиас> = <поле>

Вопрос. Почему нельзя обращаться к алиасам полет в предложении WHERE. Какой есть способ обойти проблему?
Ответ. Согласно логическому порядку обработки предложений, WHERE срабатывает перед SELECT, поэтому алиасы на этапе WHERE еще не известны. Выход - использовать те же имена столбцов или выражений, которые использованы в предложении SELECT.

Вопрос. Назовите отличия между WHERE и HAVING.
Ответ. Предложение WHERE выполняется перед тем как данные группируются, и выполняются для каждой строки таблицы. Предложение HAVING выполняет фильтр после того как заприси сгруппированы и применяются к каждой группе.

Вопрос. Почему не разрешается обращаться к алиасам определенных в предложении SELECT в том же предложении?
Ответ. Это запрещено специально, потому что все выражения, которые появляются на одном и том же логическом этапе концептуально выполняются одновременно. Иным словами, концептуально не доускается последовательное выполнение выражений в предложении SELECT.

Вопрос. Сформируйте правила задания "регулярных" идентификаторов для столбоцов.
Ответ. Идентификатор должен начинаться с буквы в диапазоне от A до Z (регистр не важен), с символа подчеркивания (_), со знака  (@), или решетки (#). Последующе символы могут включать буквы, цифры, знак амперсанта и знак доллара ($), решетки или подчеркивания.

Вопрос. Правила указания "нерегулярных" идентификаторов.
Ответ. Не удовлетворяющие регулярным правилам идентификаторы должны заключаться в двоыные кавычки или в квадратные скобки.
 

четверг, 22 ноября 2012 г.

Забанили ресурс по SharePoint

Забанили ресурс по SharePoint

 http://sharepoint.infoyen.com/

забанен по решению Россиского государства. Приехали.

среда, 12 сентября 2012 г.

Завершили проект по Русагротрансу


Компания Softline (а конкретно, я и ведущий аналитик Марат Губайдулин) завершила работы по модернизации корпоративного портала ЗАО «Русагротранс». В рамках проекта портал был реструктурирован и перенесен на платформу Microsoft SharePoint Server 2010

http://www.microsoft.com/casestudies/Case_Study_Detail.aspx?casestudyid=710000001302

вторник, 17 июля 2012 г.

LinqToSharePoint & RunWithElevatedPriveledges

Итак проблема

SPSecurity.RunWithElevatedPriveledges игнорируется при использовании команд LinqToSharePoint, так как для выполнения команд в любом случае берется текущий контекст, SPContext.Current, если он не пуст. И только если мы выполняем команды вне веб-контекста, он сформирует контекст из наших данных.

Проблема решается, путем  «подсовывания» текущему контексту, того Web данных, который мы используем внутри RunWithoutPriveledges.

Делается это так:

1)      Запоминаем текущий веб-контекст
2)      Выполняем RunWithoutPriveledges
3)      Внутри открываем Site и Web
4)      Ассоциируем текущий контекст с новым Web
5)      Открываем DataContext LinqToSharePoint
6)      Выполняем все команды с повышенными правами
7)      Закрываем Web и Site
8)      Восстанавливаем веб-контекст из п.1


Техническая реализация описана в этой ссылке:




Вот класс-обертки для вызова
public class LinqToSPSecurity
{
  public static void RunWithElevatedPriveledges(SPWeb web, Action<SPWeb> action) 
  {
    Guid webId = web.ID;
    Guid siteId = web.Site.ID;

    HttpContext backupCtxt = HttpContext.Current;
    try
    {
      if (SPContext.Current != null)
        HttpContext.Current = null;
      SPSecurity.RunWithElevatedPrivileges(() =>
      {
        using (SPSite elevatedSite = new SPSite(siteId))
        {
          using (SPWeb elevatedWeb = elevatedSite.OpenWeb(webId))
          {
            HttpRequest httpRequest = new HttpRequest("", elevatedWeb.Url, "");
            HttpContext.Current = new HttpContext(httpRequest, new HttpResponse(new System.IO.StringWriter()));
            SPControl.SetContextWeb(HttpContext.Current, elevatedWeb);
            action(elevatedWeb);
          }
        }
      });
    }
    finally
    {
      if (SPContext.Current != null)
        HttpContext.Current = backupCtxt;
    }

  }

  public static void RunWithElevatedPriveledges(Action<SPWeb> action) 
  {
    RunWithElevatedPriveledges(SPContext.Current.Web, action);
  }
}

вторник, 3 июля 2012 г.

Yet Another Shitty Code (Говнокод 2)


/// <summary>
        /// Сохранение орг. звена
        /// </summary>
        /// <param name="orgUnit">Орг. звено</param>
        /// <param name="newchildren">Список идентификаторов добавляемых дочерних орг. единиц</param>
        /// <param name="deletechildren">Список идентификаторов удаляемых дочерних орг. единиц</param>
        /// <param name="newcustomers">Список идентификаторов добавляемых заказчиков</param>
        /// <param name="deletecustomers">Список идентификаторов удаляемых заказчиков</param>
        /// <param name="newemployees">Список идентификаторов добавляемых сотрудников</param>
        /// <param name="deleteemployees">Список идентификаторов удаляемых сотрудников</param>
            public void Save(OrgUnit orgUnit,
                  List<int> newchildren, List<int> deletechildren,
                  List<int> newcustomers, List<int> deletecustomers,
                  List<int> newemployees, List<int> deleteemployees)
            {
                  if (newchildren == null) newchildren = new List<int>();
                  if (newcustomers == null) newcustomers = new List<int>();
                  if (newemployees == null) newemployees = new List<int>();
                  if (deletechildren == null) deletechildren = new List<int>();
                  if (deletecustomers == null) deletecustomers = new List<int>();
                  if (deleteemployees == null) deleteemployees = new List<int>();

                  if (orgUnit.OrgUnitID == 0) // новая - создать
                  {
                L_UserActivityController.Write("Орг.звено", "Создание", orgUnit.Title);
                        DataContext.OrgUnits.InsertOnSubmit(orgUnit);
                  }

                  foreach (int id in newemployees)
                  {
                OUBind oldbins = App.DataContext.OUBinds.FirstOrDefault(b => b.EmployeeID == id &&
                    b.EndDate == null && b.BindType == 'M');
                if (oldbins != null)
                    oldbins.EndDate = DateTime.Now;

                oldbins = App.DataContext.OUBinds.FirstOrDefault(b => b.EmployeeID == id &&
                    b.EndDate == null && b.BindType == 'E');
                if (oldbins != null)
                    oldbins.EndDate = DateTime.Now;


                        OUBind eou = new OUBind();
                        eou.OrgUnit = orgUnit;
                        eou.EmployeeID = id;
                        eou.BeginDate = DateTime.Now;
                eou.BindType = 'M';
                        App.DataContext.OUBinds.InsertOnSubmit(eou);
                if (App.DataContext.EmployeeRoles.Count(r => r.EmployeeID == id && r.RoleCode == RoleEnum.Employee.ToString()) == 0)
                {
                    EmployeeRole er = new EmployeeRole { EmployeeID = id, RoleCode = RoleEnum.Employee.ToString() };
                    App.DataContext.EmployeeRoles.InsertOnSubmit(er);
                }
                  }

                  if (deleteemployees.Count > 0)
                  {
                        List<OUBind> eoudel = DataContext.OUBinds.Where(
                             eou => deleteemployees.Contains(eou.EmployeeID) &&
                                   eou.OrgUnitID == orgUnit.OrgUnitID).ToList();
                foreach (var b in eoudel)
                    b.EndDate = DateTime.Now;
                  }

                  if (orgUnit.BranchID == null) // сохранение филиалов
                  {
                        foreach (int id in newchildren)
                        {
                             OrgUnit o = new OrgUnit();
                             o.Affiliation = orgUnit.Affiliation;
                             o.BranchID = id;
                             o.IsActive = true;
                    L_UserActivityController.Write("Орг.звено", "Создание", orgUnit.Title + " / " + GetAdminUnit(id).Title);
                             DataContext.OrgUnits.InsertOnSubmit(o);
                        }

                        if (deletechildren.Count > 0)
                        {
                             List<OrgUnit> oudel = DataContext.OrgUnits.Where(ou =>
                                   ou.AffiliationID == orgUnit.AffiliationID &&
                                   ou.BranchID.HasValue &&
                                   !ou.FuncDirectionID.HasValue &&
                                   !ou.ActivityCategoryID.HasValue &&
                                   !ou.RegionID.HasValue &&
                                   !ou.DepartmentID.HasValue &&
                                   !ou.GroupID.HasValue &&
                                   deletechildren.Contains(ou.BranchID ?? 0)
                                   ).ToList();
                    foreach (var ou in oudel)
                    {
                        L_UserActivityController.Write("Орг.звено", "Удаление", ou.Title);
                        ou.IsActive = false;
                        DeleteChild(ou);
                    }
                        }
                  }
                  else if (orgUnit.FuncDirectionID == null) // сохранение функциональных направлений
                  {
                        foreach (int id in newchildren)
                        {
                             OrgUnit o = new OrgUnit();
                             o.Affiliation = orgUnit.Affiliation;
                             o.Branch = orgUnit.Branch;
                             o.FuncDirectionID = id;
                             o.IsActive = true;
                    L_UserActivityController.Write("Орг.звено", "Создание", orgUnit.Title + " / " + GetAdminUnit(id).Title);
                             DataContext.OrgUnits.InsertOnSubmit(o);
                        }

                        if (deletechildren.Count > 0)
                        {
                             List<OrgUnit> oudel = DataContext.OrgUnits.Where(ou =>
                                   ou.AffiliationID == orgUnit.AffiliationID &&
                                   ou.BranchID == orgUnit.BranchID &&
                                   ou.FuncDirectionID.HasValue &&
                                   !ou.ActivityCategoryID.HasValue &&
                                   !ou.RegionID.HasValue &&
                                   !ou.DepartmentID.HasValue &&
                                   !ou.GroupID.HasValue &&
                                   deletechildren.Contains(ou.FuncDirectionID ?? 0)
                                   ).ToList();
                    foreach (var ou in oudel)
                    {
                        L_UserActivityController.Write("Орг.звено", "Удаление", ou.Title);
                        ou.IsActive = false;
                        DeleteChild(ou);
                    }
                        }
                  }
                  else if (orgUnit.ActivityCategoryID == null) // сохранение видов деятельности
                  {
                        foreach (int id in newchildren)
                        {
                             OrgUnit o = new OrgUnit();
                             o.Affiliation = orgUnit.Affiliation;
                             o.Branch = orgUnit.Branch;
                             o.FuncDirection = orgUnit.FuncDirection;
                             o.ActivityCategoryID = id;
                             o.IsActive = true;
                    L_UserActivityController.Write("Орг.звено", "Создание", orgUnit.Title + " / " + GetAdminUnit(id).Title);
                             DataContext.OrgUnits.InsertOnSubmit(o);
                        }

                        if (deletechildren.Count > 0)
                        {
                             List<OrgUnit> oudel = DataContext.OrgUnits.Where(ou =>
                                   ou.AffiliationID == orgUnit.AffiliationID &&
                                   ou.BranchID == orgUnit.BranchID &&
                                   ou.FuncDirectionID == orgUnit.FuncDirectionID &&
                                   ou.ActivityCategoryID.HasValue &&
                                   !ou.RegionID.HasValue &&
                                   !ou.DepartmentID.HasValue &&
                                   !ou.GroupID.HasValue &&
                                   deletechildren.Contains(ou.ActivityCategoryID ?? 0)
                                   ).ToList();
                    foreach (var ou in oudel)
                    {
                        L_UserActivityController.Write("Орг.звено", "Удаление", ou.Title);
                        ou.IsActive = false;
                        DeleteChild(ou);
                    }
                        }
                  }
                  else if (orgUnit.RegionID == null) // сохранение регионов
                  {
                        foreach (int id in newchildren)
                        {
                              OrgUnit o = new OrgUnit();
                             o.Affiliation = orgUnit.Affiliation;
                             o.Branch = orgUnit.Branch;
                             o.FuncDirection = orgUnit.FuncDirection;
                             o.ActivityCategory = orgUnit.ActivityCategory;
                             o.RegionID = id;
                             o.IsActive = true;
                    L_UserActivityController.Write("Орг.звено", "Создание", orgUnit.Title + " / " + GetAdminUnit(id).Title);
                             DataContext.OrgUnits.InsertOnSubmit(o);
                        }

                        if (deletechildren.Count > 0)
                        {
                             List<OrgUnit> oudel = DataContext.OrgUnits.Where(ou =>
                                   ou.AffiliationID == orgUnit.AffiliationID &&
                                   ou.BranchID == orgUnit.BranchID &&
                                   ou.FuncDirectionID == orgUnit.FuncDirectionID &&
                                   ou.ActivityCategoryID == orgUnit.ActivityCategoryID &&
                                   ou.RegionID.HasValue &&
                                   !ou.DepartmentID.HasValue &&
                                   !ou.GroupID.HasValue &&
                                   deletechildren.Contains(ou.RegionID ?? 0)
                                   ).ToList();
                    foreach (var ou in oudel)
                    {
                        L_UserActivityController.Write("Орг.звено", "Удаление", ou.Title);
                        ou.IsActive = false;
                        DeleteChild(ou);
                    }
                        }

                        foreach (int id in newcustomers)
                        {
                             CustomerOrgUnit cou = new CustomerOrgUnit();
                             cou.OrgUnitID = orgUnit.OrgUnitID;
                             cou.CustomerID = id;
                             DataContext.CustomerOrgUnits.InsertOnSubmit(cou);
                        }

                        if (deletecustomers.Count > 0)
                        {
                             List<CustomerOrgUnit> coudel = DataContext.CustomerOrgUnits.Where(
                                   cou => deletecustomers.Contains(cou.CustomerID) &&
                                         cou.OrgUnitID == orgUnit.OrgUnitID).ToList();
                             DataContext.CustomerOrgUnits.DeleteAllOnSubmit(coudel);
                        }

                  }
                  else if (orgUnit.DepartmentID == null) // сохранение отделов
                  {
                        foreach (int id in newchildren)
                        {
                             OrgUnit o = new OrgUnit();
                             o.Affiliation = orgUnit.Affiliation;
                             o.Branch = orgUnit.Branch;
                             o.FuncDirection = orgUnit.FuncDirection;
                             o.ActivityCategory = orgUnit.ActivityCategory;
                             o.Region = orgUnit.Region;
                              o.DepartmentID = id;
                             o.IsActive = true;
                    L_UserActivityController.Write("Орг.звено", "Создание", orgUnit.Title + " / " + GetAdminUnit(id).Title);
                             DataContext.OrgUnits.InsertOnSubmit(o);
                        }

                        if (deletechildren.Count > 0)
                        {
                             List<OrgUnit> oudel = DataContext.OrgUnits.Where(ou =>
                                   ou.AffiliationID == orgUnit.AffiliationID &&
                                   ou.BranchID == orgUnit.BranchID &&
                                   ou.FuncDirectionID == orgUnit.FuncDirectionID &&
                                   ou.ActivityCategoryID == orgUnit.ActivityCategoryID &&
                                   ou.RegionID == orgUnit.RegionID &&
                                   ou.DepartmentID.HasValue &&
                                   !ou.GroupID.HasValue &&
                                   deletechildren.Contains(ou.DepartmentID ?? 0)
                                   ).ToList();
                    foreach (var ou in oudel)
                    {
                        L_UserActivityController.Write("Орг.звено", "Удаление", ou.Title);
                        ou.IsActive = false;
                        DeleteChild(ou);
                    }
                        }
                  }
                  else if (orgUnit.GroupID == null) // сохранение групп
                  {
                        foreach (int id in newchildren)
                        {
                             OrgUnit o = new OrgUnit();
                             o.Affiliation = orgUnit.Affiliation;
                             o.Branch = orgUnit.Branch;
                             o.FuncDirection = orgUnit.FuncDirection;
                             o.ActivityCategory = orgUnit.ActivityCategory;
                             o.Region = orgUnit.Region;
                             o.Department = orgUnit.Department;
                             o.GroupID = id;
                             o.IsActive = true;
                    L_UserActivityController.Write("Орг.звено", "Создание", orgUnit.Title + " / " + GetAdminUnit(id).Title);
                             DataContext.OrgUnits.InsertOnSubmit(o);
                        }

                        if (deletechildren.Count > 0)
                        {
                             List<OrgUnit> oudel = DataContext.OrgUnits.Where(ou =>
                                   ou.AffiliationID == orgUnit.AffiliationID &&
                                   ou.BranchID == orgUnit.BranchID &&
                                   ou.FuncDirectionID == orgUnit.FuncDirectionID &&
                                   ou.ActivityCategoryID == orgUnit.ActivityCategoryID &&
                                   ou.RegionID == orgUnit.RegionID &&
                                   ou.DepartmentID == orgUnit.DepartmentID &&
                                   ou.GroupID.HasValue &&
                                   deletechildren.Contains(ou.GroupID ?? 0)
                                   ).ToList();
                    foreach(var ou in oudel)
                    {
                        L_UserActivityController.Write("Орг.звено", "Удаление", ou.Title);
                        ou.IsActive = false;
                        DeleteChild(ou);
                    }
                        }
                  }

                  DataContext.SubmitChanges();
                  RedirectTo<OrgUnitController>(c => c.Edit(orgUnit.OrgUnitID));
            }