Thursday, March 21, 2013

SharePoint 2013 Event Receiver Redirect

It used to be a standard practice to use redirect in event receivers in SharePoint 2010 and SharePoint 2007. SharePoint 2013 has some changes in this functionality.

Redirect Option Availability

No redirect options available for ItemAdded, ItemUpdated and ItemDeleted.

Some redirect options available for ItemAdding, ItemUpdating and ItemDeleting. They are only available when the form, which initiates an event, is being rendered in CSRRenderMode.ServerRender mode. Otherwise list forms are committed through asynchronous XmlHttpRequests, and redirect options are not available.

CancelWithRedirectUrl - DOES NOT WORK

The following code does function:

properties.RedirectUrl = "http://www.google.com";
properties.Status = SPEventReceiverStatus.CancelWithRedirectUrl;
Compiler is issuing the following warning:
'Microsoft.SharePoint.SPEventReceiverStatus.CancelWithRedirectUrl' is obsolete: '"Default list forms are committed through asynchronous XmlHttpRequests, so redirect urls specified in this way aren't followed by default. In order to force a list form to follow a cancelation redirect url, set the list form web part's CSRRenderMode property to CSRRenderMode.ServerRender"'. If you need to figure out how to set CSRRenderMode.ServerRender property for the form automatically (in list definition), please refer to my other blog post

SPUtility.Redirect - DOES NOT WORK

SPUtility.Redirect does not work in Event Receivers any more. It actually throws an exception. The reason - SPUtility.Redirect relies on HttpContext.Current, which does not exist at that point of execution. Tested with different CSRRenderMode settings.

[SubsetCallableExcludeMember(SubsetCallableExcludeMemberType.UnsupportedExternalType)]
public static bool Redirect(string url, SPRedirectFlags flags, HttpContext context, string queryString)
{
    string urlRedirect = null;
    bool flag = DetermineRedirectUrl(url, flags, context, queryString, out urlRedirect);
    if (flag)
    {
        bool flag2 = SPRedirectFlags.DoNotEndResponse == (flags & SPRedirectFlags.DoNotEndResponse);
        bool flag3 = DeltaPage.IsDeltaRedirectDisabled(context);
        if (!flag3 && DeltaPage.IsRenderingDelta(context))
        {
            DeltaPage handler = context.Handler as DeltaPage;
            if (handler != null)
            {
                handler.Redirect(context, urlRedirect, !flag2);
            }
            return flag;
        }
        bool flag4 = ((!flag3 && IsMDSEnabled(context)) && (!IsDialogUrl(urlRedirect) && IsBrowserRequest(context.Request))) && (SPRedirectFlags.AttemptMDSNavigate == (flags & SPRedirectFlags.AttemptMDSNavigate));
        bool flag5 = false;
        if (flag4)
        {
            flag5 = DeltaPage.AttemptMDSRedirect(context, urlRedirect, !flag2);
        }
        if (flag4 && flag5)
        {
            return flag;
        }
        SPLongOperation currentLongOperation = SPLongOperation.CurrentLongOperation;
        if (currentLongOperation != null)
        {
            currentLongOperation.End(url, flags, context, queryString);
            return flag;
        }
        if (Utility.IsClientQuery(context) && !Utility.IsRedirectAllowed(context))
        {
            RedirectException exception = new RedirectException(urlRedirect);
            context.Items[typeof(RedirectException)] = exception;
            throw exception;
        }
        try
        {
            context.Response.Redirect(urlRedirect, !flag2);
        }
        catch (Exception exception2)
        {
            if ((exception2 is ThreadAbortException) && !flag2)
            {
                throw;
            }
            ULS.SendTraceTag(0x62613371, ULSCat.msoulscat_WSS_Runtime, ULSTraceLevel.Medium, "Redirect to {0} failed. Exception: {1}", new object[] { url, exception2.ToString() });
        }
    }
    return flag;
}


