Tuesday, June 3, 2008

Redirection from event handler

Scenario:
recently we have a requirement, for that we need to change default behavior of SharePoint.

Here is the requirement.
• While adding Item in the list, create sub site regarding that item.
• Redirect to the newly created site.
• Same case while updating item also.

Default behavior.
• After adding or updating item your SharePoint will redirect you to the page from where you come.
• Like in list if you adding item by clicking “New Item” from “alltems.aspx” SharePoint will redirect you back to the same page “allitems.aspx”.

How to do it?• Create an event receiver like ItemAdding and ItemUpdating for that List or library.
• In that event receiver, provision site programmatically.
• After provisioning new site, redirect user to newly created sub site using SPUtility.Redirect method. (need to include Microsoft.SharePoint.Utilities)

Roadblocks
• My first road block is that HttpContext is not available in event handler.
• To resolve this problem Check this
• Now after finding a way to get HttpContext we can use SPUtility.Redirect method but if we use it item is not added and your thread will redirect.
• And you’re other events (Asynchronous) like ItemAdded and ItemUpdated will not fire.

Solution
• To resolve this problem please check Code snippet below
public override void ItemAdding(SPItemEventProperties properties)
{

//perform validation if required.

// get the list which item to be added
SPSite objsite = new SPSite (properties.SiteId);
SPWeb objweb = objsite.OpenWeb (properties.RelativeWebUrl);
SPList objlist = objweb.Lists[properties.ListId];

//use this method to disable reoccurence of events.

DisableEventFiring();
SPListItem itemToAdd = objlist.Items.Add();

//add item to list

itemToAdd.Update();
EnableEventFiring();

// provision sub site and perform required action

//redirect it to your new destination like newly provisioned sub site or any other page you want.

SPUtility.Redirect(strNewresiractionUrl, SPRedirectFlags.Trusted,current);

}

I hope you got my problem and solution. With the help of this you can find your solution according to your requirement.

And thank you very much to Eric Bartels for his superb post which was helpful to resolve this problem.

43 comments:

Sandeep said...

Hi!

A very interesting article you have here. I am able to successfully re-direct to a different page in ItemAdding synchronous event.

Your solution, which by the way is awesome, solves part of my problem.

In an ItemAdding event when I re-direct to a different page, SharePoint throws following error message:

"{Unable to evaluate expression because the code is optimized or a native frame is on top of the call stack.}" -{System.Threading.ThreadAbortException}


As a result of which ItemAdded event does not fire. Additional processing which needs to be done in ItemAdded event does not execute.

Any help on this will be much appreciated. I have been trying to solve this problem over 2 weeks now.

Cheers,
Sandeep

Parth Patel said...

Hi Sandeep,

It’s nice to hear that the given solution was helpful to you.

Now, regarding your error.
SharePoint has its event sequence
1) ItemAdding (which is Synchronous)
2) ItemAdded (which is Asynchronous)

now if you are not redirecting from event handler than this event will fire as per SharePoint standard.
But you are interpreting the flow of SharePoint and changing the thread execution from one way to other Page.

Over here you are redirecting the from Item handler so your thread which is executing item handler will aborted and new execution from where you are redirecting will start.

So it will not execute any event, which should fire logically, after redirecting from any place.

And about “System.Threading.ThreadAbortException” error,
Whenever you will use “SPUtility.Redirect” you will have this error because as I mentioned you are aborting the thread ( current execution ) and forcefully make other code to execute.

This error will not cause any problem and it comes whenever force redirection will be done.

But you can execute your code of “ItemAdding” or whatever the operation you want to do in “ItemAdding” just after enabling event after adding.

Find it in original post.
itemToAdd.Update();
EnableEventFiring();

At this time your item is added so that whatever parameter or value you want you can find after this lines and before redirection.

Hope this will help you to solve your another part of your problem.

Sriram said...

Hi

Very useful post, i had to do a redirection after a survey response is submitted. Don't know if there is any other better way to do this, but this certainly helped!!

