Sunday 17 July 2016

Отношения мастер-деталь в SharePoint 2013 между списками

Как построить связи между списками SharePoint в рамках отношений Один ко Многим, Много ко Многим и организовать между ними нечто подобие ссылочной целостности?
Наверняка этот вопрос задавали себе многие начинающие программисты SharePoin, так как стандартные средства этой великолепной системы управления сайтами не позволяют организовать полноценную ссылочную целостность между списками.

На самом деле построить каскадные отношения между списками несложно. Давайте рассмотрим небольшой пример на эту тему:

1. Создадим список сотрудников:
<?xml version="1.0" encoding="utf-8"?>
<List xmlns:ows="Microsoft SharePoint" Title="MFCEmployees" Name="MFCEmployees"
 Direction="$Resources:Direction;" Url="Lists/MFCEmployees" BaseType="0" 
xmlns="http://schemas.microsoft.com/sharepoint/" EnableContentTypes="TRUE">
  <MetaData>
       <ContentTypes>         
    <ContentType JSLink="~site/_layouts/15/webservice_001/
MyContent001/JS/MFCEmployees/MFCEmployees.js" 
ID="0x01005174e8c58336491db85ea917c7f65031" Name="Полное представление">
       <FieldRefs>                               
       <FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Name="Title" />
       <FieldRef ID="{ca828b4b-d9cf-4653-b4a5-2d44d3e9b9df}" Name="EmployeeName" />
       <FieldRef ID="{66cf8cfe-39b9-4123-bdaf-c36b7774dcd9}" Name="Birthdate" />
       <FieldRef ID="{254aabea-f690-4a88-b258-4fe87c61c612}" Name="Date_MFC_Employment" />
       <FieldRef ID="{163c8a49-88be-46e4-837c-57f3e0c1d96c}" Name="EmployeeStatus" />
       <FieldRef ID="{6d47a98d-c144-44ba-9912-339119c25681}" Name="Situation" />
       <FieldRef ID="{0655a999-cdc3-4c35-8b53-3bbd49e8192d}" Name="StatusEmployee" />
       <FieldRef ID="{6345b705-0bce-4e42-8b3e-0b3e3d37fe94}" Name="EndDateWork" />
       <FieldRef ID="{b5544a5a-5950-485e-8b3f-4286adbecb78}" Name="CompensatoryLeave" />
       <FieldRef ID="{234309e1-1823-4ad4-aba6-3c6390a378c6}" Name="Otdel" />
    </FieldRefs>
    </ContentType><ContentTypeRef ID="0x0120" /></ContentTypes>

    ФАЙЛ Schema.xml СОКРАЩЕН

  </MetaData>
</List>
Теперь создадим подчиненный список статусов сотрудников:
<?xml version="1.0" encoding="utf-8"?>
<List Id="{ca222b4b-d9cf-4653-b4a5-2d44d3e9b9df}" xmlns:ows="Microsoft SharePoint" 
Title="MFCEmployeesStatus" FolderCreation="FALSE" Direction="$Resources:Direction;"
 Url="Lists/MFCEmployeesStatus" BaseType="0" 
xmlns="http://schemas.microsoft.com/sharepoint/" EnableContentTypes="TRUE">
  <MetaData>
    <ContentTypes>        
      <ContentType ID="0x01005fec9402a88147e6937d2c0a3fb6303b" 
Name="Даты текущего состояния">
        <FieldRefs>                   
        <FieldRef ID="{4f243fe7-3f34-4e48-9c29-49f538bd4885}" Name="StatusEmployee" />
<FieldRef ID="{41790c3c-762d-4f2d-b570-939ab8281679}" Name="StartDateWork" />
<FieldRef ID="{e5da87f0-b02b-47f1-88d9-ce2cfc511ff1}" Name="EndDateWork" />
<FieldRef ID="{a50cd5f7-67ab-40d6-b9be-3ffcc6ff54e7}" Name="MFCEmployeesId" />
</FieldRefs>
      </ContentType><ContentTypeRef ID="0x01" /><ContentTypeRef ID="0x0120" />
</ContentTypes>

ФАЙЛ Schema.xml СОКРАЩЕН

  </MetaData>
</List>

Обратите внимание - у списка MFCEmployeesStatus объявлен столбец MFCEmployeesId. Это вторичный ключ списка MFCEmployees. По нему мы и будем строить отношения между списками по принципу Один ко Многим, где мастер - MFCEmployees, а деталь - MFCEmployeesStatus.

