{"id":27017902,"url":"https://github.com/teamchong/dotnetforce","last_synced_at":"2025-08-21T02:07:32.526Z","repository":{"id":48071130,"uuid":"152982866","full_name":"teamchong/DotNetForce","owner":"teamchong","description":"Interact with the Salesforce REST APIs using native libraries.","archived":false,"fork":false,"pushed_at":"2022-12-08T14:49:12.000Z","size":82588,"stargazers_count":5,"open_issues_count":3,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-08-15T07:56:53.870Z","etag":null,"topics":["dotnet","dotnet-standard","salesforce","salesforce-api","salesforce-rest-api"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/teamchong.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-10-14T14:29:46.000Z","updated_at":"2025-01-26T13:26:48.000Z","dependencies_parsed_at":"2023-01-25T14:01:15.128Z","dependency_job_id":null,"html_url":"https://github.com/teamchong/DotNetForce","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/teamchong/DotNetForce","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teamchong%2FDotNetForce","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teamchong%2FDotNetForce/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teamchong%2FDotNetForce/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teamchong%2FDotNetForce/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/teamchong","download_url":"https://codeload.github.com/teamchong/DotNetForce/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teamchong%2FDotNetForce/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271415496,"owners_count":24755639,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-21T02:00:08.990Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dotnet","dotnet-standard","salesforce","salesforce-api","salesforce-rest-api"],"created_at":"2025-04-04T16:35:30.652Z","updated_at":"2025-08-21T02:07:32.509Z","avatar_url":"https://github.com/teamchong.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DotNetForce\n\nnuget package \u003chttps://www.nuget.org/packages/DotNetForce\u003e\n\nRun this on nuget command to fix the binding problem\n\n```nuget\nGet-Project –All | Add-BindingRedirect\n```\n\nPlease check the TestClasses for code samples \u003chttps://github.com/teamchong/DotNetForce/tree/master/DotNetForceTest\u003e\n\nThis library provides an easy way for .NET developers to interact with the Salesforce REST \u0026 Chatter APIs using native libraries.\n\nv6.0.0\n\nRewrote to support netstandard 2.1\n\nv5.0.9\n\nnew function GetAsyncEnumerableByFieldValues, support any string field\n\nAdd test class for GetAsyncEnumerableByIds, GetAsyncEnumerableByFieldValues\n\n\u003chttps://github.com/teamchong/DotNetForce/blob/master/DotNetForceTest/DNFClientTest.cs\u003e\n\nv5.0.8\n\nBug Fixed JObjectWrapper.Set support relationship field\n\nv5.0.7\n\nRemove ExplainAllAsync\n\nBugFixed: QueryAll\n\n(All = include deleted \u0026 achieved)\n\nV5.0.1\n\nAdd support for Blazor (WebAssembly)\n\n+Setup \u003chttps://teamchong.github.io/DotNetForce/\u003e for demo running C# on web client\n\n+DNFClient.DefaultApiVersion = \"v52.0\"; // to change API Version\n\n+DNFClient.UseCompression = false; // to disabled UseCompression\n\n+DNFClient.Proxy = uri =\u003e new Uri(new Uri(\"https://yourproxy\"), uri.PathAndQuery); // to send to proxy instead of instanceUrl\n\nV4.0.0\n\nI tried put DotNetForce.Schema on nuget, but it doesn't work.\n\nfollowing is the step by step guide to generate Schema for your org\n\n1) Install the latest version from nuget (v4.0.0+) \u003chttps://nuget.org/packages/DotNetForce\u003e\n\n2) Download all the files from (Except DotNetForce.Schema.csproj) \u003chttps://github.com/teamchong/DotNetForce/tree/master/DotNetForce.Schema\u003e\n\n3) Place the files in the root of your project\n\n4) Open DEV.tt and update the your_app_client_id, your_app_client_secret, your_app_redirect_uri_for_server_flow_only, your_user_name_for_password_flow_only, your_password_for_password_flow_only \u003chttps://raw.githubusercontent.com/teamchong/DotNetForce/master/DotNetForce.Schema/DEV.tt\u003e\n\n5) Run the DEV.tt using T4\n\n6) a new DEV.cs should be created which contains the Schema helper classes for your org.\n\nV2.0.7\n\n1) increase query string limit to 20000 characters\n2) new Client.GetAsyncEnumerableByIds\nusages:\n\n```cs\nvar (opp, acc) = Schema.Of(s =\u003e (s.Opportunity, s.Account));\nvar lotsOfOpps = await Schema.Wrap(client.GetAsyncEnumerable($@\"\nSELECT {opp.AccountId}\nFROM {opp}\nLIMIT 100000\")).ToListAsync().ConfigureAwait(false);\nvar lotsofIds = lotsOfOpps.Select(o =\u003e o.Get(opp.AccountId)).ToList(); // new [] { \"0010I00000QkwK5\"... x 100000 };\n\nvar results = Schema.Wrap(Client.GetAsyncEnumerableByIds(lotsofIds, $@\"\nSELECT {acc.Id}, {acc.AccountNumber}, {acc.Owner.Email}\nFROM {acc}\nWHERE {acc.Id} IN(\u003cids\u003e)\", \"\u003cids\u003e\")).ToListAsync().ConfigureAwait(false); // \u003cids\u003e is a text template you defined, you have multiple template, it willl be replaced by '0010I00000QkwK5','0010I00000QkwK5' etc.\n```\n\nV2.0.0\n\n1) new \"DotNetForce.Schema\" created by T4 template, please fill in LoginProfiles.json, and run the T4 template \"DEV.tt\" using Visual Studio\n\nusages\n\n```cs\nvar opp = Schema.Of(s =\u003e s.Opportunity);\nvar line = Schema.Of(s =\u003e s.OpportunityLineItem);\n```\n\nor\n\n```cs\nvar (opp, line) = Schema.Of(s =\u003e (s.Opportunity, s.OpportunityLineItem));\n```\n\n```cs\nvar oppties = Schema.Wrap(Client.GetAsyncEnumerable($@\"\nSELECT {opp.Id}, {opp.Account.CreatedBy.Name}, (SELECT {line.ListPrice} FROM {opp.OpportunityLineItems})\nFROM {opp}\nWHERE {opp.Name} LIKE 'Test'\n\"));\nawait foreach (var oppObj in oppties)\n{\n    var id = oppObj.Get(opp.Id);\n    var createdByName = oppObj.Get(opp.CreatedBy.Name);\n    var oppLines = Schema.Wrap(Client.GetAsyncEnumerable(oppObj.Get(opp.OpportunityLineItems)));\n    await foreach (var oppLine in oppLines)\n    {\n        var listPrice = oppLine.Get(line.ListPrice);\n    }\n}\n```\n\n--------------------------------------------------------------------------------\n\nOriginal repository \u003chttps://github.com/developerforce/Force.com-Toolkit-for-NET\u003e\n\nCurrently it support the following\n\n1) Rest API, when query large dataset, after the first batch is retrieved (usually 2000 records will be returned for first batch)\n   it will put 5 next queries in 1 composite API call as a chunk and run all the chunks in parallel (not lazy loading)\n2) Composite, Multiple request in 1 api call\n   (when allOrNone: true, up to 25 requests, 5 of them can bee soql query).\n   (when allOrNone: false, there no limit, the requests will be ran in parallel).\n   \u003chttps://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_composite_composite.htm\u003e\n3) Composite Tree, create up to 100 objects and its related child objects in 1 api call\n   \u003chttps://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_composite_sobject_tree.htm\u003e\n4) Composite Collection, up to 2000 x 5 retrieve or 200 x 5 create|update|delete requests in 1 api call\n   x 5 because I can put 5 composite collection requests in 1 Composite API call\n   \u003chttps://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_composite_sobjects_collections.htm\u003e\n5) Batch, Executes up to 25 subrequests in a single request. unlike the above Composite API\n   Each subrequest counts against rate limits.\n6) Tooling API, let you run apex using rest api etc.\n   \u003chttps://developer.salesforce.com/docs/atlas.en-us.api_tooling.meta/api_tooling/intro_api_tooling.htm\u003e\n7) Chatter API same as Force.com-Toolkit-for-NET\n   \u003chttps://developer.salesforce.com/docs/atlas.en-us.chatterapi.meta/chatterapi/\u003e\n8) Bulk API same as Force.com-Toolkit-for-NET\n   \u003chttps://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/asynch_api_intro.htm\u003e\n\nThis library is now targeting .NET Standard 2.0.\n\n## Authentication\n\nTo access the Force.com APIs you must have a valid Access Token. Currently there are two ways to generate an Access Token: the [Username-Password Authentication Flow](http://help.salesforce.com/HTViewHelpDoc?id=remoteaccess_oauth_username_password_flow.htm\u0026language=en_US) and the [Web Server Authentication Flow](http://help.salesforce.com/apex/HTViewHelpDoc?id=remoteaccess_oauth_web_server_flow.htm\u0026language=en_US)\n\n### Username-Password Authentication Flow\n\nThe Username-Password Authentication Flow is a straightforward way to get an access token. Simply provide your consumer key, consumer secret, username, and password.\n\n```cs\nvar client = await DNFClient.LoginAsync(new Uri(\"https://www.salesforce.com\"), \"YOUR_CLIENT_ID\", \"YOUR_CLIENT_SECRET\", \"YOUR_USER_NAME\", \"YOUR_PASSWORD\").ConfigureAwait(false);\n```\n\n### Web-Server Authentication Flow\n\nThe Web-Server Authentication Flow requires a few additional steps but has the advantage of allowing you to authenticate your users and let them interact with the Force.com using their own access token.\n\nFirst, you need to authenticate your user. You can do this by creating a URL that directs the user to the Salesforce authentication service. You'll pass along some key information, including your consumer key (which identifies your Connected App) and a callback URL to your service.\n\nAfter the user logs in you'll need to handle the callback and retrieve the code that is returned. Using this code, you can then request an access token.\n\n```cs\nvar client await DNFClient.LoginAsync(new OAuthProfile\n{\n    LoginUri = new Uri(\"https://www.salesforce.com\"),\n    ClientId =\"YOUR_CLIENT_ID\",\n    ClientSecret = \"YOUR_CLIENT_SECRET\",\n    Code = \"YOUR_ACCESS_TOKEN_FROM_AUTH_SERVICE\",\n    RedirectUri = \"YOUR_REDIRECT_URI\",\n    RefreshToken = \"YOUR_REFRESH_TOKEN\"\n}).ConfigureAwait(false);\n```\n\nFor refreshToken, you need refresh_token \"permission\", even you already have \"full\" permission.\n\u003chttps://developer.salesforce.com/docs/atlas.en-us.packagingGuide.meta/packagingGuide/connected_app_create.htm\u003e\n\n```cs\nvar client await DNFClient.LoginAsync(new OAuthProfile\n{\n    LoginUri = new Uri(\"https://www.salesforce.com\"),\n    ClientId =\"YOUR_CLIENT_ID\",\n    ClientSecret = \"YOUR_CLIENT_SECRET\",\n    Code = \"YOUR_ACCESS_TOKEN_FROM_AUTH_SERVICE\",\n    RedirectUri = \"YOUR_REDIRECT_URI\",\n    RefreshToken = \"YOUR_REFRESH_TOKEN\"\n}).ConfigureAwait(false);\n\n\n.... to refresh call this\nawait client.TokenRefreshAsync().ConfigureAwait(false);\n```\n\n## Query\n\nQuery 1 records\n\n```cs\nvar name = \"abc\";\nvar record = (await client.QueryAsync($@\"\nSELECT ID FROM Opportunity WHERE Name = {DNF.SOQLString(name)} LIMIT 1\").ConfigureAwait(false)\n).Records.FirstOrDefault();\n```\n\nQuery By Id\n\n```cs\nvar oppId = \"006xxxx\";\nvar record = await client.QueryByIdAsync(\"Opportunity\", oppId).ConfigureAwait(false);\n```\n\nQuery\n\n```cs\nvar records = await client.GetAsyncEnumerable($@\"\nSELECT Id FROM Opportunity WHERE LastModifiedDate \u003e {DNF.SOQLDateTime(new DateTime(2018, 1, 1))}\")\n    .ToListAsync().ConfigureAwait(false);\n```\n\nQuery (included deleted)\n\n```cs\nvar result = await client.GetAsyncEnumerableAll($@\"\nSELECT Id FROM Opportunity WHERE LastModifiedDate \u003e {DNF.SOQLDateTime(new DateTime(2018, 1, 1))}\")\n    .ToListAsync().ConfigureAwait(false);\nvar records = await result.GetAsyncEnumerable(client).ToListAsync().ConfigureAwait(false);\n```\n\nQuery Sub-query\n\n```cs\nvar result = await client.GetAsyncEnumerableAllAsync($@\"\nSELECT Id, (SELECT Id FROM Opportunities) FROM Account LIMIT 1\").ToListAsync().ConfigureAwait(false);\nfor (var acc in result) {\n   var records = client.GetAsyncEnumerable((QueryResult\u003cJObject\u003e)acc[\"Opportunities\"]);\n}\n```\n\n## Create\n\nCreate a record\n\n```cs\nvar caseList = Enumerable.Range(1, 40).Select(i =\u003e new AttributedObject(\"Case\")\n{\n  [\"Subject\"] = $\"UnitTest{i}\",\n}).ToArray();\nvar result = await client.Composite.CreateAsync(caseList, true).ConfigureAwait(false);\nDNF.ThrowIfError(result);\nAssert.NotEmpty(result.SuccessResponses());\n```\n\n Create multiple records (in transaction, allOrNone: true, up to 25 records can be created)\n\n```cs\n var caseList = Enumerable.Range(1, 40).Select(i =\u003e new AttributedObject(\"Case\")\n {\n     [\"Subject\"] = $\"UnitTest{i}\",\n }).ToArray();\n var result = await client.Composite.CreateAsync(caseList, true).ConfigureAwait(false);\n DNF.ThrowIfError(result);\n Assert.NotEmpty(result.SuccessResponses());\n ```\n\n Create multiple records (no transaction, no limit)\n\n```cs\n var expected = 200 * 26;\n var existingCase = await client.GetAsyncEnumerable($@\"\nSELECT Name FROM Opportunity ORDER BY Id LIMIT {expected}\");\n\n var newCase = await existingCase\n     .Select(c =\u003e new AttributedObject(\"Case\") { [\"Subject\"] = $\"UnitTest{c[\"Name\"]}\" })\n     .CreateAsync(client).ConfigureAwait(false);\n DNF.ThrowIfError(newCase);\n Assert.NotEmpty(newCase.SuccessResponses());\n ```\n\nCreate Tree\n\n```cs\nvar accounts =\n   Enumerable.Range(1, 6).Select(i =\u003e new AttributedObject(\"Account\", $\"acc{i}\")\n   {\n      [\"Name\"] = $\"UnitTest Account {i}\",\n      [\"Contacts\"] = new RecordsObject(Enumerable.Range(1, 2).Select(j =\u003e\n          new AttributedObject(\"Contact\", $\"cont{i}[{j}]\")\n          { [\"Name\"] = $\"UnitTest Contact {i}-{j}\" }))\n   });\n\nvar accountsResult = await client.Composite.CreateTreeAsync(\"Account\", accounts).ConfigureAwait(false);\nDNF.ThrowIfError(accountsResult);\nAssert.NotEmpty(accountsResult.Results);\n```\n\n## Retrieve\n\nRetrieve a retreive by Id\n\noops, still under contruction, will continue when i have the time...\n\n## Code sample\n\nSee. \u003chttps://github.com/ste80/DotNetForce/tree/master/DotNetForceTest\u003e\nRefer to Unit Test for code sample.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteamchong%2Fdotnetforce","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fteamchong%2Fdotnetforce","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteamchong%2Fdotnetforce/lists"}