Sriram

Sriram said...

Hi
Very useful post, i wanted to redirect to a web page after user responded to a survey. didn't know there is no way to get context in ItemAdded event handler!!
Solution you sggested did the trick!!
Thanks
Sriram

chris said...

Hi,
For some reason, after adding an item,it does NOT redirect to another site.Nothing happens!Please find below my code:

public override void ItemAdding(SPItemEventProperties properties)
{
base.ItemAdding(properties);
this.DisableEventFiring();
SPSite newSIte = new SPSite(properties.WebURl.ToString());
SPWEb newWeb = newSite.OpenWeb();
SPWebCollection subSites = newWeb.Webs;

string Reserved = porperties.AfterProperties["Reserve_x0020_a_x0020_REsource"].ToString();
if( Reserved == "True")
{
SPUtility.Redirect("http://cs2/RoomEquipReserv",SPRedirectFlags.Trusted,HttpContext.Current)
}
newSIte.Dispose();
newWeb.Dispose();
}
}
}

Parth Patel said...

hi chris,

We Checked your code.
You are missing some steps.
I’ll explain you in brief.

When we are redirecting from event handler then we are aborting the thread to another location.

As per SharePoint basic behavior if you complete item adding event only then item is added.
And over here we are not doing it.
We are redirecting the user to another page before item is added.

so to solve that what you have to do in the code of ItemAdding


DisableEventFiring();
SPListItem itemToAdd = objlist.Items.Add();

//add item to list

itemToAdd.Update();
EnableEventFiring();


Only your item added and you can redirect.

we hope this will solve your problem.

siva said...

I wrote an itemAdding event in which I am doing some validations before the iten is added and after that I am trying updating an item in a different list.

But it is not updating the second list.

public override void ItemAdding(SPItemEventProperties properties)
{
try
{
bool okHours = false;
SPList pihours = null;
SPList resources = null;
SPList tasks = null;


using (SPSite site = new SPSite(properties.WebUrl))
{
using (SPWeb web = site.OpenWeb())
{
pihours = web.Lists["PI Hours"];
resources = web.Lists["resources"];
tasks = web.Lists["tasks"];
}
}
if (resources != null && tasks != null && properties.ListId == pihours.ID)
{
string resrate = null;
string taskHours = "";
string taskalocHours = "";
double baserate = 63.95;
int selResource = int.Parse(properties.AfterProperties["Resource"].ToString());
int selHours = int.Parse(properties.AfterProperties["Hours"].ToString());
int selTask = int.Parse(properties.AfterProperties["Task"].ToString());
// Get rate of the selected resource from resource List
SPQuery query = new SPQuery();
SPListItemCollection resourceList = null;
query.ViewFields = "<FieldRef Name='Rate'/>";
query.Query =
string.Format("<Where><Eq><FieldRef Name='ID'/>" +
"<Value Type='Integer'>{0}</Value></Eq></Where>",
selResource);
resourceList = resources.GetItems(query);

if (resourceList.Count == 1)
{
resrate = resourceList[0].GetFormattedValue("Rate");
}
// Get hours remain in the selected task from the task List
SPQuery query1 = new SPQuery();
SPListItemCollection taskList = null;
query1.ViewFields = "<FieldRef Name='HoursRemain'/>" + "<FieldRef Name='Hours_x0020_Allocated'/>";
query1.Query =
string.Format("<Where><Eq><FieldRef Name='ID'/>" +
"<Value Type='Integer'>{0}</Value></Eq></Where>",
selTask);
taskList = tasks.GetItems(query1);
if (taskList.Count == 1)
{
taskHours = taskList[0]["HoursRemain"].ToString();
taskHourstaskHours = taskHours.Substring(taskHours.IndexOf("#") + 1);
taskalocHours = taskList[0]["Hours_x0020_Allocated"].ToString();
taskalocHourstaskalocHours = taskalocHours.Substring(taskalocHours.IndexOf("#") + 1);
}

// calculate and compare amounts
double amountReq = double.Parse(resrate) * selHours;
double amountAvail = baserate * double.Parse(taskHours);
if (amountReq < amountAvail)
{
okHours = true;
}
// update tasklist with new hours alloacted with this
double allocHours = selHours + double.Parse(taskalocHours);
// Update allocated hours in task list use internal Name while updating
taskList[0]["Hours_x0020_Allocated"] = allocHours;
taskList[0].Update();
tasks.Update();

if (!okHours)
{
properties.Cancel = true;
properties.ErrorMessage = "Funds not available in the selected Task.";
}
}
}

catch (Exception ex)
{
properties.Cancel = true;
properties.ErrorMessage = ex.StackTrace + ex.Message;
}
}

