Will using a SPListItemCollection returned from a function reopen the SPWeb? - sharepoint

After reading Stefan Gossner's post about disposing objects and this question about Cross method dispose patterns, I found that I was guilty of accidentally reopening some SPWebs. I know in Stefan Gossner's post he mentions you should dispose of an SPWeb after you are finished with any child object. However, the microsoft documentation mentions Caching the SPListItemCollection object. Is the following code correct? Would the returned SPListItemCollection reopen an SPWeb object? Is there any way to tell for sure?
// is this correct????
private SPListItemCollection GetListItems()
{
SPListItemCollection items = null;
try
{
using (SPSite site = new SPSite(GetListSiteUrl()))
{
using (SPWeb web = site.OpenWeb())
{
// retrieve the list
SPList list = web.Lists[_ListName];
// more code to create the query...
items = list.GetItems(query);
}
}
}
catch (Exception e)
{
// log error
}
return items;
}
Edit 09/09/09
I am mainly referring to this part of Stefan Grossner's post:
You should dispose a SPWeb or SPSite
object after the last access to a
child object of this object.
I believe what he is saying is that if I use the SPListItemCollection after I dispose of the SPWeb that I used to get it... the SPWeb will be reopened automatically.

I found out after asking Stefan directly that the SPListItemCollection can indeed reopen the SPWeb after you dispose of it. This means that my code posted above is INCORRECT and I would only be able to dispose of the SPWeb after I use the SPListItemCollection.
Update: It is better to convert to the SPListItemCollection to something else and return that instead.
private DataTable GetListItems()
{
DataTable table = null;
try
{
SPListItemCollection items = null;
using (SPSite site = new SPSite(GetListSiteUrl()))
{
using (SPWeb web = site.OpenWeb())
{
// retrieve the list
SPList list = web.Lists[_ListName];
// more code to create the query...
items = list.GetItems(query);
// convert to a regular DataTable
table = items.GetDataTable();
}
}
}
catch (Exception e)
{
// log error
}
return table;
}

As far as I know the answer is no, but I would have written the code something like
private void FetchItems(Action<SPListItemCollection> action)
{
using(...)
{
var items = list.GetItems(query);
action(items);
}
}
By doing this, to call this method you would need to send a method along (delegate) that the SPListItemCollection should be used for, an example:
FetchItems( items => ....) or FetchItems( DoStuffWithItems(SPListItemCollection) )

If you are talking about whether you need an SPWeb in the same scope when you get around to using the SPListItemCollection, I think the answer is no.
For example, I routinely do the following:
private IEnumerable<SPListItem> AllItems;
public void GetItems()
{
var results = SPContext.Current.Web.Lists[ListName].Items.Cast<SPListItem>();
this.AllItems = results;
}
and then I use AllItems all over the place, and it works fine.
Incase you are wondering, the cast is done so I can use Linq on the result set - much much faster than submitting a query to the list, especially if you are doing multiple subselects on the data.

Related

Can't use SPItemEventProperties ListItem on ItemAdded in Event Receiver

I'm using event receivers to modify some of the inputs in a SharePoint 2013 site.
They are fairly straight forward, here is a simple example
public override void ItemAdded(SPItemEventProperties properties)
{
base.ItemAdded(properties);
using (SPSite site = new SPSite(properties.WebUrl))
{
using (SPWeb web = site.OpenWeb(properties.RelativeWebUrl))
{
//web.AllowUnsafeUpdates = true;
SPListItem item = properties.ListItem; // Boom!
var title = item["Title"].ToString();
item["Title"] = title.Replace(" ", "_");
//item.Update();
//item.SystemUpdate(false);
}
}
}
This renders the error
Message:
Method not found: 'Microsoft.BusinessData.Runtime.IEntityInstance Microsoft.BusinessData.Runtime.NotificationParser.GetChangedEntityInstance(Microsoft.BusinessData.MetadataModel.IEntity, Microsoft.BusinessData.MetadataModel.ILobSystemInstance)'.
Source:
Microsoft.SharePoint
StackTrace:
at Microsoft.SharePoint.SPItemEventProperties.get_ListItem()
at eventreceivers.Kundregister.PrivateCustomer.PrivateCustomer.<>c__DisplayClass2.<ItemAdded>b__0()
at Microsoft.SharePoint.SPSecurity.<>c__DisplayClass5.<RunWithElevatedPrivileges>b__3()
at Microsoft.SharePoint.Utilities.SecurityContext.RunAsProcess(CodeToRunElevated secureCode)
I have ensured that those methods are available in the class.
Any advices are highly appreciated, thanks!
I was already facing the same issue.
instead of using
SPListItem item = properties.ListItem;
use following code to get item,
SPListItem item = properties.Web.Lists.TryGetList(properties.ListTitle).GetItemById(properties.ListItemId);
All the best!
Regards,
Praveen Singh

