I have tried to make a simple console application that retrieves list items from Share Point Online. It works fine when I retrieve list of the sites or list titles from the site, but I receive no list items when I try to get it from the particular list.
I've listed many examples of similar tasks and almost all of them were written in the same way. That's why I don't exclude that the reason of my case might be in an insufficient permissions (I attach a screenshot of the API permissions).
Please, check my code and permissions. Any help will be highly appreciated.
Application code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security;
using System.Threading.Tasks;
using Microsoft.SharePoint.Client;
using SP = Microsoft.SharePoint.Client;
namespace SharePointTrigger
{
class Program
{
static async Task Main(string[] args)
{
Uri site = new Uri("https://MyCompany.sharepoint.com/sites/MyProject");
string user = $"ServiceUser#MyCompany.com";
string rawPassword = $"password";
SecureString password = new SecureString();
foreach (char c in rawPassword) password.AppendChar(c);
using (var authenticationManager = new AuthenticationManager()) //HELPER CLASS TO OBTAIN ACCESS TOKEN
using (var context = authenticationManager.GetContext(site, user, password))
{
//RETRIVING LIST TITLE - WORKS FINE
/*
Web web = context.Web;
context.Load(web.Lists,
lists => lists.Include(list => list.Title,
list => list.Id));
context.ExecuteQuery();
foreach (SP.List list in web.Lists)
{
Console.WriteLine(list.Title);
}
*/
//RETRIVING LIST ITEM
Web myWeb = context.Web;
SP.List myList = myWeb.Lists.GetByTitle("List_of_Items");
SP.ListItemCollection listItemCollection = myList.GetItems(CamlQuery.CreateAllItemsQuery());
context.Load(listItemCollection,
eachItem => eachItem.Include(
item => item,
item => item["Title"],
item => item["ID"]
)
);
context.ExecuteQuery();
foreach (SP.ListItem listItem in listItemCollection)
{
Console.WriteLine("ID: " + listItem["ID"].ToString() + "Title: " + (string)listItem["Title"].ToString());
}
Console.ReadKey();
}
}
}
}
API permissions
API permissions
It doesn't look like permission issue on checking your app permissions or Code issue.
However, looking at the below line :
SP.ListItemCollection listItemCollection = myList.GetItems(CamlQuery.CreateAllItemsQuery());
You are trying to bring in all the items.
A similar behavior is observed of returning the empty items when the list is a very large list (>5000 or more than 12 lookup columns) or the list hits a list threshold)
If you are falling on the above scenario - you could create a index and filter the items.
You can use CAML query to filter the items instead of trying to fetch all the items in a single go !
Reference for CAML query :
https://techcommunity.microsoft.com/t5/sharepoint/filtering-list-item-using-caml-query-in-sharepoint/m-p/1415904
https://sharepoint.stackexchange.com/questions/94631/filtering-list-using-caml-query-and-binding-it-to-dropdown-in-c
Also, I would check whether the items have a item level permission.
List settings -> Advanced Settings
Ideally, you should not encounter this issue because of the above, however this settings increases number of the scopes and may cause to hit the threshold sooner because.
Related
Background; it was working with SP2013, but a supplier has switched to SP365.
Modifying the authentication using OfficeDevPnP.Core.AuthenticationManager, ClientID and ClientSecret I can get the access token. I can then do all the JSON reads I like, but it will only allow me to write two items to a list (orders), then it just times out. I restart the project and it does exactly the same. I can read the list to make sure the order hasn't been uploaded already, but when it comes to writing the third item it just throws timeout errors.
I updated the code to call for a new access token for each write and just get "Token Request Failed" after the second write.
Any thoughts on how to approach the supplier on config options, or change my approach?
Thanks in advance.
Found the answer, changing up the usage of GetAppOnlyAuthenticatedContext to something like this works wonders.
public void CreateListItemV2(string listName, QDS_WorkOrderEntry entry)
{
OfficeDevPnP.Core.AuthenticationManager authMgr = new OfficeDevPnP.Core.AuthenticationManager();
using (var context = authMgr.GetAppOnlyAuthenticatedContext(SPSiteUrl, "<clientid>", "<secret>"))
{
List list = context.Web.Lists.GetByTitle(listName);
var itemCreateInfo = new ListItemCreationInformation();
var newItem = list.AddItem(itemCreateInfo);
newItem["HHSDetails"] = entry.HHSDetails?.HHSDetailsId;
...
newItem.Update();
context.Load(newItem);
context.ExecuteQuery();
}
}
My Situation
Hi,
my client has a Sharepoint Server 2013 on Premise. He wants to write metadata on multiple files. More specifically he wants to choose a folder and then the application should write the same value in a column for every file/folder in that folder.
What I already did
I wrote a CAML-query that returns all files/folder below the chosen folder. I then write the value for each item and update it.
What I want to do
I want to write an application where my client can choose a folder. After that the software should update all files under that folder.
I want to write this application using C# and CSOM.
My Problem
This is very slow. With multiple thousands of files per folder it takes hours just for one folder to complete.
My Questions
Is there a way to speed things up?
Can CAML-Query also update values?
What is the recommended way of doing what I want to do?
Is there another way of achieving what my client wants?
Thanks for your help.
You could take advantage of feature called Request Batching, which in turn could dramatically affect the performance by minimizing the number of messages that are passed between the client and the server.
From the documentation:
The CSOM programming model is built around request batching. When you
work with the CSOM, you can perform a series of data operations on the
ClientContext object. These operations are submitted to the server in
a single request when you call the ClientContext.BeginExecuteQuery
method.
For comparison lets demonstrate two ways the updating of multiple list items:
Standard way:
foreach (var item in items)
{
item["LastReviewed"] = DateTime.Now;
item.Update();
ctx.ExecuteQuery();
}
Batched update:
foreach (var item in items)
{
item["LastReviewed"] = DateTime.Now;
item.Update();
}
ctx.ExecuteQuery();
Note: in the second approach only a single batched request is
submitted to the server
Complete example
var listTitle = "Documents";
var folderUrl = "Archive/2013";
using (var ctx = GetContext(url, userName, password))
{
//1. Get list items operation
var list = ctx.Web.Lists.GetByTitle(listTitle);
var items = list.GetListItems(folderUrl);
ctx.Load(items);
ctx.ExecuteQuery();
//2. Update list items operation
var watch = Stopwatch.StartNew();
foreach (var item in items)
{
if(Convert.ToInt32(item["FSObjType"]) == 1) //skip folders
continue;
item["LastReviewed"] = DateTime.Now;
item.Update();
//ctx.ExecuteQuery();
Console.WriteLine("{0} has been updated", item["FileRef"]);
}
ctx.ExecuteQuery(); //execute batched request
watch.Stop();
Console.WriteLine("Update operation completed: {0}", watch.ElapsedMilliseconds);
Console.ReadLine();
}
where GetListItems is the extension method for getting list items located under a specific folder:
using System.Linq;
using Microsoft.SharePoint.Client;
namespace SharePoint.Client.Extensions
{
public static class ListCollectionExtensions
{
/// <summary>
/// Get list items located under specific folder
/// </summary>
/// <param name="list"></param>
/// <param name="relativeFolderUrl"></param>
/// <returns></returns>
public static ListItemCollection GetListItems(this List list, string relativeFolderUrl)
{
var ctx = list.Context;
if (!list.IsPropertyAvailable("RootFolder"))
{
ctx.Load(list.RootFolder, f => f.ServerRelativeUrl);
ctx.ExecuteQuery();
}
var folderUrl = list.RootFolder.ServerRelativeUrl + "/" + relativeFolderUrl;
var qry = CamlQuery.CreateAllItemsQuery();
qry.FolderServerRelativeUrl = folderUrl;
var items = list.GetItems(qry);
return items;
}
}
}
the web shows dozens of examples to query the exchange's global address list but i want to query the specific address lists! So every user in our Enterprise is ofcourse listed in our global address list but i want to query the address list of a specific company within our Enterprise.
In the example below, Aswebo, Cosimco, etc.. are address lists.
How do I list these address lists?
How do I list the people within these address lists?
I don't have exchange setup to test this code, so it will need modifications but it should give you a starting point to explore.
The idea is that you set the ItemView to the ContactSchema to retrieve results by company.
// Get the number of items in the Contacts folder. To keep the response smaller, request only the TotalCount property.
ContactsFolder contactsfolder = ContactsFolder.Bind(service,
WellKnownFolderName.Contacts,
new PropertySet(BasePropertySet.IdOnly, FolderSchema.TotalCount));
// Set the number of items to the smaller of the number of items in the Contacts folder or 1000.
int numItems = contactsfolder.TotalCount < 1000 ? contactsfolder.TotalCount : 1000;
// Instantiate the item view with the number of items to retrieve from the Contacts folder.
ItemView view = new ItemView(numItems);
view.PropertySet = new PropertySet(ContactSchema.CompanyName, ContactSchema.EmailAddress1);
// Retrieve the items in the Contacts folder that have the properties you've selected.
FindItemsResults<Item> contactItems = service.FindItems(WellKnownFolderName.Contacts, view);
foreach(var contact in contactItems)
{
Contact contact = item as Contact;
// Filter / Group by company name
// contact.Companyname
}
You can also use service.FindItems(WellKnownFolderName, SearchFilter, ViewBase) to provide additional filtering.
See this MSDN blog for a code example.
I've been searching all afternoon and came up with the code below. It works.. but looking dirty. I would like an entire Principal approach but it seems I'm too dumb :-)
Anyone that wants to translate this code to 100% 'System.DirectoryServices.AccountManagement'?
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
DirectoryEntry ldap;
DirectorySearcher ldap_search;
SearchResultCollection ldap_results;
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
var addressLists = new Dictionary<string, string>();
// Flexible way (but visually complex!) for building the path LDAP://CN=All Address Lists,CN=Address Lists Container,CN=First Organization,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=DOMAIN,DC=local
ldap = new DirectoryEntry("LDAP://RootDSE");
ldap_search = new DirectorySearcher(new DirectoryEntry("LDAP://CN=Microsoft Exchange, CN=Services," + ldap.Properties["configurationNamingContext"].Value), "(objectClass=msExchOrganizationContainer)");
ldap_search = new DirectorySearcher(new DirectoryEntry("LDAP://CN=All Address Lists,CN=Address Lists Container," + ldap_search.FindOne().Properties["distinguishedName"][0]), "(objectClass=addressBookContainer)");
ldap_search.Sort = new SortOption("name", SortDirection.Ascending);
// Find All Address Lists alphabetically and put these into a dictionary
ldap_results = ldap_search.FindAll();
foreach (SearchResult ldap_result in ldap_results)
{
var addressList = new DirectoryEntry(ldap_result.Path);
addressLists.Add(addressList.Properties["name"].Value.ToString(), addressList.Properties["distinguishedName"][0].ToString());
}
//// list Address Lists
//foreach (var addressList in addressLists) Console.WriteLine(addressList.Key);
// List all users from Address List "Aswebo"
ldap = new DirectoryEntry("LDAP://" + ldap.Properties["defaultNamingContext"].Value); // rename ldap to LDAP://DC=DOMAIN,DC=local
ldap_search = new DirectorySearcher(ldap, string.Format("(&(objectClass=User)(showInAddressBook={0}))", addressLists["Aswebo"])); // Search all users mentioned within the specified address list
ldap_results = ldap_search.FindAll();
foreach (SearchResult ldap_result in ldap_results)
{
// Fetch user properties using the newer interface.. just coz it's nice :-)
var User = UserPrincipal.FindByIdentity(ctx, IdentityType.DistinguishedName, ldap_result.Path.Replace("LDAP://", ""));
Console.WriteLine(User.DisplayName);
}
Console.ReadLine();
}
}
}
right now, I'm using the SPSecurity.RunWithElevatedPrivileges method to let anonymous users add list items to a list. What i would like to do is make a general method that takes a Site, List and List item as an argument and adds the item to the list being passed. Right now I have :
public static void AddItemElevated(Guid siteID, SPListItem item, SPList list)
{
SPSite mySite = SPContext.Current.Site;
SPList myList = WPToolKit.GetSPList(mySite, listPath);
SPWeb myWeb = myList.ParentWeb;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite eleSite = new SPSite(mySite.ID))
{
using (SPWeb eleWeb = eleSite.OpenWeb(myWeb.ID))
{
eleWeb.AllowUnsafeUpdates = true;
SPList eleList = eleWeb.Lists[myList.Title];
SPListItem itemToAdd = list.Items.Add();
itemToAdd = item;
itemToAdd.Update();
eleWeb.AllowUnsafeUpdates = false;
}
}
});
}
The problem is that 'item' gets initialized outside of the elevated privileges so when 'itemToAdd' is set to 'item' it loses its elevated privileges, causing the code to break at 'item.update()' if used my an non-privileged user.
Any Thoughts?
The problem could be because you are passing in your list. Try just passing in the list name and then grabbing the list from the elevated web like this:
public static void AddItemElevated(SPListItem itemToAdd, string listName)
{
SPWeb web = SPContext.Current.Web;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite elevatedSite = new SPSite(web.Url))
{
using (SPWeb elevatedWeb = elevatedSite.OpenWeb())
{
elevatedWeb.AllowUnsafeUpdates = true;
SPList list = elevatedWeb.Lists[listName];
SPListItem item = list.Items.Add();
item = itemToAdd;
item.Update();
elevatedWeb.AllowUnsafeUpdates = false;
}
}
}
}
Following line itemToAdd = item; does something strange - you adding item to one list (with list.Items.Add() ) but updating item from another list/location (one that comes as argument).
Not sure what you actually want, but maybe you want to co copy all fileds from item to itemToAdd. Consider in this case to pass fieldName/value pairs as argument to make it clear that you are adding new item with given values.
Note, that anonymous users are able to add items to lists that explicitly allow it.
I haven't tried it but possibly this could help - http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.splistitem.copyto.aspx
Regards,
Nitin Rastogi
If item is coming from an SPList.AddItem() method, the splist instance must be get from an elevated web. otherwise this code will always break for anonymous users.
or you can allow anonymous user to add item to list, so you won't need running the code with elevated privileges.
by the way, itemToAdd = item; is not a correct way of setting the new added item to an old instance.
I created web part (something like wizard) and need change item value in list, but when get list item, they haven't items (logged user haven't access to this list). Can I ignore sharepoint permission, and update this value?
I use LINQ to sharepoint and get context:
using (SystemOcenContextDataContext ctx = new SystemOcenContextDataContext("http://sh2010/sites/270"))
{
// code :)
}
Update:
make test when get list using:
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite ElevatedSite = new SPSite("http://sh2010/sites/270"))
{
using (SPWeb ElevatedWeb = ElevatedSite.OpenWeb())
{
list = ElevatedWeb.Lists["Ankiety i oceny"];
}
}
});
the object list "have" items
but in my project I use sharepoint linq datacontext when using:
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SystemOcenContextDataContext ctx = new SystemOcenContextDataContext("http://sh2010/sites/270"))
{
item = ctx.AnkietyIOceny.First();
}
});
the context(ctx) didn't have any items :/
any idea?
SPSecurity.RunWithElevatedPrivileges(delegate()
{
// Pur your code here.
});
Get more details Here
The SharePoint linq provides doesn't work with ElevatedPrivileges. It accesses the SPWeb.Current instance which will have the access rights of the request and not the elevated user.
http://jcapka.blogspot.com/2010/05/making-linq-to-sharepoint-work-for.html
There's a work around, which I've implemented generally the same thing. It's a big awkward but it works as far as I can tell.