пятница, 30 марта 2012 г.

Redirect из EventReceiver’а

Как оказалось, это довольно частая задача. Но практически нигде не разрешённая.
Итак, у нас есть обработчик события создания элемента списка (ItemAdding); и нам необходимо сразу после создания перейти на другую страницу.

На первый взгляд задача простая: у нас есть и «Page.Response.Redirect(…)» и «SPUtility.Redirect(…)».
Но… Для первого случая у нас нет объекта Page! А второй метод требует указания HttpContext, который в обработчиках события пуст.

Решение!
Как всегда хак! :)

Как известно, создание элементов списка можно отменять (для этого обработчики событий видимо изначально и создавались). При этом, отменяя, мы можем указать адрес, на который нам следует перейти (чтобы посмотреть причину отмены) (доступно только в SharePoint 2010).
То есть, мы можем отменить создание элемента, и сделать искомый редирект на нужную страницу. Нам нужно только пересоздать отменённый элемент. Вот тут и кроется ограничение этого метода – у нас нет доступа к вложениям элемента при его создании.

Пример кода реализации данного метода:

public override void ItemAdding(SPItemEventProperties properties)
{
    base.ItemAdding(properties);

    //Отключаем сам обработчик, чтобы избежать зацикливания
    EventFiringEnabled = false;

    var list = properties.List;

    //Создаём элемент списка и заполняем его свойствами из только что созданного пользователем элемента
    var ni = list.Items.Add();

    //Выбираем все созданные пользователем свойства доступные для редактирования, а также свойство Title (Название)
    foreach (var ff in list.Fields.Cast<SPField>().Where(spf => !spf.ReadOnlyField && (spf.FromBaseType == false || spf.InternalName == "Title")))
        ni[ff.InternalName] = properties.AfterProperties[ff.InternalName];
    ni.SystemUpdate();

    //Включаем обработчик
    EventFiringEnabled = true;

    //Отменяем создание элемента созданного пользователем
    properties.Cancel = true;
    //Устанавливаем действие при отмене - переход на другую страницу
    properties.Status = SPEventReceiverStatus.CancelWithRedirectUrl;
    //Устанавливаем адрес страницы, на которую делаем переход. В данном случае - страница редактирования только что созданного элемента
    properties.RedirectUrl = string.Format("/{0}?ID={1}", properties.List.Forms[PAGETYPE.PAGE_EDITFORM].Url, ni.ID);
}

Событие ItemAdded для этого метода не подходит, т.к. там элемент списка уже создан и отменить это действие уже нельзя.