Cannot add new item to list in ItemAdded Event Receiver

Can anybody tell me why this code doesn't work?
The "adding code" itself works, but unfortunately not in an ItemAdded Event.
I need this code in the ItemAdded Event and therefor i cannot use ItemAdding.
Thanks for any help.
public override void ItemAdded(SPItemEventProperties properties)
{
SPSite site = new SPSite("http://air_sim:39167/");
SPWeb web1 = site.RootWeb;
SPList List = web1.Lists["Announcements"];
SPListItem newitem = List.Items.Add();
newitem["Title"] = "Example";
newitem.Update();
}
Did you do any steps to attach event receiver to your list?
If no, you can install a feature to manage event receivers and
verify that the event receiver is added and if not, add it manually:
http://chrissyblanco.blogspot.com/2007/08/event-receiver-management.html
Maybe exception is thrown somwere? For example, if such site or list
with such name doesn't exist, exception will be thrown. Also if you
don't initialise required fields of your item, the Update() call
will throw exception.
By the way the properties variable contains many useful properties:
SPListItem newitem = properties.List.Items.Add();
newitem["Title"] = "Example";
newitem.Update();
Do you use Sharepoint 2010 or Sharepoint2007?
Do you use VS2008 or VS2010?
If you couldn’t use debugger, use EventLog:
public override void ItemAdded(SPItemEventProperties properties)
{
EventLog.WriteEntry("DebugSharepoint", "ItemAdded fired");
try
{
SPSite site = new SPSite("http://air_sim:39167/");
SPWeb web1 = site.RootWeb;
SPList List = web1.Lists["Announcements"];
SPListItem newitem = List.Items.Add();
newitem["Title"] = "Example";
newitem.Update();
}
catch(Exception e)
{
EventLog.WriteEntry("DebugSharepoint", e.Message, EventLogEntryType.Error);
}
}
Attach a debugger.
Go to cmd and type iisapp. You would get the worker process id.
Then open your event handler project and go to the tools and attach process and set the debug point on ItemAdded as well as ItemAddding event
Try the below solutions:
Check whether the Site exists with that name.
Check whether user has the permission to insert item.
Try using AllowUnsafeUpdates:
SPSite site = new SPSite("site address");
SPWeb web1 = site.RootWeb;
SPList List = web1.Lists["Announcements"];
web1.AllowUnsafeUpdates = true;
SPListItem newitem = List.Items.Add();
newitem["Title"] = "Example";
newitem.Update();
web1.AllowUnsafeUpdates = false;

how to update infopath form library item field programatically?