internal static bool IsMDSEnabled(HttpContext ctx)
{
    return (((((ctx != null) && !DeltaPage.IsStartPage) && (!SPMobileUtility.IsMobilePageRequest(ctx, ctx.Request.Browser) && (ContextCompatibilityLevel >= 15))) && ((SPContext.Current != null) && (SPContext.Current.Web != null))) && SPContext.Current.Web.EnableMinimalDownload);
}


public static bool IsStartPage
{
    get
    {
        return IsMDSStartPageUrl(SPAlternateUrl.ContextUri.AbsolutePath);
    }
}


public static Uri ContextUri
{
    get
    {
        return GetContextUri(HttpContext.Current); //Here it throws an exception
    }
}

WORKING SOLUTION:

_currentContext.Response.Redirect

_currentContext.Response.Redirect does work though (IMPORTANT: Only when the form, which initiates an event, is being rendered in CSRRenderMode.ServerRender mode.) You can use the following sample:

public class TestEventHandler : SPItemEventReceiver
 {
  private readonly HttpContext _currentContext;
  public TestEventHandler()
  {
   _currentContext = HttpContext.Current;
  }
  // Methods
  public override void ItemAdding(SPItemEventProperties properties)
  {
   var url = new StringBuilder("test.aspx");
   string urlRedirect = null;
   bool flag = SPUtility.DetermineRedirectUrl(url.ToString(), SPRedirectFlags.RelativeToLayoutsPage, _currentContext, null, out urlRedirect);
   _currentContext.Response.Redirect(urlRedirect, true);
  }
 }

9 comments:

  1. Hi Paul,

    Do you know any way to redirect in SharePoint 2013? Thanks in advance.

    Mario

    ReplyDelete
    Replies
    1. _currentContext.Response.Redirect within ItemAdding event receiver.

      Delete
  2. Hi again, Paul

    I’ve tried the example code you show on your post about "_currentContext.Response.Redirect", but two problems have raised:
    1. When I try to use 'DetermineRedirectUrl' method on class 'SPUtility' Visual Studio 2012 returns me the next error: "'Microsoft.SharePoint.Utilities.SPUtility' does not contain a definition for 'DetermineRedirectUrl'". In my project I’ve added Microsoft.SharePoint.dll and Microsoft.SharePoint.Client.ServerRuntime.dll references and, in the class, I’ve added Microsoft.SharePoint y Microsoft.SharePoint.Utilities usings.
    2. I’ve tried to solve the redirection without 'DetermineRedirectUrl' method but, when I try to use _currentContext object (i.e.: _currentContext.Response.Redirect("page.aspx", true)), on saving an element on Sharepoint list it returns me the next error: "That assembly does not allow partially trusted callers".

    I wonder if you could advise me with a different way to solve these problems.

    Thanks in advance for your support.
    Best regards

    ReplyDelete
    Replies
    1. You should be able to use DetermineRedirectUrl: http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.utilities.sputility.determineredirecturl.aspx It feels like something is wrong in the solution you are testing, so you should carefully check on what exactly you are doing, also do not forget to set CSRRenderMode.ServerRender for the form

      Delete
  3. Hi Paul,
    Do you know any way to redirect in item updating in sp2013? Thanks in advance.
    Gautam

    ReplyDelete
  4. Hi Paul,
    same question as gautamsehth - do you know a way to Redirect in item updating in sp2013. If I'm using your code an error message is displayed to me which says something like the form can not be processed.

    ReplyDelete
  5. Sorry guys - never researched this problem for item updating. My guess would be that you need to make sure you are rendering your form in CSRRenderMode.ServerRender mode. http://blog.sharepointalist.com/2013/03/custom-listformwebpart-list-definition.html

    ReplyDelete
  6. As I work on prepping my web parts for MOSS 2013 upgrade, I'm running into little issues like this one that require minor code tweaks.

    Thanks for documenting this issue regarding SPUtility.Redirect and providing a work around.

    ReplyDelete
  7. Is it will to cancel the event (when redirection is fired) and the item will not be added?

    ReplyDelete