Search code examples
c#sharepointsharepoint-2013csomprovider-hosted

Sharepoint 2013 On-Premises C# CSOM Cross Site Collection List Access


I see this has sorta been asked a bunch times before, but all the examples I am running across will not work or are in JavaScript , I NEED HELP WITH C# please.

I have a farm with server site collections on it, I successfully created a provider hosted addin/App, When it trys to access lists on the Web that launched it everything is fine! I need to try to access lists on other webs in the same farm and on the same domain does anyone have an example of C# code that can do this


Solution

  • I figured this out, so I am posting it just in case someone else needs it, also please understand that I am total noob to SharePoint and this may not be the best way or even the SharePoint Accepted way of doing things.

    First you need to give you app Tenant Permission (Full Control or Manage) ! - Very Important

    Second I created this function that make a n SharePoint Context to a site other then the one the app is running on

     public ClientContext CreateRemoteSharePointContext(string TargetWebURL, SharePointContext CurrentSharePointContext)
        {
            //In order for us to create a share point client context that points to 
            //site other then the site that this app is running we need to copy some url parameters from the current 
            //context. These parameters are found on the current share-point context 
            NameValueCollection QueryString = Request.QueryString;
            //Since, The Query string is a read only collection, a use of reflection is required to update the 
            //values on the request object,  we must use the current request object because it contains 
            //security and other headers/cookies that we need for the context to be created, Grab the url params that we need 
            //other then TargetWebUrl, that will be the url of the site we want to manipulate
            Utility.AddToReadonlyQueryString(QueryString, "SPHostUrl", CurrentSharePointContext.SPHostUrl.ToString(), System.Web.HttpContext.Current.Request);
            Utility.AddToReadonlyQueryString(QueryString, "SPAppWebUrl", TargetWebURL, System.Web.HttpContext.Current.Request);
            Utility.AddToReadonlyQueryString(QueryString, "SPLanguage", CurrentSharePointContext.SPLanguage, System.Web.HttpContext.Current.Request);
            Utility.AddToReadonlyQueryString(QueryString, "SPClientTag", CurrentSharePointContext.SPClientTag, System.Web.HttpContext.Current.Request);
            Utility.AddToReadonlyQueryString(QueryString, "SPProductNumber", CurrentSharePointContext.SPProductNumber, System.Web.HttpContext.Current.Request);
            //This is a special line, we need to get the AppOnly access token and pass it along to the target site, its is a little counter intuitive
            //Because we are using TokenHelper.GetS2SAccessToeknWithWindowsIdentity - but we pass NULL as the User identity, this will 
            //check the app manifest and if the app has a CERT and AppOnly Permission it will return a valid app only token to use
            Utility.AddToReadonlyQueryString(QueryString, "AppContextToken", TokenHelper.GetS2SAccessTokenWithWindowsIdentity(new Uri(TargetWebURL), null), System.Web.HttpContext.Current.Request);
            //Return the newly created context
            return SharePointContextProvider.Current.CreateSharePointContext(HttpContext.Request, TargetWebURL).CreateAppOnlyClientContextForSPAppWeb();
        }
    

    As you can see the I had to kinda hack up the Querystring and grab some values so here is the Utility class that does that :

    public class Utility
    {
        public static void UpdateReadonlyQueryString(NameValueCollection collectionToUpdate, string paramName, string paramValue, HttpRequest Request)
        {
    
            collectionToUpdate = (NameValueCollection)Request.GetType().GetField("_queryString", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(Request);
            PropertyInfo readOnlyInfo = collectionToUpdate.GetType().GetProperty("IsReadOnly", BindingFlags.NonPublic | BindingFlags.Instance);
            readOnlyInfo.SetValue(collectionToUpdate, false, null);
            collectionToUpdate[paramName] = paramValue;
            readOnlyInfo.SetValue(collectionToUpdate, true, null);
        }
        public static void AddToReadonlyQueryString(NameValueCollection collectionToUpdate, string paramName, string paramValue, HttpRequest Request)
        {
            collectionToUpdate = Request.QueryString;
            collectionToUpdate = (NameValueCollection)Request.GetType().GetField("_queryString", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(Request);
            PropertyInfo readOnlyInfo = collectionToUpdate.GetType().GetProperty("IsReadOnly", BindingFlags.NonPublic | BindingFlags.Instance);
            readOnlyInfo.SetValue(collectionToUpdate, false, null);
            collectionToUpdate.Add( paramName, paramValue);
            readOnlyInfo.SetValue(collectionToUpdate, true, null);
        }
    }
    

    and Finally the SharePoint access code the looks like much of the same SharePoint code out there on the web, I had to remove some stuff from it that would identify the project or who its for, but it should be easy to pick out what you need from in side

     try
    {
    
        //Get the name of the sharepoint list that needs to be updated from settings 
        var ListName = ConfigurationManager.AppSettings[Constants.SettingsConstants.SPLaunchQueList];
        var TargetSiteToUpdate =  "URL TO THE SITE YOUR TRYING TO UPDATE"; 
        //Get the sharepoint context from session 
        var spContext = <SOME HOW CREATE YOUR CONTEXT> 
        //Lets create a client context from the current sharepoint context to the target site 
        //NOTE this requires the application to HAVE Tenant level permission, it must be trusted by
        //the farm admin 
        using (var spClientContext = CreateRemoteSharePointContext(TargetSiteToUpdate, spContext))
        {
    
            //Get the current Web (Sharepoint Web) from the client context 
            var web = spClientContext.Web;
            //Load all the webs properties including title , url all the lists and get the subwebs if any as well 
            spClientContext.Load(web, x => x.Title, x => x.Url, x => x.Lists, x => x.Webs.Include(w => w.Title, w => w.Url));
            spClientContext.ExecuteQuery();
            //Lets grab the list that needs to be updated 
            SP.List OrgList = web.Lists.GetByTitle(ListName);
            //Construct a caml query Where the groupID of the SQL Server record is the same 
            //as the list GroupID 
            var caml = new Caml<DetailParts>().Where(o => o.GroupID == updateRecord.GroupID);
            CamlQuery camlQuery = new CamlQuery();
            camlQuery.ViewXml = caml.ToString();
            //Load the CAML query 
            ListItemCollection Rows = OrgList.GetItems(camlQuery);
            spClientContext.Load(Rows);
            spClientContext.ExecuteQuery();
            //The CAML Query should only return one row because GroupID should be UNQIUE 
            //however due to how sharepoint returns list data we are forcing the first value 
            //here 
            ListItem RowToUpdate = Rows[0];
            //Get a list of sharepoint columns that match the local detail parts 
            var ColumnsToUpdate = GetSharePointColumns(typeof(DetailParts));
    
             RowToUpDate["SomeColumn"] = "YOUR NEW VALUE"; 
    
             RowToUpdate.Update();
             //Commit the changes 
             spClientContext.ExecuteQuery();
            }
        }
    
    }
    catch (Exception ex)
    {
        //Log any exception and then throw to the caller 
        logger.Error("Sharepoint exception", ex);
    }
    

    That last section of code should be in a function or method of some sort I just pull out the relevant parts. As I Stated this is the only way I found that works and if someone has a better way please share it as I am not a SharePoint expert.