I was successfully able to update one of the fields (which was of type boolean) from infopath for library item using sharepoint object Model as if it was a list item.
But for another field which is of type text, the same code just gets executed but does not change the field value !!!!
I am using following code, which works for that boolean field but for another field of type string , not sure why it is not working. Any idea ?
SPSecurity.RunWithElevatedPrivileges(delegate()
{
SPWeb web;
SPSite site = new SPSite("http://sharepointsite");
web = site.OpenWeb();
SPList formLibList = web.Lists["FormLibraryName"];
SPQuery query = new SPQuery(); query.Query = "<Where><Eq><FieldRef Name='Title' /><Value Type='Text'>" + titleName + "</Value></Eq></Where>";
web.Site.WebApplication.FormDigestSettings.Enabled = false;
web.AllowUnsafeUpdates = true;
SPListItemCollection col = formLibList.GetItems(query);
if (col.Count > 0)
{
col[0]["CustomerName"] = "test customer name";
col[0].Update();
}
web.Site.WebApplication.FormDigestSettings.Enabled = true; web.AllowUnsafeUpdates = false;
});
Thanks,
Nikhil
I had to declare SPListItem and set it instead of directly modifying list item collection.
It's not an answer to your question (you already found the solution yourself), but you may want to put your SPSite and SPWeb objects in a using block. In your example code you are not disposing them, which results in a memory leak. The correct way would be like this:
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite site = new SPSite("http://sharepointsite"))
{
using (SPWeb web = site.OpenWeb())
{
// the rest of your code
}
}
});

Retrieve all Master Pages for a SharePoint Site?

How can I determine programmatically what master pages (custom and OOTB) that are available for to use for a web site in SharePoint?
Thanks, MagicAndi
I came up with this solution, making use of a SPQuery object to query the team site collection's Master Page Gallery list:
try
{
using (SPSite site = new SPSite(this.ParentSiteUrl))
{
using (SPWeb web = site.OpenWeb())
{
SPList myList = web.Lists["Master Page Gallery"];
SPQuery oQuery = new SPQuery();
oQuery.Query = string.Format("<Where><Contains><FieldRef Name=\"FileLeafRef\" /><Value Type=\"File\">.master</Value></Contains></Where><OrderBy><FieldRef Name=\"FileLeafRef\" /></OrderBy>");
SPListItemCollection colListItems = myList.GetItems(oQuery);
foreach (SPListItem currentItem in colListItems)
{
// Process master pages
}
}
}
}
catch (Exception ex)
{
}
Use reflection and check whether the type's base type equals System.Web.UI.MasterPage.
So something along the lines of:
foreach(Type t in Assembly.GetExecutingAssembly().GetTypes())
{
if (t.BaseType==typeof(MasterPage))
{
// do something, add to collection - whatever
}
}
But, depending on in what assembly your MasterPages are defined, and the fact it iterates over all the types in a specific assembly, it may definitely not be the best solution.
I am blissfully ignorant about SharePoint, but this solution is somewhat more generic I guess.

Sharepoint SPDisposeCheck