Теперь. когда у нас готовы списки нам необходимо создать механизм связывания двух списков по вторичному ключу. Для этого мы воспользуемся API SharePoint REST.
Без долгих объяснений прилагаю код соответствующих функций:
1. MFCEmployeesId.js:
SP.SOD.executeFunc("clienttemplates.js""SPClientTemplates"function () {
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
        Templates: {
            Fields: {
                //имя поля
                'MFCEmployeesId':
                {
                    NewForm: function (ctx) {
                        var qur = GetUrlKeyValue('MFCEmployeesId');
                        var val = 0;
                        if (typeof qur !== "undefined" && qur.length > 0)
                            val = qur;
                        else
                            val = 0;
                        ctx.CurrentFieldValue = val;
                        return SPFieldNumber_Edit(ctx);
                    }
                }
            }
        },
        OnPostRender: function (ctx) {
            //после загрузки
            var f = ctx.ListSchema.Field[0];
            if (f.Name == "MFCEmployeesId") {
                InputHiden(f, "Field",true);
            }
        },
        ListTemplateType: 10006
    });
});
Функция GetUrlKeyValue читает параметр запроса по имени MFCEmployeesId. 
Если находит значение, то подставляет его в поле вторичного ключа. В свою очередь функция 
InputHiden в событии OnPostRender скрывает поле от глаз пользователей.
Как видите, все очень просто. Самое главное:
1. Передать в строке запроса значение ключа сотрудника.
2. Перед сохранение статуса у сотрудника прочитать соответствующий параметр, где храниться
ключ сотрудника и сохранить его в соответствующем input-е.

Пока все. Я показал, как можно прочитать и сохранить значение вторичного ключа.
В следующий раз я продемонстрирую как можно организовать ссылочную целостность 
на практике. Следите за обновлениями :)

Фильтр авторизации ASP.NET MVC >= 5

Начиная с ASP.NET MVC 4 стало возможным применять обновленный фильтр авторизации класс AuthorizationFilter c проверкой подлинности ролей.

Этот фильтр реализует тип IAuthorizationFilter и определяет необходимость выполнения метода действия (например, выполнение проверки подлинности или проверки свойств запроса) с точки зрения безопасности.
Классы AuthorizeAttribute и RequireHttpsAttribute являются примерами фильтров проверки подлинности. Фильтры проверки подлинности выполняются перед выполнением любых других фильтров.
Вот, немножко сплагиатил с официальной документации о фильтрах. Теперь по сути:

Лично я использовал в своих проектах указанный фильтр, в том числе и для проверки подлинности на основании определенных ролей. Это, впрочем, самое главное новшество фильтра авторизации, которое появилось с 4-ой версией MVC.

Я был удовлетворен работой класса и, особенно не вникал в суть его работы :) до тех пор, пока не воспользовался новой системой авторизации и профилей Microsoft ASP.NET Identity 2.0. Тогда выяснилось, что класс AuthorizeAttribute полноценно использовать нельзя. Например, для проверка ролей класс взаимодействует с интерфейсом IPrincipal, который реализует собственный, отличающийся от Identity 2.0, интерфейс. Вывод - сочинить собственный фильтр авторизации. Далее речь пойдет об этом :)

Итак, с начало код фильтра:

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using System.Web;
using System.Web.Mvc;
 
namespace avk_commerce.web.Filters
{
    public class RoleAuthorizationAttribute : ActionFilterAttributeIActionFilter
    {
        public string Roles { getset; }
        #region вызывается перед обращением к методу действия
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {                                 
            bool Match = false;
            var user = filterContext.HttpContext.User.Identity;
            //роли, переданные через параметр
            string[] _Roles = Roles.Split(',');
            if (user.IsAuthenticated)
            {
                var UserManager = 
     filterContext.HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
                int UserId = user.GetUserId<int>();
                foreach (var Role in _Roles)
                {
                    Match = UserManager.IsInRole(UserId, Role);                    
                }
                if (!Match)
                {
                    filterContext.Result = new ViewResult { ViewName = "AccessDenied" };
                }
            }
            else {
                filterContext.Result = new ViewResult { ViewName= "LoginNow" };
            }            
            base.OnActionExecuting(filterContext);
        }
        #endregion
    }
}
Как видим, класс переопределяет метод OnActionExecuting. Класс содержит свойство
Roles. Это список ролей через запятую. Мы в первую очередь проверяем прошел ли текущий 
пользователь авторизацию на сайте. Потом проверяем - соответствуют ли задекларированные
в методе контролера разрешенные роли, тем ролям, которые присвоены текущему пользователю.
Выводы сохраняем в локальной переменной Match. Далее в зависимости от значения в переменной 
Match принимаем решения. Как видите, все очень просто :)

Пример практического применение:

using System.Web.Mvc;
using avk_commerce.web.Filters;
using avk_commerce.web.Models;
 
namespace avk_commerce.web.Controllers.Admin
{
    [RoleAuthorization(Roles = CustomRoles.Admin)]
    [Culture]    
    public class AdminController : BaseController
    {
        #region Index
        public ActionResult Index()
        {
            var item = this.GetHomeData(null);
            return View(item);
        }
        #endregion
               
        #region Dispose
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                //base.db.Dispose();
            }
            base.Dispose(disposing);
        }
        #endregion
    }
}