can you check and let meknow what the error in the code is?

The code in bold and italic is the code not working. Executing but list is not updating

Manoj Kumar Sriram said...

Hi,
Thanks for sharing, i have a small issue. I am trying to redirect to some page after when i add an item in an list. For this i used your code snippet, compiles fine but couldn't able to redirect.

Here is the code:
public class RPClass:SPItemEventReceiver
{
public override void ItemAdded(SPItemEventProperties properties)
{
GenerateID(properties);
}
void GenerateID(SPItemEventProperties properties)
{
//perform validation if required.

// get the list which item to be added
SPSite objsite = new SPSite(properties.SiteId);
SPWeb objweb = objsite.OpenWeb(properties.RelativeWebUrl);
SPList objlist = objweb.Lists[properties.ListId];

//use this method to disable reoccurence of events.

DisableEventFiring();
SPListItem itemToAdd = objlist.Items.Add();

//add item to list

itemToAdd.Update();
EnableEventFiring();

// provision sub site and perform required action

//redirect it to your new destination like newly provisioned sub site or any other page you want.

SPUtility.Redirect("http://manoj.com/subsite1/default.aspx", SPRedirectFlags.Trusted, HttpContext.Current);
}
}

When i observed during runtime, it HttpContext.Current is null.
So i think that is the reason its not redirecting i guess.

Can u help me in this.

Regards,
Manoj Sriram.

Parth Patel said...

@ "Manoj Sriram"
here is the link for how to get httpcontext in event handler

http://www.sharepointkings.com/2008/05/httpcontext-in-eventhandler.html

this link is also mentioned in the post.

Sandeep said...

Manoj,

Yes, it will not redirect. Reason being Context is available in Synchronous events only. Item Added is a asynchronous event, therefore HttpContext.Current is passed as null.

As, HttpContext.Current is passed as null,

SPUtility.Redirect("http://manoj.com/subsite1/default.aspx", SPRedirectFlags.Trusted, HttpContext.Current);

SPUtility will not redirect to control to a different page. I faced similar problem while working on a POC.

I did not look @ your code, so if it is feasible to apply the same logic in ItemAdding event, then Redirect will work like a charm.

Parth Patel said...

@ siva

in your case what I can figure out is that

the method you use for updating the list is not proper.

We can suggest one method over here

SPList spList = spWeb.Lists[strListName];
SPListItemCollection listItems = spWeb.Lists[strListName].Items;
SPListItem item = listItems.GetItemById(iItemID);
//in your case
item[“Hours_x0020_Allocated”] = allocHours;

//use allow unsafe update
spWeb.AllowUnsafeUpdates = true;
item.Update();
spWeb.AllowUnsafeUpdates = false;


this snippet is just for an idea how to update list item programmatically.
You have to modified it with your objects.

Try this way.
Hope fully it solves your problem.

Manoj Kumar Sriram said...

Hi Sandeep, Parth Patel...

As you people said, it was working well and good, Thanks for that.

but the problem is the page is redirecting before the item is created.
I commented everything except redirecting code line.

Please check the following code:

public class RPClass:SPItemEventReceiver
{
HttpContext current;
public RPClass()
{
if (current == null)
{
current = HttpContext.Current;
}
}

public override void ItemAdding(SPItemEventProperties properties)
{
GenerateID(properties);
}
void GenerateID(SPItemEventProperties properties)
{
//perform validation if required.

// get the list which item to be added
//SPSite objsite = new SPSite(properties.SiteId);
//SPWeb objweb = objsite.OpenWeb(properties.RelativeWebUrl);
//SPList objlist = objweb.Lists[properties.ListId];

////use this method to disable reoccurence of events.

////DisableEventFiring();
//SPListItem itemToAdd = objlist.Items.Add();

////add item to list

//itemToAdd.Update();
//EnableEventFiring();

// provision sub site and perform required action

//redirect it to your new destination like newly provisioned sub site or any other page you want.

SPUtility.Redirect("http://manoj.com/default.aspx", SPRedirectFlags.Trusted, current);
}
}

Parth Patel said...

hi manoj

As we had already mentioned in the post and comments that when we are redirecting means we are aborting the thread.
So before redirection you have to add item manually from code.

This is the basic code to add item in the same list for where the event handler attached.( that you commented)

// get the list which item to be added
//SPSite objsite = new SPSite(properties.SiteId);
//SPWeb objweb = objsite.OpenWeb(properties.RelativeWebUrl);
//SPList objlist = objweb.Lists[properties.ListId];

////use this method to disable reoccurence of events.

////DisableEventFiring();
//SPListItem itemToAdd = objlist.Items.Add();

////add item to list
////to add the item in the list use this way
//// itemToAdd[“Title”] = porperties.AfterProperties[“Titile”].ToString();

//itemToAdd.Update();
//EnableEventFiring();


Hopefully this will solve your problem.

Manoj Kumar Sriram said...

Guys,

I successfully solved my problem.

No need of adding manually.
Just have a glance at this code:

public class RPClass:SPItemEventReceiver
{
HttpContext current;
static object obj;
public RPClass()
{
if (current == null)
{
current = HttpContext.Current;
}
}

public override void ItemAdded(SPItemEventProperties properties)
{
current = (HttpContext)obj;
SPUtility.Redirect("http://manoj.com/default.aspx", SPRedirectFlags.Trusted, current);
}

public override void ItemAdding(SPItemEventProperties properties)
{
obj = current;
System.Threading.Thread.Sleep(5000);

}

In Synchronous, got the HttpContext and tried to maintain it till the item is added.
and in Item added event used the same HttpContext for redirecting.

Anyway, Thanks for the assistance Guys :)
You guys are really doing good work like knowledge sharing :)

But this has met my partial requirement only, Actually i need to show a message box asking for the next step, if he is really interested then i need to open an another list page where some data has to be filled from this previous page while loading itself.
So was just wondering about if there are any events to handle such situation.

Any suggestion ????

Sandeep said...

manoj,

any specific reason for adding

System.Threading.Thread.Sleep(5000);

in ItemAdding event?

Parth Patel said...

Hi Manoj,

Very good approach.
we checked your snippet and its work like a charm.
Hats off for your sharing.
we will also update our post regarding your solution.

Now about your requirement.
You have very particular requirement.
There may be some work around that you have to try we don’t know it will work or not but there will be ray of hope.

Approach 1) its looks like you are using list for inserting data. The easiest is use custom webpart instead of list forms. So that ok click code will be in your hand and insert record programmatically and do whatever you want. And for that you do not need to create your page you can replace only webpart.
Go through this link.
http://www.sharepointkings.com/2008/05/how-to-edit-list-forms-like-newformaspx.html

Approach 2) if there is constrain that you have to use list forms then you have to create custom control with ascx for particular field.
After that use JavaScript in that ascx file like
document.forms[0].onsubmit = customalert;
function customalert()
{
Alert(‘hi’);
}
Now the problem is how to get the value of yes no.
Use hidden field in that ascx. Capture that value of yes or no in that controls set value using some separation.
And in event handler separate that value and redirect accordingly.