24 комментария:

  1. а кнопочку на форме переопределить не проще? Если кто-то подписался на событие создания item кроме тебя, то что делать?

    ОтветитьУдалить
  2. 1. Не проще, если у нас много таких списков. Тем более если они уже развёрнуты.
    Если же всё ещё впереди, то да, тут в зависимости от задачи.
    2. Ставим Sequence у этого события побольше, либо у того другого поменьше. И всё будет ok!

    ОтветитьУдалить
    Ответы
    1. 1. Что может быть проще, чем унаследоваться от класса SaveButton и добавить редирект?
      2. Кроме события adding есть событие added, и тут уже никто не сможет подписаться.

      Т. е. своим хакам ты ломаешь стандартный функционал, хотя можешь поменять renderingtemplate формы и поставить свою кнопку, а это не есть гут.

      3. В коде при копировании не учтены seald и hidden филды;
      4. Хак доступен только для SharePoint 2010.

      Удалить
  3. 1. Что может быть проще, чем унаследоваться от класса SaveButton и добавить редирект?
    2. Кроме события adding есть событие added, и тут уже никто не сможет подписаться;

    Т. е. своим хакам ты ломаешь стандартный функционал, хотя можешь поменять renderingtemplate формы и поставить свою кнопку, а это не есть гут.

    3. В коде при копировании не учтены seald и hidden филды;
    4. Хак доступен только для SharePoint 2010.

    В SharePoint есть еще delegate контролы в них тоже можно организовать редирект, но в данном случае своя кнопка - это самое верное решение.

    ОтветитьУдалить
    Ответы
    1. 1. А если элемент создаётся не через форму? Есть и другие способы создать элемент списка в обход формы.
      2. А кто должен? Если это наш список, мы с ним работаем! Мы изменяем поведение только того списка к которому прицеплено наше событие.
      3. Это специально так сделано, пользователь ничего не может ввести в hidden поля. А если нужно sealed поля, то пожалуйста, модифицируйте код под ваши нужны!
      4. Да, об этом упоминалось в статье.

      Удалить
    2. 1. Допустим элемент создаётся рабочим процессом...

      Удалить
    3. Если SPListItem создается в job, workflow, console, services или в других местах, зачем редирект? Редирект нужен на форме, а так только увевичиваете число обращений к базе. Для таких вещей и были введены renderingtemplates и своя страница для создания SPListItem.

      Удалить
    4. У человека была реальная задача, сделать редирект после создания элемента списка рабочим процессом, который запускался из другого списка.
      В связи с чем и возникла данная статья.

      Удалить
  4. Простите, а чем Вас не устраивает такое решение?
    http://www.sharepointkings.com/2008/05/httpcontext-in-eventhandler.html

    ОтветитьУдалить
    Ответы
    1. Вы его пробовали?
      Оно написано для SP'07 и возможно там оно работало.

      Но для SP'10 оно не работает, и как я писал выше "HttpContext.Current" всегда пусто!

      Удалить
    2. Я пробовал несколько вариантов получения HttpContext, но без результатно. Если у вас есть рабочий вариант, то прошу вас поделиться!

      Спасибо!

      Удалить
    3. Да, пробовал.

      http://dl.dropbox.com/u/34871964/EventHandlersTest.zip

      Удалить
    4. Да, контекст существует. Но redirect происходит если только создавать элемент списка через форму. Если же элемент списка создавать рабочим процессом, то нет.

      Удалить
  5. В FormContext всегда можно указать редирект неважно через кнопку или другое поле.

    ОтветитьУдалить
    Ответы
    1. Вот только где его взять, если SPContext отсутствует???

      Удалить
    2. И в конструкторе эвент ресивера SPContext.Current тоже равен null?

      Удалить
    3. Я не правильный вопрос задал. )
      Каким образом вы укажете OnSaveHandler (я так понял мы про него говорим) в обработчике события?

      Удалить
  6. Игорь, мне потребовалось подобное решение..
    но редирект просто игнорируется, и переход происходит на стандартную error.aspx
    или для библиотеки документов подобное не реализуемо?

    ОтветитьУдалить
    Ответы
    1. Дайте Ваш код.
      properties.RedirectUrl задаёте?
      properties.Status = SPEventReceiverStatus.CancelWithRedirectUrl; - так задано?
      Это два ключевых свойства для осуществления редиректа.

      Удалить
  7. Этот комментарий был удален автором.

    ОтветитьУдалить
  8. public override void ItemAdding(SPItemEventProperties properties)
    {
    base.ItemAdding(properties);
    this.EventFiringEnabled = false;
    ....

    int _numb = GetNextNumberDocument(folderparent, properties.List, _nameDocSet);
    Hashtable prop = new Hashtable();
    var list = properties.List;
    string _title= properties.List.Title + "-" + _numb.ToString();
    SPContentTypeId oCntID = new SPContentTypeId();
    oCntID = list.ContentTypes[_nameDocSet].Id;
    properties.AfterProperties[_nameFieldNumber] = _numb;
    properties.AfterProperties["Title"] = properties.List.Title + "-" + _numb.ToString();

    //Выбираем все созданные пользователем свойства доступные для редактирования, а также свойство Title (Название)
    foreach (SPField ff in list.Fields)
    {
    if (!ff.ReadOnlyField && (ff.FromBaseType == false || ff.InternalName=="Title"))
    {
    object vv= properties.AfterProperties[ff.InternalName];
    if (vv!=null && !string.IsNullOrEmpty(vv.ToString())) prop.Add(ff.InternalName,vv.ToString());
    }
    }
    DocumentSet oDocSet = DocumentSet.Create(folderparent, _title, oCntID, prop, true);
    properties.Cancel = false; //ставил true тоже
    properties.Status = SPEventReceiverStatus.CancelWithRedirectUrl;
    properties.RedirectUrl = oDocSet.WelcomePageUrl;
    }
    }
    this.EventFiringEnabled = true;
    }

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

    ОтветитьУдалить
    Ответы
    1. Какой точно адрес передаётся в oDocSet.WelcomePageUrl?
      Возможно должен быть локальный, но точно не помню уже...

      Удалить
  9. Я пробовал создавать пустую страницу test.aspx ложил в layouts/myasp/
    properties.RedirectUrl ="/_layouts/myasp/test.aspx";
    результат тот же был...открывалась стандартная страница
    завтра на работе гляну какой именно адрес передается, но кажется там полный адрес:
    http://.../../_layouts/не_помню названия_страницы_отображения_доксета.aspx?параметры"
    вот типа такого передается.

    ОтветитьУдалить
  10. Высылайте тогда проект, посмотрю у себя.
    Можно в скайп: akimov-ru

    ОтветитьУдалить