diff --git a/.gitignore b/.gitignore index a122c5f6..35e76fe9 100644 --- a/.gitignore +++ b/.gitignore @@ -120,4 +120,10 @@ artifacts # Test testrunner -scripts/output.txt \ No newline at end of file +scripts/output.txt +/.vs +/.vs +/.vs/slnx.sqlite +/src/.vs/config +/src/.vs/config +/src/.vs/config/applicationhost.config diff --git a/samples/CompositeConsole/App.config b/samples/CompositeConsole/App.config new file mode 100644 index 00000000..8d725cbd --- /dev/null +++ b/samples/CompositeConsole/App.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/CompositeConsole/CompositeConsole.csproj b/samples/CompositeConsole/CompositeConsole.csproj new file mode 100644 index 00000000..9e290683 --- /dev/null +++ b/samples/CompositeConsole/CompositeConsole.csproj @@ -0,0 +1,66 @@ + + + + + Debug + AnyCPU + {95CF1FA1-98E8-411C-89BF-DB7D086F27CE} + Exe + CompositeConsole + CompositeConsole + v4.6.1 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + Designer + + + + + {688BC3A2-29C2-48F7-BC28-7A81ABF5C3D3} + CommonLibrariesForNET + + + {1CC985BC-27F3-4F1D-8946-F4DDB084B58F} + ForceToolkitForNET + + + + \ No newline at end of file diff --git a/samples/CompositeConsole/CompositeConsole.sln b/samples/CompositeConsole/CompositeConsole.sln new file mode 100644 index 00000000..8cf17fb8 --- /dev/null +++ b/samples/CompositeConsole/CompositeConsole.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26228.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CompositeConsole", "CompositeConsole.csproj", "{95CF1FA1-98E8-411C-89BF-DB7D086F27CE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ForceToolkitForNET", "..\..\src\ForceToolkitForNET\ForceToolkitForNET.csproj", "{1CC985BC-27F3-4F1D-8946-F4DDB084B58F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommonLibrariesForNET", "..\..\src\CommonLibrariesForNET\CommonLibrariesForNET.csproj", "{688BC3A2-29C2-48F7-BC28-7A81ABF5C3D3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {95CF1FA1-98E8-411C-89BF-DB7D086F27CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {95CF1FA1-98E8-411C-89BF-DB7D086F27CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {95CF1FA1-98E8-411C-89BF-DB7D086F27CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {95CF1FA1-98E8-411C-89BF-DB7D086F27CE}.Release|Any CPU.Build.0 = Release|Any CPU + {1CC985BC-27F3-4F1D-8946-F4DDB084B58F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1CC985BC-27F3-4F1D-8946-F4DDB084B58F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1CC985BC-27F3-4F1D-8946-F4DDB084B58F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1CC985BC-27F3-4F1D-8946-F4DDB084B58F}.Release|Any CPU.Build.0 = Release|Any CPU + {688BC3A2-29C2-48F7-BC28-7A81ABF5C3D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {688BC3A2-29C2-48F7-BC28-7A81ABF5C3D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {688BC3A2-29C2-48F7-BC28-7A81ABF5C3D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {688BC3A2-29C2-48F7-BC28-7A81ABF5C3D3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/samples/CompositeConsole/ExtensionMethods.cs b/samples/CompositeConsole/ExtensionMethods.cs new file mode 100644 index 00000000..2e7df00b --- /dev/null +++ b/samples/CompositeConsole/ExtensionMethods.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CompositeConsole +{ + public static class ExtensionMethods + { + public static IEnumerable> Partition(this IList source, Int32 size) + { + for (int i = 0; i < (source.Count / size) + (source.Count % size > 0 ? 1 : 0); i++) + yield return new List(source.Skip(size * i).Take(size)); + } + } +} diff --git a/samples/CompositeConsole/Program.cs b/samples/CompositeConsole/Program.cs new file mode 100644 index 00000000..9f8e20aa --- /dev/null +++ b/samples/CompositeConsole/Program.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Salesforce.Common; +using Salesforce.Force; + +namespace CompositeConsole +{ + class Program + { + private static readonly string SecurityToken = ConfigurationManager.AppSettings["SecurityToken"]; + private static readonly string ConsumerKey = ConfigurationManager.AppSettings["ConsumerKey"]; + private static readonly string ConsumerSecret = ConfigurationManager.AppSettings["ConsumerSecret"]; + private static readonly string Username = ConfigurationManager.AppSettings["Username"]; + private static readonly string Password = ConfigurationManager.AppSettings["Password"] + SecurityToken; + private static readonly string IsSandboxUser = ConfigurationManager.AppSettings["IsSandboxUser"]; + + public static void Main() + { + try + { + var task = RunSample(); + task.Wait(); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + Console.WriteLine(e.StackTrace); + + var innerException = e.InnerException; + while (innerException != null) + { + Console.WriteLine(innerException.Message); + Console.WriteLine(innerException.StackTrace); + + innerException = innerException.InnerException; + } + } + + Console.WriteLine("\nPress enter to close..."); + Console.ReadLine(); + } + + private static async Task RunSample() + { + var auth = new AuthenticationClient(); + + // Authenticate with Salesforce + Console.WriteLine("Authenticating with Salesforce"); + var url = IsSandboxUser.Equals("true", StringComparison.CurrentCultureIgnoreCase) + ? "https://test.salesforce.com/services/oauth2/token" + : "https://login.salesforce.com/services/oauth2/token"; + + await auth.UsernamePasswordAsync(ConsumerKey, ConsumerSecret, Username, Password, url); + Console.WriteLine("Connected to Salesforce"); + + // Get a bulk client + var client = new ForceClient(auth.InstanceUrl, auth.AccessToken, auth.ApiVersion); + + //Create some Accounts using the SOobject Tree + var sObjectTreeRecords = new + { + records = new[] + { + new + { + attributes = new {type = "Account", referenceId = "ref1"}, + name = "SampleAccount1", + phone = "1111111111", + website = "nicode.org", + numberOfEmployees = "1", + industry = "Software" + }, + new + { + attributes = new {type = "Account", referenceId = "ref2"}, + name = "SampleAccount2", + phone = "22222222222", + website = "nicode.org", + numberOfEmployees = "2", + industry = "Software" + }, + new + { + attributes = new {type = "Account", referenceId = "ref3"}, + name = "SampleAccount1", + phone = "3333333333", + website = "nicode.org", + numberOfEmployees = "3", + industry = "Software" + }, + new + { + attributes = new {type = "Account", referenceId = "ref4"}, + name = "SampleAccount4", + phone = "4444444444", + website = "nicode.org", + numberOfEmployees = "4", + industry = "Software" + }, + } + }; + + var results = (await client.SObjectTreeSave("Account", sObjectTreeRecords)).results; + + //Record the AccountIds to a List + var newAccountIds = new List(); + foreach (var successResponseObjectTreeResult in results) + { + Console.WriteLine(successResponseObjectTreeResult.referenceId + " : " + successResponseObjectTreeResult.id); + newAccountIds.Add(successResponseObjectTreeResult.id); + } + + + //Using the partition extension method to split into partitions of 25 (requirement for batch request) + foreach (var newAccountIdPartition in newAccountIds.Partition(25)) + { + //Update the industry of each Account + var updateObject = new + { + batchRequests = newAccountIdPartition + .Select(id => new + { + method = "PATCH", + url = "v34.0/sobjects/Account/" + id, + richInput = new { Industry = "Engineering" } + }) + .ToArray() + }; + var unused = (await client.BatchSave(updateObject)).results; + } + + //Verifying industry changed + var accounts = (await client.QueryAsync("SELECT Id, Industry FROM Account WHERE website = 'nicode.org'")).Records; + foreach (var account in accounts) + { + Console.WriteLine(account.Id + " : " + account.Industry); + } + + foreach (var accountPartition in accounts.Partition(25)) + { + var deleteObjects = new + { + batchRequests = accountPartition + .Select(account => new { method = "DELETE", url = "v34.0/sobjects/Account/" + account.Id }) + .ToArray() + }; + var unused = (await client.BatchSave(deleteObjects)).results; + } + } + public class Account + { + public string Id { get; set; } + public string Industry { get; set; } + } + } +} diff --git a/samples/CompositeConsole/Properties/AssemblyInfo.cs b/samples/CompositeConsole/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..68f2c660 --- /dev/null +++ b/samples/CompositeConsole/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CompositeConsole")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CompositeConsole")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("95cf1fa1-98e8-411c-89bf-db7d086f27ce")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/ChatterToolkitForNET/ChatterToolkitForNET.csproj b/src/ChatterToolkitForNET/ChatterToolkitForNET.csproj index e64b890b..5957e902 100644 --- a/src/ChatterToolkitForNET/ChatterToolkitForNET.csproj +++ b/src/ChatterToolkitForNET/ChatterToolkitForNET.csproj @@ -20,6 +20,10 @@ 2.0 + SAK + SAK + SAK + SAK true diff --git a/src/CommonLibrariesForNET/CommonLibrariesForNET.csproj b/src/CommonLibrariesForNET/CommonLibrariesForNET.csproj index ba151d83..a072c3db 100644 --- a/src/CommonLibrariesForNET/CommonLibrariesForNET.csproj +++ b/src/CommonLibrariesForNET/CommonLibrariesForNET.csproj @@ -20,6 +20,10 @@ 2.0 + SAK + SAK + SAK + SAK true @@ -63,6 +67,8 @@ + + @@ -82,6 +88,7 @@ + diff --git a/src/CommonLibrariesForNET/Models/Json/SuccessResponseBatchSave.cs b/src/CommonLibrariesForNET/Models/Json/SuccessResponseBatchSave.cs new file mode 100644 index 00000000..6905085c --- /dev/null +++ b/src/CommonLibrariesForNET/Models/Json/SuccessResponseBatchSave.cs @@ -0,0 +1,34 @@ +using Newtonsoft.Json; + +namespace Salesforce.Common.Models.Json +{ + + public class SuccessResponseBatchSave + { + public bool hasErrors { get; set; } + public dynamic results { get; set; } + } + + public class SuccessResponseBatchSaveResult + { + public int statusCode { get; set; } + public SuccessResponseBatchSaveResult1 result { get; set; } + } + + public class SuccessResponseBatchSaveResult1 + { + public SuccessResponseBatchSaveResult1Attributes attributes { get; set; } + public string Name { get; set; } + public string BillingPostalCode { get; set; } + public string Id { get; set; } + } + + public class SuccessResponseBatchSaveResult1Attributes + { + public string type { get; set; } + public string url { get; set; } + } + + +} + diff --git a/src/CommonLibrariesForNET/Models/Json/SuccessResponseSObjectTree.cs b/src/CommonLibrariesForNET/Models/Json/SuccessResponseSObjectTree.cs new file mode 100644 index 00000000..eee0e3e2 --- /dev/null +++ b/src/CommonLibrariesForNET/Models/Json/SuccessResponseSObjectTree.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; + +namespace Salesforce.Common.Models.Json +{ + public class SuccessResponseSObjectTree + { + //[JsonProperty(PropertyName = "hasErrors")] + public bool hasErrors { get; set; } + public SuccessResponseObjectTreeResult[] results { get; set; } + } + public class SuccessResponseObjectTreeResult + { + public string referenceId { get; set; } + public string id { get; set; } + } +} + diff --git a/src/CommonLibrariesForNET/Models/Xml/SObjectList.cs b/src/CommonLibrariesForNET/Models/Xml/SObjectList.cs index f42ea89e..ffb9a4fe 100644 --- a/src/CommonLibrariesForNET/Models/Xml/SObjectList.cs +++ b/src/CommonLibrariesForNET/Models/Xml/SObjectList.cs @@ -4,6 +4,7 @@ using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; +using Salesforce.Common.Serializer; namespace Salesforce.Common.Models.Xml { @@ -32,10 +33,16 @@ public void WriteXml(XmlWriter writer) } else { - var xmlSerializer = new XmlSerializer(typeof(T), new XmlRootAttribute("sObject")); + XmlSerializer xmlSerializer; + if (!XmlSerializerCache.GetInstance().XmlSerializerDictionary.TryGetValue(typeof(T).FullName, out xmlSerializer)) + { + xmlSerializer = new XmlSerializer(typeof(T), new XmlRootAttribute("sObject")); + XmlSerializerCache.GetInstance().XmlSerializerDictionary.Add(typeof(T).FullName, xmlSerializer); + } + var ns = new XmlSerializerNamespaces(); ns.Add(string.Empty, string.Empty); - var settings = new XmlWriterSettings {OmitXmlDeclaration = true}; + var settings = new XmlWriterSettings { OmitXmlDeclaration = true }; var stringBuilder = new StringBuilder(); using (var xmlWriter = XmlWriter.Create(stringBuilder, settings)) { diff --git a/src/CommonLibrariesForNET/XmlSerializerCache.cs b/src/CommonLibrariesForNET/XmlSerializerCache.cs new file mode 100644 index 00000000..93fb93af --- /dev/null +++ b/src/CommonLibrariesForNET/XmlSerializerCache.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Salesforce.Common +{ + + public class XmlSerializerCache + { + private static XmlSerializerCache _instance; + + private XmlSerializerCache() + { + XmlSerializerDictionary = new Dictionary(); + } + + public static XmlSerializerCache GetInstance() + { + if (_instance == null) + _instance = new XmlSerializerCache(); + return _instance; + } + + public Dictionary XmlSerializerDictionary { get; set; } + } +} diff --git a/src/ForceToolkitForNET/ForceClient.cs b/src/ForceToolkitForNET/ForceClient.cs index dd7149b1..93047523 100644 --- a/src/ForceToolkitForNET/ForceClient.cs +++ b/src/ForceToolkitForNET/ForceClient.cs @@ -206,6 +206,20 @@ public async Task UserInfo(string url) return response; } + public async Task SObjectTreeSave(string objectName, object sObjectTree) + { + if (string.IsNullOrEmpty(objectName)) throw new ArgumentNullException("objectName"); + if (sObjectTree == null) throw new ArgumentNullException("sObjectTree"); + + return await _jsonHttpClient.HttpPostAsync(sObjectTree, string.Format("composite/tree/{0}/", objectName)); + } + public async Task BatchSave(object batchRequest) + { + if (batchRequest == null) throw new ArgumentNullException("batchRequest"); + + return await _jsonHttpClient.HttpPostAsync(batchRequest, "composite/batch/"); + } + // BULK METHODS public async Task> RunJobAsync(string objectName, BulkConstants.OperationType operationType, diff --git a/src/ForceToolkitForNET/ForceToolkitForNET.csproj b/src/ForceToolkitForNET/ForceToolkitForNET.csproj index 9592b245..e5bb9b97 100644 --- a/src/ForceToolkitForNET/ForceToolkitForNET.csproj +++ b/src/ForceToolkitForNET/ForceToolkitForNET.csproj @@ -20,6 +20,10 @@ 2.0 + SAK + SAK + SAK + SAK true @@ -61,7 +65,7 @@ - {688bc3a2-29c2-48f7-bc28-7a81abf5c3d3} + {688BC3A2-29C2-48F7-BC28-7A81ABF5C3D3} CommonLibrariesForNET @@ -73,4 +77,4 @@ --> - + \ No newline at end of file