We know it’s sounds strange and we also not sure it works or not but you can try if you have no option left.

Any ways very good solution for redirection.

Regards,
Sharepoint Kings Team.

Anonymous said...

Hi, your article is very interesting for me. I understood the way of redirection from event handler. Thank you!

By the way, how about redirection after uploading the document not the list? Do you know it is possible to redirect after uploading file?

--ishiro

Varun said...

Hey Manoj & Sharepoint kings,

I was using Manoj's code to redirect user when he adds an item in Task1 he gets redirected to Task 2. Please help me in correcting my code. Its not working for me. I would appreciate if you could help me.

Class library Code eventhandler1.cs :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;

namespace eventhandler1
{
public class Class1 : SPItemEventReceiver
{
private HttpContext current;
static object obj;

public Class1(): base()
{
if (current == null)
{
current = HttpContext.Current;
}
}
public override void ItemAdded(SPItemEventProperties properties)
{
current = (HttpContext)obj;
//Redirect

SPUtility.Redirect("Destination URL", SPRedirectFlags.Trusted, current);
}
public override void ItemAdding(SPItemEventProperties properties)
{
obj = current;
System.Threading.Thread.Sleep(0);
}
} }

Console Application COde:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
SPSite site = new SPSite(http://Site1);
SPWeb web = site.OpenWeb();
SPList def = web.Lists["List"];

string assemblyName = "eventhandler1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ea1e223d62c285df";

string className = "eventhandler1.Class1";

def.EventReceivers.Add(SPEventReceiverType.ItemAdding, assemblyName, className);

def.EventReceivers.Add(SPEventReceiverType.ItemAdded, assemblyName, className);
}
}
}

akshay

shreecanth said...

Hi,
thanks for the very good post. In my case i need to get the Id of a list item and redirect to other page with Item Id. In ItemAdding Item Id is null here redirection is working. In ItemAdded I am able to get the Item Id but redirection to other page is not happening. Please advice me in this regard.

Thanks,
shreecanth.

shreecanth said...

Hi,
nice artile. In my scenario, i have to get Item Id add redirect to other page. In ItemAdding Item Id is null, here redirection happening. In ItemAdded I am getting Item Id but redirection not happening.
Please advice me in this regard.

iyad said...

Hi Varun did you find solution to your problem? ... cause i have exactly the same issue

Parth Patel said...

hi shreecanth,

in item adding you can not find itemid. that is why in our code we are adding item.add and item.update so after that we can get itemid in item adding.

but for this you have to go through the code thouroughly. you will get itemid after item.update.

Parth Patel said...

Hi varun (akshay) and iyad ,

in ItemAdded you will not find httpconotext.

you have to add ItemAdding event and fill your static object
in ItemAdding then you will get that in ItemAdded.

Erin said...

I implemented the solution Manoj proposed and I discovered that the reason for the sleep command is to keep the original thread alive long enough for the ItemAdded event receiver to reference the HttpContext object. However, since there is really no way of knowing how long the asynchronous event handler will take, I am uneasy about using that as a solution.

In my particular case, I am trying to conditionally redirect to the initiate document approval work flow page when a user selects a checkbox on the editItem.aspx page during a document upload.

The problem I am running into is that the form field values have not yet been applied to the properties.ListItem's fields in the ItemUpdating event receiver so I feel forced to redirect in the (asynchronous)ItemAdded event receiver instead. I looked for the checkbox value in properties.AfterProperties as well but the field comes up as null.

KRANTHI said...

Is there any way we can show a custom message box when an event is fired and continue with the event

SharePoint Kings said...

Kranthi,

that is not possible from the method described here.

you can put alert on button click of the form by the javascript but can not be done by event handler.

KRANTHI said...

actually i need to show a popup meesage to user not to checkout an image if a workflow is in progress so programming on button click is ruled as i need to do it on item checking can some one suggest me a better way