The SPDisposeCheck utility alerted me to an impproperly disposed SPWeb.Add call. As you can see below, the typical using(SPWeb NewWeb = webs.add(siteUrl ....) method would not work because of the RunWithElevatedPrivileges would make the return newWeb out of context.
By looking at the newWeb = webs.Add() line below, can anyone suggest a way to properly dispose the new SPWeb object? Thanks in advance.
public partial class SiteHelper
{
public static SPWeb CreateSiteFromSTP(SPWeb parentWeb, string newSiteSTP, int teamId)
{
try
{
SPWeb newWeb = null;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
string siteUrl = teamId.ToString();
SPWebCollection webs = parentWeb.Webs;
newWeb = webs.Add(siteUrl,.,.,.,);
TraceProvider.WriteLine("Activating Feature : MembersFeature ");
newWeb.Features.Add(new Guid(TeamSiteAttributes.MembersFeature), true);
TraceProvider.WriteLine("Activating Feature : BadgeAwardsFeature ");
newWeb.Features.Add(new Guid(TeamSiteAttributes.BadgeAwardsFeature), true);
TraceProvider.WriteLine("Activating Feature : ProjectBenefitsFeature ");
newWeb.Features.Add(new Guid(TeamSiteAttributes.ProjectBenefitsFeature), true);
TraceProvider.WriteLine("Activating Feature : TeamScoreFeature ");
newWeb.Features.Add(new Guid(TeamSiteAttributes.TeamScoreFeature), true);
newWeb.Update();
parentWeb.Update();
});
return newWeb;
}
catch (Exception ex)
{
TraceProvider.WriteLine("Error", ex);
throw;
}
}
}
SPDisposeCheck is reporting this as an error because it's not smart enough to know what you are doing with newWeb once you've returned it from this method. As long as you dispose of newWeb after your call to CreateSiteFromSTP() then you won't have a memory leak.
If you are confident that you don't have a memory leak in this method, you can set SPDisposeCheck to ignore just this particular warning. Just add the following declaration (with the correct SPDisposeCheckID number you received) above your CreateSiteFromSTP method:
[SPDisposeCheckIgnore(SPDisposeCheckID.SPDisposeCheckID_110, "Caller will dispose")]
This is mostly a question of best practices for safe cleanup in this case... would it be better for this method to return a GUID or the url for the new SPWeb rather than the actual SPWeb that was created? That way this method could properly dispose of the SPWeb it created, and the caller would still have an opportunity to easily create another SPWeb whose lifetime is less mysterious. What's the real cost in creating an SPWeb compared to the risk of passing one around and potentially neglecting proper cleanup?
The proper way to dispose of your new SPWeb is in the scope where it was created. If you need to perform additional operations on your new web, just pass in a delegate to call:
public static void CreateSiteFromSTP(SPWeb parentWeb, string newSiteSTP, int teamId, Action<SPWeb> actionOnCreate)
{
// ...
using(var newWeb = webs.Add(...))
{
// ...
newWeb.Update();
actionOnCreate(newWeb);
}
}
Then you can just pass in a method (anonymous or named) that manipulates your new SPWeb, without responsibility for disposal being passed around. That approach also has the benefit of not requiring that you return the SPWeb outside your elevated block, which is unsupported and unreliable.
In fact, I would be surprised to find that your code actually works as you intend: existing SharePoint objects (specifically parentWeb) have their permissions set when they are created and should also not be passed into an elevated context. A better approach for elevating permissions within SharePoint is to use SPSite impersonation. Using a RunAsSystem method defined here, I would refactor your code like this:
public static void ElevateToCreateSiteFromSTP(SPWeb parentWeb, string newSiteSTP, int teamId, Action<SPWeb> actionOnCreate)
{
parentWeb.RunAsSystem(elevWeb =>
CreateSiteFromSTP(elevWeb, newSiteSTP, teamId, actionOnCreate));
}
private static void CreateSiteFromSTP(SPWeb parentWeb, string newSiteSTP, int teamId, Action<SPWeb> actionOnCreate)
{
try
{
string siteUrl = teamId.ToString();
SPWebCollection webs = parentWeb.Webs;
using(var newWeb = webs.Add(siteUrl, ...))
{
var newWebFeatures = newWeb.Features;
TraceProvider.WriteLine("Activating Feature : MembersFeature ");
newWebFeatures.Add(new Guid(TeamSiteAttributes.MembersFeature), true);
TraceProvider.WriteLine("Activating Feature : BadgeAwardsFeature ");
newWebFeatures.Add(new Guid(TeamSiteAttributes.BadgeAwardsFeature), true);
TraceProvider.WriteLine("Activating Feature : ProjectBenefitsFeature ");
newWebFeatures.Add(new Guid(TeamSiteAttributes.ProjectBenefitsFeature), true);
TraceProvider.WriteLine("Activating Feature : TeamScoreFeature ");
newWebFeatures.Add(new Guid(TeamSiteAttributes.TeamScoreFeature), true);
newWeb.Update();
parentWeb.Update();
if(actionOnCreate != null)
actionOnCreate(newWeb);
}
}
catch (Exception ex)
{
TraceProvider.WriteLine("Error", ex);
throw;
}
}
This has the added benefit of separating your elevation concerns from the logic to create the SPWeb. I also prefer to make it quite obvious where my code will be running with different permissions.

Resources