SharePoint Kings said...

Kranthi,

we are not getting your exact requirement but on event handler you can check your validation and redirect user to some page without saving data, and show your custom massage or pop up, you can do whatever you want in your page.

another is you can user SPUtility.TransferToErrorPage Method to shoe sharepoint error page with custom massage.

Radi said...

Hi.

The code work, but I have strange problem with Date and Time fields.
I have custom list with 3 different content type. If the content type contain a Date and Time filed the new item is saved, but the user go to AllItems.aspx(default behavior), not to redirect url. It look like if I have Date Time field in Content time redirect command doesn't work. Can anyone help me with this problem?

SharePoint Kings said...

Radi,

have tried with removing that field with content type.

because there should be no relation between content and redirection. there might be something else that is causing your problem.

because what we are doing is simple adding item programmatically and by adding item programmatically, there should not be any redirection.

so try then with removing that field then if problem persist then it should be some other problem.

entery said...

We tried the suggestion with the manually saving of the item in itemadding, but we need to handle the attachments as well.

Manoj's code work with small attachment, but the sleeping time is too short for big attachment and you can not force the user to wait 30 second for every itempost.

Are there a good way to handle the attachments manually?

Hans R. said...

Hi,

I used this article to resolve my problem. I need to redirect to a page after the item is updated.

But can't get it to work.
Any help for the event itemupdating please? :)

Kind regards,
Hans

SharePoint Kings said...

Hans,

you can check comments. that might be resolve your problem.

Hans R. said...

Hi,

The comments where indeed helpfull.
I used the solution from Manoj, and this helpt. But did not solved it :)

This code doesn't work when you are updating a word document. I need to redirect to a page (so I can start a workflow on the initiation form) after a word document is updated.

Any idea's?

Kind regards.

Hans R. said...

Hi,

I checked the comments and they helped me a little further. I'm still facing the problem that it's not redirecting when a word document is being edited.

In my case, I need to redirect to the initiation form of a workflow after the document has been edited.

Any idea's?

Kind regards.

SharePoint Kings said...

@Hans R,

what we understood, you have to add/update item/document, that will start the workflow and you have to redirect user to that page.

if that is the case,
then what we think is you can do something like this.
after adding item wait for some time, use threding.sleep then check that item has workflow or not, then if yes then check its status then find task of that workflow then redirect to that task item.

it should work atleast theoretically.

Hans R said...

Hi,

I've tried this. It worked when I edited the listitem, but it didn't work when I made changes into the word document, and saved it again.

Kind regards

Anonymous said...

This apears to not work in SharePoint 2010 in a document lib.

SharePoint Kings said...

not tried in 2010 yet
but will try soon and let you know soon

Anonymous said...

Hi, your post is interesting.

I have quick question.
Have you tried webadding and webprovisoned in sp2010.

if you tried how to overwrite redirection to site welcome page with our custom page.

knowsharepoint@gmail.com
mkd

SharePoint Kings said...

no we had not tried web event in 2010 for redirection yet.

Anonymous said...

hi,

i am able to redirect to my custom error page when i delete item from ribbon but when i try to redirect page when i click delete option through context menu it redirects to sharepoint default error page

does anybody has a solution for this

thanks

SharePoint Kings said...

where you had write your code for redirection means how you set error page?

does your debug point hit in event handler?




Share your SharePoint Experiences with us...
As good as the SharePointKings is, we want to make it even better. One of our most valuable sources of input for our Blog Posts comes from ever enthusiastic Visitors/Readers. We welcome every Visitor/Reader to contribute their experiences with SharePoint. It may be in the form of a code stub, snippet, any tips and trick or any crazy thing you have tried with SharePoint.
Send your Articles to sharepointkings@gmail.com with your Profile Summary. We will Post them. The idea is to act as a bridge between you Readers!!!

If anyone would like to have their advertisement posted on this blog, please send us the requirement details to sharepointkings@gmail.com