├── src ├── TestApp │ ├── hostsettings.json │ ├── TestApp.csproj │ ├── appsettings.json │ ├── README.md │ ├── Program.cs │ └── .editorconfig ├── AuthApp │ ├── Internal │ │ ├── Constants.cs │ │ ├── SfConfig.cs │ │ └── HttpServer.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Program.cs │ ├── AuthApp.csproj │ ├── HostBuilderOptions.cs │ ├── ConsoleHandler.cs │ ├── README.md │ ├── TokenGeneratorCommand.cs │ ├── HostBuilderExtensions.cs │ └── .editorconfig └── CometD.NetCore.Salesforce │ ├── Messaging │ ├── MessagePayload.cs │ ├── MessageEvent.cs │ ├── MessageEnvelope.cs │ └── MessageData.cs │ ├── README.md │ ├── CometD.NetCore.Salesforce.csproj │ ├── IStreamingClient.cs │ ├── SalesforceConfiguration.cs │ ├── DependencyInjection │ └── StreamingClientServiceExtensions.cs │ ├── ResilientStreamingClient.cs │ ├── Resilience │ ├── IResilientForceClient.cs │ └── ResilientForceClient.cs │ └── .editorconfig ├── img ├── icon.png ├── logo64x64.png ├── new-app-api-auth.jpg ├── new-app-basic-info.jpg ├── new-platform-event.jpg └── new-platform-event-field.jpg ├── Directory.Build.targets ├── clean.sh ├── .github └── FUNDING.yml ├── Directory.Build.props ├── .vscode ├── extensions.json └── settings.json ├── test └── CometD.UnitTest │ ├── CometD.UnitTest.csproj │ ├── UnitTest1.cs │ └── .editorconfig ├── CHANGELOG.md ├── LICENSE ├── appveyor.yml ├── CometD.NetCore.Salesforce.sln ├── README.md └── .gitignore /src/TestApp/hostsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "environment": "Development" 3 | } 4 | -------------------------------------------------------------------------------- /img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdcllc/CometD.NetCore.Salesforce/HEAD/img/icon.png -------------------------------------------------------------------------------- /img/logo64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdcllc/CometD.NetCore.Salesforce/HEAD/img/logo64x64.png -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /img/new-app-api-auth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdcllc/CometD.NetCore.Salesforce/HEAD/img/new-app-api-auth.jpg -------------------------------------------------------------------------------- /img/new-app-basic-info.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdcllc/CometD.NetCore.Salesforce/HEAD/img/new-app-basic-info.jpg -------------------------------------------------------------------------------- /img/new-platform-event.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdcllc/CometD.NetCore.Salesforce/HEAD/img/new-platform-event.jpg -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/bash 3 | find . -name 'obj' -type d -exec rm -rv {} + ; find . -name 'bin' -type d -exec rm -rv {} + ; 4 | -------------------------------------------------------------------------------- /img/new-platform-event-field.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdcllc/CometD.NetCore.Salesforce/HEAD/img/new-platform-event-field.jpg -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [kdcllc] 4 | custom: ["https://www.buymeacoffee.com/vyve0og"] -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/AuthApp/Internal/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace AuthApp.Internal 2 | { 3 | internal static class Constants 4 | { 5 | public const string CLIToolName = "salesforce"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-vscode.csharp", 4 | "jchannon.csharpextensions", 5 | "EditorConfig.EditorConfig", 6 | "ms-vscode-remote.vscode-remote-extensionpack", 7 | "docsmsft.docs-authoring-pack", 8 | "ms-vscode.vscode-node-azure-pack", 9 | "streetsidesoftware.code-spell-checker", 10 | "formulahendry.dotnet-test-explorer", 11 | "eamodio.gitlens", 12 | "marp-team.marp-vscode", 13 | "kdcllc.vscode-k8s-pack" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/AuthApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "AuthApp": { 4 | "commandName": "Project", 5 | "commandLineArgs": "get-tokens --azure https://kdcllc.vault.azure.net/", 6 | //"commandLineArgs": "get-tokens --verbose:information --section:sfconfig" 7 | 8 | //"commandLineArgs": "get-tokens --key: --secret: --verbose:information --section:Salesforce" 9 | 10 | //"commandLineArgs": "get-tokens --key: --secret: --verbose:debug --section:Salesforce" 11 | 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.trimTrailingWhitespace": true, 3 | 4 | "files.associations": { 5 | "*.*proj": "xml", 6 | "*.props": "xml", 7 | "*.targets": "xml", 8 | "*.tasks": "xml", 9 | "Dockerfile*": "dockerfile" 10 | }, 11 | 12 | "dotnet-test-explorer.enableTelemetry": false, 13 | "dotnet-test-explorer.testProjectPath": "/test/**/*.csproj", 14 | "dotnet-test-explorer.autoExpandTree": true, 15 | "dotnet-test-explorer.pathForResultFile": "TestResults", 16 | "dotnet-test-explorer.addProblems": true 17 | } 18 | -------------------------------------------------------------------------------- /src/CometD.NetCore.Salesforce/Messaging/MessagePayload.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CometD.NetCore.Salesforce.Messaging 4 | { 5 | /// 6 | /// The payload base class for the other salesforce events. 7 | /// 8 | public class MessagePayload 9 | { 10 | /// 11 | /// The payload creation timestamp. 12 | /// 13 | public DateTimeOffset CreatedDate { get; set; } 14 | 15 | /// 16 | /// The user id. 17 | /// 18 | public string CreatedById { get; set; } = string.Empty; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/CometD.UnitTest/CometD.UnitTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### 2021-04-06 v3.0.3 4 | 5 | * [Add support for handling 401 Authentication Errors from Salesforce](https://github.com/kdcllc/CometD.NetCore.Salesforce/issues/23) thanks @[apaulro](https://github.com/apaulro) 6 | ### 2021-04-06 v3.0.2 7 | 8 | * Updated dependencies 9 | - NetCoreForce.Client 10 | - netstandard2.1 and net5.0 framework support client support via Microsoft.Bcl.AsyncInterfaces 11 | 12 | * [Allow configuration of SF authentication token type](https://github.com/kdcllc/CometD.NetCore.Salesforce/pull/24) 13 | * [Allow injecting a strategy for handling invalid replay Ids](https://github.com/kdcllc/CometD.NetCore.Salesforce/pull/25) 14 | * [Silently fails when replay id is too old.](https://github.com/kdcllc/CometD.NetCore.Salesforce/issues/20) -------------------------------------------------------------------------------- /src/CometD.NetCore.Salesforce/Messaging/MessageEvent.cs: -------------------------------------------------------------------------------- 1 | namespace CometD.NetCore.Salesforce.Messaging 2 | { 3 | /// 4 | /// Child element of . 5 | /// 6 | public class MessageEvent 7 | { 8 | /// 9 | /// Contains Salesforce replay id. 10 | /// 11 | /// "replayId": 10 12 | /// 13 | /// 14 | public int ReplayId { get; set; } 15 | 16 | /// 17 | /// Contains Salesforce event Uuid. 18 | /// 19 | /// "EventUuid": "e981b488-81f3-4fcc-bd6f-f7033c9d7ac3" 20 | /// 21 | /// 22 | public string EventUuid { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/CometD.UnitTest/UnitTest1.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.Hosting; 5 | 6 | using Xunit; 7 | 8 | namespace CometD.UnitTest 9 | { 10 | public class UnitTest1 11 | { 12 | [Fact] 13 | public void Test1() 14 | { 15 | var dic = new Dictionary 16 | { 17 | { "Salesforce:Id", "-2" }, 18 | { "Salesforce:Name", string.Empty } 19 | }; 20 | 21 | IConfiguration configuration = null; 22 | 23 | var host = new HostBuilder() 24 | .ConfigureAppConfiguration((context, builder) => 25 | { 26 | configuration = builder.AddInMemoryCollection(dic).Build(); 27 | }) 28 | .ConfigureServices((services) => 29 | { 30 | }); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/CometD.NetCore.Salesforce/Messaging/MessageEnvelope.cs: -------------------------------------------------------------------------------- 1 | namespace CometD.NetCore.Salesforce.Messaging 2 | { 3 | /// 4 | /// Top level objects of the salesforce reply message. 5 | /// 6 | /// "data": {} 7 | /// "channel": "/event/Custom_Event__e" 8 | /// 9 | /// 10 | /// 11 | public class MessageEnvelope where TPayload : MessagePayload 12 | { 13 | /// 14 | /// where TPayload is a generic message. 15 | /// 16 | public MessageData? Data { get; set; } 17 | 18 | /// 19 | /// Channel or event information. 20 | /// 21 | /// "channel": "/event/Custom_Event__e" 22 | /// 23 | /// 24 | public string Channel { get; set; } = string.Empty; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/CometD.NetCore.Salesforce/README.md: -------------------------------------------------------------------------------- 1 | # CometD.NetCore.Salesforce 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/baalfhs6vvc38icc?svg=true)](https://ci.appveyor.com/project/kdcllc/cometd-netcore-salesforce) 4 | [![NuGet](https://img.shields.io/nuget/v/CometD.NetCore.Salesforce.svg)](https://www.nuget.org/packages?q=Bet.AspNetCore) 5 | [![MyGet](https://img.shields.io/myget/kdcllc/v/CometD.NetCore.Salesforce.svg?label=myget)](https://www.myget.org/F/kdcllc/api/v2) 6 | 7 | Add the following to the project 8 | 9 | ```csharp 10 | dotnet add package CometD.NetCore.Salesforce 11 | ``` 12 | 13 | This library includes: 14 | 15 | - `ResilientStreamingClient` class to create an event bus for Salesforce 16 | - `IResilientForceClient` wrapper class with Resilience for `NetCoreForce.Client` 17 | 18 | Complete Sample App using this library can be found at [Bet.BuildingBlocks.SalesforceEventBus](https://github.com/kdcllc/Bet.BuildingBlocks.SalesforceEventBus) 19 | -------------------------------------------------------------------------------- /src/CometD.NetCore.Salesforce/CometD.NetCore.Salesforce.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;netstandard2.1;net6.0 5 | enable 6 | CometD.NetCore2.Salesforce 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/CometD.NetCore.Salesforce/Messaging/MessageData.cs: -------------------------------------------------------------------------------- 1 | namespace CometD.NetCore.Salesforce.Messaging 2 | { 3 | /// 4 | /// Data from the Salesforce platform event reply. 5 | /// 6 | /// 7 | public class MessageData where TPayload : MessagePayload 8 | { 9 | /// 10 | /// Unique id of the data. 11 | /// "schema": "1qUPELmVz7qUv3ntwyN1eA" 12 | /// 13 | public string Schema { get; set; } = string.Empty; 14 | 15 | /// 16 | /// Generic type of the payload. 17 | /// "payload": {} 18 | /// 19 | public TPayload? Payload { get; set; } 20 | 21 | /// 22 | /// Contains Message event. 23 | /// 24 | /// "event": { 25 | /// "replayId": 10 26 | /// } 27 | /// 28 | /// 29 | public MessageEvent? Event { get; set; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/AuthApp/Internal/SfConfig.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace AuthApp.Internal 4 | { 5 | internal class SfConfig 6 | { 7 | /// 8 | /// Salesforce Client Id. 9 | /// 10 | [Required] 11 | public string? ClientId { get; set; } 12 | 13 | /// 14 | /// Salesforece Secret Id. 15 | /// 16 | [Required] 17 | public string? ClientSecret { get; set; } 18 | 19 | /// 20 | /// i.e. https://login.salesforce.com. 21 | /// 22 | [Required] 23 | [Url] 24 | public string? LoginUrl { get; set; } 25 | 26 | /// 27 | /// Default set to /services/oauth2/token. 28 | /// 29 | public string OAuthUri { get; set; } = "/services/oauth2/token"; 30 | 31 | /// 32 | /// Authorize Uri for Salesforce end point. 33 | /// 34 | public string OAuthorizeUri { get; set; } = "/services/oauth2/authorize"; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2021 King David Consulting LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/AuthApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Threading.Tasks; 3 | 4 | using AuthApp.Internal; 5 | 6 | using McMaster.Extensions.CommandLineUtils; 7 | 8 | using Console = Colorful.Console; 9 | 10 | namespace AuthApp 11 | { 12 | [Command(Name = Constants.CLIToolName, Description = "cli tool to help with Salesforce development by providing with Refresh token generation.")] 13 | [Subcommand(typeof(TokenGeneratorCommand))] 14 | [HelpOption("-?")] 15 | [VersionOptionFromMember("--version", MemberName = nameof(GetVersion))] 16 | public class Program 17 | { 18 | private static Task Main(string[] args) 19 | { 20 | return CommandLineApplication.ExecuteAsync(args); 21 | } 22 | 23 | private static string GetVersion() 24 | { 25 | return typeof(Program).Assembly.GetCustomAttribute().InformationalVersion; 26 | } 27 | 28 | private int OnExecute(CommandLineApplication app, IConsole console) 29 | { 30 | Console.WriteAscii(Constants.CLIToolName, Colorful.FigletFont.Default); 31 | 32 | console.WriteLine(); 33 | console.WriteLine("You must specify at a subcommand."); 34 | app.ShowHelp(); 35 | return 1; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 3.0.{build} 2 | branches: 3 | only: 4 | - master 5 | pull_requests: 6 | do_not_increment_build_number: true 7 | image: Visual Studio 2022 8 | ## temporary until 6.0.300 sdk is installed 9 | install: 10 | - ps: $urlCurrent = "https://dotnetcli.blob.core.windows.net/dotnet/Sdk/6.0.300/dotnet-sdk-6.0.300-win-x64.zip" 11 | - ps: $env:DOTNET_INSTALL_DIR = "$pwd\.dotnetsdk" 12 | - ps: mkdir $env:DOTNET_INSTALL_DIR -Force | Out-Null 13 | - ps: $tempFileCurrent = [System.IO.Path]::GetTempFileName() 14 | - ps: (New-Object System.Net.WebClient).DownloadFile($urlCurrent, $tempFileCurrent) 15 | - ps: Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory($tempFileCurrent, $env:DOTNET_INSTALL_DIR) 16 | - ps: $env:Path = "$env:DOTNET_INSTALL_DIR;$env:Path" 17 | nuget: 18 | disable_publish_on_pr: true 19 | 20 | build_script: 21 | - ps: dotnet restore CometD.NetCore.Salesforce.sln -v quiet 22 | - ps: dotnet build CometD.NetCore.Salesforce.sln /p:configuration=Release /p:Version=$($env:appveyor_build_version) 23 | 24 | #test: off 25 | test_script: 26 | - dotnet test test/CometD.UnitTest/CometD.UnitTest.csproj 27 | 28 | artifacts: 29 | - path: .\src\**\*.nupkg 30 | name: NuGet package 31 | 32 | deploy: 33 | - provider: NuGet 34 | artifact: /NuGet/ 35 | api_key: 36 | secure: a8sCawSwgb2kYDJAN+xTUvy+MH5jdJR+DmKakUmc/Xom1c+uxyvV+yvpSTJs+ypF 37 | on: 38 | branch: master 39 | -------------------------------------------------------------------------------- /src/TestApp/TestApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | appsettings.json 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/TestApp/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AzureVault": { 3 | "BaseUrl": "https://kdcllc.vault.azure.net/" 4 | }, 5 | 6 | "Logging": { 7 | "IncludeScopes": true, 8 | "Debug": { 9 | "LogLevel": { 10 | "Default": "Debug", 11 | "System": "Warning", 12 | "Microsoft": "Warning", 13 | "SetMinimumLevel": "Debug" 14 | } 15 | }, 16 | "Console": { 17 | "LogLevel": { 18 | "Default": "Debug", 19 | "System": "Warning", 20 | "Microsoft": "Warning", 21 | "SetMinimumLevel": "Debug" 22 | } 23 | } 24 | }, 25 | 26 | "AllowedHosts": "*", 27 | 28 | "ResilientForceClient": { 29 | 30 | }, 31 | 32 | "Salesforce": { 33 | "ClientId": "", 34 | "ClientSecret": "", 35 | 36 | "//OAuth flow with refresh token": "", 37 | "RefreshToken": "", 38 | "AccessToken": "", 39 | 40 | "//Password grant type instead of using persistent refresh token": "", 41 | "Username": "", 42 | "Password": "", 43 | "UserPasswordToken": "", 44 | 45 | "RedirectUri": "http://localhost:5050/", 46 | "OrganizationUrl": "", 47 | "LoginUrl": "https://login.salesforce.com", 48 | "OAuthUri": "/services/oauth2/token", 49 | "PublishEndpoint": "/services/data/v42.0/sobjects/", 50 | "EventOrTopicUri": "/event", 51 | "CometDUri": "/cometd/42.0", 52 | "Retry": 3, 53 | "BackoffPower": 2, 54 | "CustomEvent": "Custom_Event__e", 55 | "ReplayId": "-2" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/AuthApp/AuthApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | salesforce 8 | salesforce 9 | True 10 | 11 | 12 | 13 | A command-line tool that enables Salesforce Access and Refresh Tokens Generation. 14 | Salesforce refresh tokens, Salesforce access tokens, DotNetCore, AspNetCore, salesforce, OAuth Authentication Flow 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/AuthApp/HostBuilderOptions.cs: -------------------------------------------------------------------------------- 1 | using AuthApp.Internal; 2 | 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace AuthApp 6 | { 7 | internal class HostBuilderOptions 8 | { 9 | /// 10 | /// Sets Configuration file besides appsettings.json. 11 | /// 12 | public string? ConfigFile { get; set; } 13 | 14 | /// 15 | /// Provides ability to get troubleshooting information. 16 | /// 17 | public bool Verbose { get; set; } 18 | 19 | /// 20 | /// TraceLevel if verbose is present. 21 | /// 22 | public LogLevel Level { get; set; } 23 | 24 | /// 25 | /// Ability to use Web project secrets. 26 | /// 27 | public bool UserSecrets { get; set; } 28 | 29 | /// 30 | /// Url for the azure key vault i.e. https://{vaultname}.vault.azure.net/. 31 | /// 32 | public string? AzureVault { get; set; } 33 | 34 | /// 35 | /// Prefix is based on Environment i.e. Development = dev, Production = prod. 36 | /// 37 | public bool UseAzureKeyPrefix { get; set; } 38 | 39 | /// 40 | /// Pass Hosting environment for the context of the application. 41 | /// 42 | public string HostingEnviroment { get; set; } = "Production"; 43 | 44 | /// 45 | /// Salesforce options. 46 | /// 47 | public SfConfig? Settings { get; set; } 48 | 49 | /// 50 | /// The name of the configuration section for the options. 51 | /// 52 | public string SectionName { get; set; } = "Salesforce"; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/AuthApp/ConsoleHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Runtime.InteropServices; 5 | using System.Xml.Linq; 6 | 7 | namespace AuthApp 8 | { 9 | internal static class ConsoleHandler 10 | { 11 | internal static Process OpenBrowser(string url) 12 | { 13 | try 14 | { 15 | return Process.Start(url); 16 | } 17 | catch 18 | { 19 | // hack because of this: https://github.com/dotnet/corefx/issues/10361 20 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 21 | { 22 | url = url.Replace("&", "^&"); 23 | return Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true }); 24 | } 25 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 26 | { 27 | return Process.Start("xdg-open", url); 28 | } 29 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 30 | { 31 | return Process.Start("open", url); 32 | } 33 | else 34 | { 35 | throw; 36 | } 37 | } 38 | } 39 | 40 | internal static string GetUserSecretsId() 41 | { 42 | var files = Directory.GetFiles("./", "*.csproj"); 43 | if (files.Length == 0) 44 | { 45 | throw new Exception("This command must be run in a project directory"); 46 | } 47 | 48 | var file = XElement.Load(files[0]); 49 | var groups = file.Elements(XName.Get("PropertyGroup")); 50 | foreach (var group in groups) 51 | { 52 | var secret = group.Element("UserSecretsId"); 53 | if (secret != null) 54 | { 55 | return secret.Value; 56 | } 57 | } 58 | 59 | throw new Exception("The UserSecretsId element was not found in your csproj. Please ensure it has been configured."); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/CometD.NetCore.Salesforce/IStreamingClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using CometD.NetCore.Bayeux.Client; 4 | 5 | namespace CometD.NetCore.Salesforce 6 | { 7 | /// 8 | /// The a wrapper class around client. 9 | /// 10 | public interface IStreamingClient : IDisposable 11 | { 12 | /// 13 | /// Event handler that sends event if the client must reconnect. 14 | /// 15 | event EventHandler Reconnect; 16 | 17 | /// 18 | /// Inject a strategy for handling invalid replay ids. 19 | /// 20 | Action InvalidReplayIdStrategy { get; set; } 21 | 22 | /// 23 | /// True/False if the is connected. 24 | /// 25 | bool IsConnected { get; } 26 | 27 | /// 28 | /// Connects to the Bayeux server. 29 | /// 30 | void Handshake(); 31 | 32 | /// 33 | /// Connects to the Bayeux server. 34 | /// 35 | /// 36 | void Handshake(int timeout); 37 | 38 | /// 39 | /// Disconnect Salesforce subscription to the platform events. 40 | /// 41 | void Disconnect(); 42 | 43 | /// 44 | /// Disconnect Salesforce subscription to the platform events. 45 | /// 46 | /// 47 | void Disconnect(int timeout); 48 | 49 | /// 50 | /// Subscribe to Salesforce Platform event. 51 | /// 52 | /// 53 | /// 54 | /// 55 | void SubscribeTopic(string topicName, IMessageListener listener, long replayId = -1); 56 | 57 | /// 58 | /// Unsubscribe from Salesforce Platform event. 59 | /// 60 | /// 61 | /// 62 | /// 63 | /// 64 | bool UnsubscribeTopic(string topicName, IMessageListener? listener = null, long replayId = -1); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/TestApp/README.md: -------------------------------------------------------------------------------- 1 | # TestApp for CometD.NetCore.Salesforce 2 | 3 | [![buymeacoffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/vyve0og) 4 | 5 | ## Give a Star! :star: 6 | 7 | If you like or are using this project to learn or start your solution, please give it a star. Thanks! 8 | 9 | ```csharp 10 | override void ErrorExtension_ConnectionError( 11 | object sender, 12 | string e) 13 | { 14 | // authentication failure 15 | if (string.Equals(e, "403::Handshake denied", StringComparison.OrdinalIgnoreCase) 16 | || string.Equals(e, "403:denied_by_security_policy:create_denied", StringComparison.OrdinalIgnoreCase) 17 | || string.Equals(e, "403::unknown client", StringComparison.OrdinalIgnoreCase)) 18 | { 19 | _logger.LogWarning("Handled CometD Exception: {message}", e); 20 | 21 | // 1. Disconnect existing client. 22 | Disconnect(); 23 | 24 | // 2. Invalidate the access token. 25 | _tokenResponse.Invalidate(); 26 | 27 | _logger.LogDebug("Invalidate token for {name} ...", nameof(BayeuxClient)); 28 | 29 | // 3. Recreate BayeuxClient and populate it with a new transport with new security headers. 30 | CreateBayeuxClient(); 31 | 32 | // 4. Invoke the Reconnect Event 33 | Reconnect?.Invoke(this, true); 34 | } 35 | else if (e.StartsWith("400::The replayId ", StringComparison.OrdinalIgnoreCase) 36 | && e.Contains("you provided was invalid. Please provide a valid ID, -2 to replay all events, or -1 to replay only new events", StringComparison.OrdinalIgnoreCase)) 37 | { 38 | _logger.LogWarning("Handled CometD Exception: {message}", e); 39 | 40 | /* 41 | logic to persist replayId which is being reset to -1 for this particular channel. 42 | that value is read when the channel is initialized. 43 | */ 44 | 45 | _logger.LogDebug($"Reset {channelName} with ReplayId -1"); 46 | _logger.LogDebug($"Initiate reconnect process.."); 47 | 48 | // 1. Disconnect existing client. 49 | Disconnect(); 50 | 51 | // 2. Invalidate the access token. 52 | _tokenResponse.Invalidate(); 53 | 54 | _logger.LogDebug("Invalidate token for {name} ...", nameof(BayeuxClient)); 55 | 56 | // 3. Recreate BayeuxClient and populate it with a new transport with new security headers. 57 | CreateBayeuxClient(); 58 | 59 | // 4. Invoke the Reconnect Event 60 | Reconnect?.Invoke(this, true); 61 | } 62 | ``` 63 | -------------------------------------------------------------------------------- /src/TestApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | 5 | using CometD.NetCore.Salesforce.Resilience; 6 | 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Hosting; 10 | using Microsoft.Extensions.Logging; 11 | 12 | namespace TestApp 13 | { 14 | #pragma warning disable RCS1102 // Make class static. 15 | internal sealed class Program 16 | #pragma warning restore RCS1102 // Make class static. 17 | { 18 | public static async Task Main(string[] args) 19 | { 20 | var host = new HostBuilder() 21 | .ConfigureHostConfiguration(configHost => 22 | { 23 | configHost.SetBasePath(Directory.GetCurrentDirectory()); 24 | configHost.AddJsonFile("hostsettings.json", optional: true); 25 | configHost.AddCommandLine(args); 26 | }) 27 | .ConfigureAppConfiguration((hostContext, configBuilder) => 28 | { 29 | configBuilder.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); 30 | configBuilder.AddJsonFile( 31 | $"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", 32 | optional: true); 33 | 34 | configBuilder.AddAzureKeyVault(hostingEnviromentName: hostContext.HostingEnvironment.EnvironmentName); 35 | configBuilder.AddCommandLine(args); 36 | 37 | // print out the environment 38 | var config = configBuilder.Build(); 39 | config.DebugConfigurations(); 40 | }) 41 | .ConfigureServices((context, services) => 42 | { 43 | services.AddTransient, DefaultServiceProviderFactory>(); 44 | 45 | services.AddResilientStreamingClient(); 46 | }) 47 | .ConfigureLogging((hostContext, configLogging) => 48 | { 49 | configLogging.AddConfiguration(hostContext.Configuration.GetSection("Logging")); 50 | configLogging.AddConsole(); 51 | configLogging.AddDebug(); 52 | }) 53 | .UseConsoleLifetime() 54 | .Build(); 55 | 56 | var srv = host.Services; 57 | 58 | await host.StartAsync(); 59 | 60 | var test = srv.GetRequiredService(); 61 | 62 | var result = await test.CountQueryAsync("SELECT COUNT() FROM ACCOUNT"); 63 | 64 | Console.WriteLine(result); 65 | 66 | await Task.Delay(TimeSpan.FromSeconds(15)); 67 | 68 | var result1 = await test.CountQueryAsync("SELECT COUNT() FROM ACCOUNT"); 69 | Console.WriteLine(result1); 70 | 71 | await host.StopAsync(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/CometD.NetCore.Salesforce/SalesforceConfiguration.cs: -------------------------------------------------------------------------------- 1 | using NetCoreForce.Client; 2 | 3 | namespace CometD.NetCore.Salesforce 4 | { 5 | /// 6 | /// Represents the configuration settings in appsettings.json. 7 | /// 8 | public sealed class SalesforceConfiguration 9 | { 10 | /// 11 | /// Consumer Id of the Salesforce Connected App. 12 | /// 13 | public string ClientId { get; set; } = string.Empty; 14 | 15 | /// 16 | /// Consumer secret of the Salesforce Connected App. 17 | /// 18 | public string ClientSecret { get; set; } = string.Empty; 19 | 20 | /// 21 | /// Username to login to Salesforce 22 | /// 23 | public string Username { get; set; } = string.Empty; 24 | 25 | /// 26 | /// Password to login to Salesforce 27 | /// 28 | public string Password { get; set; } = string.Empty; 29 | 30 | /// 31 | /// API Token for user identification to Salesforce API 32 | /// 33 | public string UserPasswordToken { get; set; } = string.Empty; 34 | 35 | /// 36 | /// Url to login to Salesforce 37 | /// https://test.salesforce.com/services/oauth2/authorize 38 | /// or https://login.salesforce.com/services/oauth2/authorize. 39 | /// 40 | public string LoginUrl { get; set; } = string.Empty; 41 | 42 | /// 43 | /// Url of the Salesforce organization. 44 | /// 45 | public string OrganizationUrl { get; set; } = string.Empty; 46 | 47 | /// 48 | /// Path of the endpoint to publish platform events. 49 | /// 50 | public string PublishEndpoint { get; set; } = string.Empty; 51 | 52 | /// 53 | /// OAuth refresh token of the Salesforce Connected App. 54 | /// 55 | public string RefreshToken { get; set; } = string.Empty; 56 | 57 | /// 58 | /// Uri to connect to CometD. 59 | /// 60 | public string CometDUri { get; set; } = string.Empty; 61 | 62 | /// 63 | /// Topic or Event Uri. 64 | /// 65 | public string EventOrTopicUri { get; set; } = string.Empty; 66 | 67 | /// 68 | /// Salesforce uri for OAuth authentication. 69 | /// 70 | public string OAuthUri { get; set; } = string.Empty; 71 | 72 | /// 73 | /// Retry for the connections and authentications. 74 | /// 75 | public int Retry { get; set; } 76 | 77 | /// 78 | /// The number to be used for BackoffPower for policy. The default is 2. 79 | /// 80 | public int BackoffPower { get; set; } = 2; 81 | 82 | /// 83 | /// The expiration for token. The default is 1:00 hour. 84 | /// 85 | public string TokenExpiration { get; set; } = "01:00:00"; 86 | 87 | /// 88 | /// The event that gets raised when a request for proposal is approved and the Deal is in working status. 89 | /// 90 | public string CustomEvent { get; set; } = string.Empty; 91 | 92 | /// 93 | /// Salesforce ReplayId for specific message. 94 | /// 95 | public int ReplayId { get; set; } 96 | 97 | /// 98 | /// Long polling duration. Default 120 * 1000. 99 | /// 100 | public long? ReadTimeOut { get; set; } 101 | 102 | /// 103 | /// The type of the Salesforce authentication token. Default "OAuth". 104 | /// 105 | public string TokenType { get; set; } = "OAuth"; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /CometD.NetCore.Salesforce.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28606.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8031B188-9FF6-4932-90AC-98DD21AF4D13}" 7 | ProjectSection(SolutionItems) = preProject 8 | .gitignore = .gitignore 9 | appveyor.yml = appveyor.yml 10 | clean.sh = clean.sh 11 | Directory.Build.props = Directory.Build.props 12 | Directory.Build.targets = Directory.Build.targets 13 | LICENSE = LICENSE 14 | README.md = README.md 15 | EndProjectSection 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AuthApp", "src\AuthApp\AuthApp.csproj", "{0AC0A8E4-AF3E-4078-A472-0D2DC1392C00}" 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{DF67E08B-61B2-46CD-B3B0-56F6BDC4313E}" 20 | ProjectSection(SolutionItems) = preProject 21 | build\dependecies.props = build\dependecies.props 22 | build\settings.props = build\settings.props 23 | build\sources.props = build\sources.props 24 | EndProjectSection 25 | EndProject 26 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F0246231-B425-4C7A-BD19-EF4177AF48C0}" 27 | EndProject 28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CometD.NetCore.Salesforce", "src\CometD.NetCore.Salesforce\CometD.NetCore.Salesforce.csproj", "{03FD9019-E9BC-4ADE-9BFF-F6FD459CD579}" 29 | EndProject 30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CometD.UnitTest", "test\CometD.UnitTest\CometD.UnitTest.csproj", "{62A83077-E672-4D88-81AC-08667FE898E3}" 31 | EndProject 32 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{1F013CA3-FD86-46BC-A162-B50DF96DE262}" 33 | EndProject 34 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApp", "src\TestApp\TestApp.csproj", "{5C96EB67-EC27-4C3B-ABFB-94B8B559CD6B}" 35 | EndProject 36 | Global 37 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 38 | Debug|Any CPU = Debug|Any CPU 39 | Release|Any CPU = Release|Any CPU 40 | EndGlobalSection 41 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 42 | {0AC0A8E4-AF3E-4078-A472-0D2DC1392C00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {0AC0A8E4-AF3E-4078-A472-0D2DC1392C00}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {0AC0A8E4-AF3E-4078-A472-0D2DC1392C00}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {0AC0A8E4-AF3E-4078-A472-0D2DC1392C00}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {03FD9019-E9BC-4ADE-9BFF-F6FD459CD579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {03FD9019-E9BC-4ADE-9BFF-F6FD459CD579}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {03FD9019-E9BC-4ADE-9BFF-F6FD459CD579}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {03FD9019-E9BC-4ADE-9BFF-F6FD459CD579}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {62A83077-E672-4D88-81AC-08667FE898E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {62A83077-E672-4D88-81AC-08667FE898E3}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {62A83077-E672-4D88-81AC-08667FE898E3}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {62A83077-E672-4D88-81AC-08667FE898E3}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {5C96EB67-EC27-4C3B-ABFB-94B8B559CD6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {5C96EB67-EC27-4C3B-ABFB-94B8B559CD6B}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {5C96EB67-EC27-4C3B-ABFB-94B8B559CD6B}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {5C96EB67-EC27-4C3B-ABFB-94B8B559CD6B}.Release|Any CPU.Build.0 = Release|Any CPU 58 | EndGlobalSection 59 | GlobalSection(SolutionProperties) = preSolution 60 | HideSolutionNode = FALSE 61 | EndGlobalSection 62 | GlobalSection(NestedProjects) = preSolution 63 | {0AC0A8E4-AF3E-4078-A472-0D2DC1392C00} = {F0246231-B425-4C7A-BD19-EF4177AF48C0} 64 | {03FD9019-E9BC-4ADE-9BFF-F6FD459CD579} = {F0246231-B425-4C7A-BD19-EF4177AF48C0} 65 | {62A83077-E672-4D88-81AC-08667FE898E3} = {1F013CA3-FD86-46BC-A162-B50DF96DE262} 66 | {5C96EB67-EC27-4C3B-ABFB-94B8B559CD6B} = {F0246231-B425-4C7A-BD19-EF4177AF48C0} 67 | EndGlobalSection 68 | GlobalSection(ExtensibilityGlobals) = postSolution 69 | SolutionGuid = {800A565D-7D93-4523-AD7A-EFA4067997DC} 70 | EndGlobalSection 71 | EndGlobal 72 | -------------------------------------------------------------------------------- /src/AuthApp/README.md: -------------------------------------------------------------------------------- 1 | # `salesforce` CLI authentication tool 2 | 3 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/kdcllc/cometd-netcore-salesforce/master/LICENSE) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/baalfhs6vvc38icc?svg=true)](https://ci.appveyor.com/project/kdcllc/cometd-netcore-salesforce) 5 | [![NuGet](https://img.shields.io/nuget/v/salesforce.svg)](https://www.nuget.org/packages?q=Bet.AspNetCore) 6 | [![feedz.io](https://img.shields.io/badge/endpoint.svg?url=https://f.feedz.io/kdcllc/kdcllc/shield/salesforce/latest)](https://f.feedz.io/kdcllc/kdcllc/packages/salesforce/latest/download) 7 | 8 | _Note: Pre-release packages are distributed via [feedz.io](https://f.feedz.io/kdcllc/kcllc/nuget/index.json)._ 9 | 10 | ## Summary 11 | 12 | This is a dotnet cli Saleforce Refresh and Access Tokens Generation tool. 13 | 14 | [![buymeacoffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/vyve0og) 15 | 16 | ## Give a Star! :star: 17 | 18 | If you like or are using this project to learn or start your solution, please give it a star. Thanks! 19 | 20 | ## Install DotNetCore Cli `salesforce` tool 21 | 22 | ```bash 23 | dotnet tool install salesforce -g 24 | ``` 25 | 26 | To verify the installation run: 27 | 28 | ```bash 29 | dotnet tool list -g 30 | ``` 31 | 32 | ## Usage of Salesforce dotnet cli tool 33 | 34 | There are several ways to run this cli tool. 35 | 36 | This tool will open web browser and will require you to log in with your credentials to Salesforce portal in order to retrieve the tokens. 37 | 38 | 1. From any location with Consumer Key and Secret provided 39 | 40 | ```bash 41 | # specify the custom login url 42 | salesforce get-tokens --key:{key} --secret:{secret} --login:https://login.salesforce.com --verbose:information 43 | 44 | # use default login url 45 | salesforce get-tokens --key:{key} --secret:{secret} --verbose 46 | ``` 47 | 48 | 2. Running the tool in the directory that contains `appsettings.json` file with configurations 49 | 50 | ```bash 51 | salesforce get-tokens --section:Salesforce 52 | ``` 53 | 54 | Note: required configurations are as follows: 55 | 56 | ```json 57 | "Salesforce": { 58 | "ClientId": "", 59 | "ClientSecret": "", 60 | "LoginUrl": "" 61 | } 62 | ```` 63 | 64 | 3. Running with Azure Vault 65 | 66 | a.) Location with `appsettings.json` file 67 | 68 | ```json 69 | "AzureVault": { 70 | "BaseUrl": "https://{name}.vault.azure.net/" 71 | }, 72 | ``` 73 | 74 | ```bash 75 | salesforce get-tokens --verbose:debug 76 | ``` 77 | b.) From any location 78 | 79 | Or specify url within the dotnet cli tool like so: 80 | 81 | ```cmd 82 | salesforce get-tokens --azure https://{name}.vault.azure.net/" 83 | ``` 84 | 85 | ## Tools possible switches 86 | 87 | - `--key` or `-k` (Salesforce `Consumer Key`) 88 | - `--secret` or `-s` (Salesforce `Consumer Secret`) 89 | - `--login` or `-l` (Salesforce login url) 90 | - `--azure` or `-a` (Azure Vault Url) 91 | - `--azureprefix` or `ax` ([Use Environment prefix for Azure vault](https://github.com/kdcllc/Bet.AspNetCore/blob/d8ff3b7bfb13817bc2b6b768d91ea19a2bc865a5/src/Bet.Extensions.AzureVault/AzureVaultKeyBuilder.cs#L24)) 92 | - `--configfile` or `-c` (Specify configuration file) 93 | - `--verbose:debug` or `--verbose:information` or `--verbose:trave` 94 | - `--usesecrets` or `us` (Usually a Guid Id of the project that contains the secret) 95 | - `--environment` or `-e` (Production, Development, Stage) 96 | - `--section` or `-sn` (The root for the tools configuration the default is `Salesforce`) 97 | 98 | ## Build self contained 99 | 100 | [rid-catalog](https://docs.microsoft.com/en-us/dotnet/core/rid-catalog) 101 | 102 | ```bash 103 | # windows 104 | dotnet build -r win-x64 -c Release -p:PackAsTool=false 105 | dotnet publish -r win-x64 -c Release -p:PackAsTool=false -p:PublishSingleFile=true -p:PublishTrimmed=true -p:PublishReadyToRun=true -f netcoreapp3.0 -o ../../packages 106 | 107 | # linux 108 | dotnet build -r linux-x64 -c Release -p:PackAsTool=false 109 | dotnet publish -r linux-x64 -c Release -p:PackAsTool=false -p:PublishSingleFile=true -p:PublishTrimmed=true -p:PublishReadyToRun=false -f netcoreapp3.0 -o ../../packages 110 | ``` 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CometD.NetCore.Salesforce 2 | 3 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/kdcllc/cometd-netcore-salesforce/master/LICENSE) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/baalfhs6vvc38icc?svg=true)](https://ci.appveyor.com/project/kdcllc/cometd-netcore-salesforce) 5 | [![NuGet](https://img.shields.io/nuget/v/CometD.NetCore.Salesforce.svg)](https://www.nuget.org/packages?q=Bet.AspNetCore) 6 | ![Nuget](https://img.shields.io/nuget/dt/CometD.NetCore.Salesforce) 7 | [![feedz.io](https://img.shields.io/badge/endpoint.svg?url=https://f.feedz.io/kdcllc/kdcllc/shield/CometD.NetCore.Salesforce/latest)](https://f.feedz.io/kdcllc/kdcllc/packages/CometD.NetCore.Salesforce/latest/download) 8 | 9 | _Note: Pre-release packages are distributed via [feedz.io](https://f.feedz.io/kdcllc/kcllc/nuget/index.json)._ 10 | 11 | ## Summary 12 | 13 | This repo contains the CometD .NET Core implementation for Salesforce Platform events. 14 | 15 | These events can be subscribed to and listened to by your custom `Event Listener`. The sample application of this library can be found [here](https://github.com/kdcllc/Bet.BuildingBlocks.SalesforceEventBus). 16 | 17 | 18 | The solution contains the following: 19 | 20 | 1. [`CometD.NetCore2.Salesforce`](./src/CometD.NetCore.Salesforce/) 21 | - A Salesforce Platform Events implementation based [Even Bus idea of eShopOnContainers](https://github.com/dotnet-architecture/eShopOnContainers). 22 | - [Reusable Building Blocks and sample application that listens to Salesforce push events](https://github.com/kdcllc/Bet.BuildingBlocks.SalesforceEventBus). 23 | 24 | 2. [DotNet Cli tool `salesforce`](./src/AuthApp/) 25 | - This dotnet cli tool allows for retrieval of `Access` or `Refresh Tokens` to be used by any other application. 26 | Please refer to [How Are Apps Authenticated with the Web Server OAuth Authentication Flow](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_understanding_web_server_oauth_flow.htm) 27 | 28 | [![buymeacoffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/vyve0og) 29 | 30 | ## Give a Star! :star: 31 | 32 | If you like or are using this project to learn or start your solution, please give it a star. Thanks! 33 | 34 | ## Install 35 | 36 | ```csharp 37 | dotnet add package CometD.NetCore.Salesforce 38 | ``` 39 | 40 | ## Saleforce Setup 41 | 42 | [Watch Video](https://www.youtube.com/watch?v=L6OWyCfQD6U) 43 | 44 | 1. Sing up for development sandbox with Saleforce: [https://developer.salesforce.com/signup](https://developer.salesforce.com/signup). 45 | 2. Create Connected App in Salesforce. 46 | 3. Create a Platform Event. 47 | 48 | ### Create Connected App in Salesforce 49 | 50 | 1. Setup -> Quick Find -> manage -> App Manager -> New Connected App. 51 | 2. Basic Info: 52 | 53 | ![info](./img/new-app-basic-info.jpg) 54 | 55 | 3. API (Enable OAuth Settings): 56 | ![settings](./img/new-app-api-auth.jpg) 57 | 58 | 4. Retrieve `Consumer Key` and `Consumer Secret` to be used within the Test App 59 | 60 | ### Create a Platform Event 61 | 1. Setup -> Quick Find -> Events -> Platform Events -> New Platform Event: 62 | 63 | ![event](./img/new-platform-event.jpg) 64 | 65 | 2. Add Custom Field 66 | 67 | ![event](./img/new-platform-event-field.jpg) 68 | 69 | (note: use sandbox custom domain for the login to workbench in order to install this app within your production) 70 | 71 | Use workbench to test the Event [workbench](https://workbench.developerforce.com/login.php?startUrl=%2Finsert.php) 72 | 73 | ## AuthApp 74 | 75 | ### OAuth Refresh Token Flow 76 | 77 | [Use login instead of test](https://github.com/developerforce/Force.com-Toolkit-for-NET/wiki/Web-Server-OAuth-Flow-Sample#am-i-using-the-test-environment) 78 | Simple application that provides with Web Server OAuth Authentication Flow to retrieve 79 | `Access Token` and `Refresh Token` to be used within the application. 80 | 81 | ### Username/Password Flow 82 | 83 | To enable Username/Password flow and grant type, simply omit the auth token and refresh token while providing the username, password and user api token. 84 | 85 | ## Special thanks to our contributors 86 | 87 | * [cwoolum](https://github.com/cwoolum) 88 | * [ts46235](https://github.com/ts46235) 89 | * [cternes](https://github.com/cternes) 90 | * [apaulro](https://github.com/apaulro) 91 | 92 | ## Related projects 93 | 94 | - [Oyatel/CometD.NET](https://github.com/Oyatel/CometD.NET) 95 | - [nthachus/CometD.NET](https://github.com/nthachus/CometD.NET) 96 | - [tdawgy/CometD.NetCore](https://github.com/tdawgy/CometD.NetCore) 97 | - [eShopOnContainers](https://github.com/dotnet-architecture/eShopOnContainers) 98 | 99 | -------------------------------------------------------------------------------- /src/AuthApp/TokenGeneratorCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Threading.Tasks; 4 | 5 | using AuthApp.Internal; 6 | 7 | using McMaster.Extensions.CommandLineUtils; 8 | 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Hosting; 11 | using Microsoft.Extensions.Logging; 12 | using Microsoft.Extensions.Options; 13 | 14 | using Console = Colorful.Console; 15 | 16 | namespace AuthApp 17 | { 18 | [Command( 19 | "get-tokens", 20 | Description = "Generates Salesforce Access and Refresh Tokens", 21 | UnrecognizedArgumentHandling = UnrecognizedArgumentHandling.Throw, 22 | AllowArgumentSeparator = true)] 23 | [HelpOption("-?")] 24 | internal class TokenGeneratorCommand 25 | { 26 | [Option("-k|--key", Description = "The Salesforce Consumer Key.")] 27 | public string? ClientId { get; set; } 28 | 29 | [Option("-s|--secret", Description = "The Salesforce Consumer Secret.")] 30 | public string? ClientSecret { get; set; } 31 | 32 | [Option("-l|--login", Description = "The Salesforce login url. The default value is https://login.salesforce.com.")] 33 | public string? LoginUrl { get; set; } 34 | 35 | [Option( 36 | "-a|--azure", 37 | Description = "Allows to specify Azure Vault Url. It overrides url specified in the appsetting.json file or any other configuration provider.")] 38 | public string? AzureVault { get; set; } 39 | 40 | [Option("-ax|--azureprefix", Description = "Enables or disables Hosting Environment prefix to be used for Azure Key Vault. Default is true.")] 41 | public bool UseAzureKeyPrefix { get; set; } 42 | 43 | [Option("-c|--configfile", Description = "Allows to specify a configuration file besides appsettings.json to be specified.")] 44 | public string? ConfigFile { get; set; } 45 | 46 | /// 47 | /// Property types of ValueTuple{bool,T} translate to CommandOptionType.SingleOrNoValue. 48 | /// Input | Value 49 | /// ------------------------|-------------------------------- 50 | /// (none) | (false, default(LogLevel)) 51 | /// --verbose | (true, LogLevel.Information) 52 | /// --verbose:information | (true, LogLevel.Information) 53 | /// --verbose:debug | (true, LogLevel.Debug) 54 | /// --verbose:trace | (true, LogLevel.Trace). 55 | /// 56 | [Option(Description = "Allows Verbose logging for the tool. Enable this to get tracing information. Default is false and LogLevel.None.")] 57 | public (bool HasValue, LogLevel level) Verbose { get; } = (false, LogLevel.None); 58 | 59 | [Option("-us|--usesecrets", Description = "Enable UserSecrets.")] 60 | public bool UserSecrets { get; set; } 61 | 62 | [Option("-e|--environment", Description = "Specify Hosting Environment Name for the cli tool execution.")] 63 | public string? HostingEnviroment { get; set; } 64 | 65 | [Option("-sn|--section", Description = "Configuration Section Name to retrieve the options. The Default value is Salesforce.")] 66 | public string? SectionName { get; set; } 67 | 68 | private async Task OnExecuteAsync(CommandLineApplication app) 69 | { 70 | var builderConfig = new HostBuilderOptions 71 | { 72 | AzureVault = AzureVault, 73 | UseAzureKeyPrefix = !UseAzureKeyPrefix, 74 | ConfigFile = ConfigFile, 75 | Verbose = Verbose.HasValue, 76 | Level = Verbose.level, 77 | UserSecrets = UserSecrets, 78 | HostingEnviroment = !string.IsNullOrWhiteSpace(HostingEnviroment) ? HostingEnviroment ?? "Development" : "Development", 79 | Settings = new SfConfig 80 | { 81 | ClientId = ClientId, 82 | ClientSecret = ClientSecret, 83 | LoginUrl = !string.IsNullOrWhiteSpace(LoginUrl) ? LoginUrl : "https://login.salesforce.com" 84 | }, 85 | SectionName = string.IsNullOrWhiteSpace(SectionName) ? "Salesforce" : SectionName ?? "Salesforce", 86 | }; 87 | 88 | try 89 | { 90 | var builder = HostBuilderExtensions.CreateDefaultBuilder(builderConfig) 91 | .ConfigureServices((hostingContext, services) => 92 | { 93 | services.ConfigureWithDataAnnotationsValidation(hostingContext.Configuration, builderConfig.SectionName); 94 | services.AddHostedService(); 95 | }); 96 | 97 | await builder.RunConsoleAsync(); 98 | 99 | return 0; 100 | } 101 | catch (OptionsValidationException exv) 102 | { 103 | Console.WriteLine($"Not all of the required configurations has been provided. {exv.Message}", Color.Red); 104 | 105 | app.ShowHelp(); 106 | } 107 | catch (Exception ex) 108 | { 109 | Console.WriteLine(ex.Message, Color.Red); 110 | } 111 | 112 | return 0; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/AuthApp/HostBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Drawing; 3 | using System.IO; 4 | 5 | using Bet.Extensions.Options; 6 | 7 | using McMaster.Extensions.CommandLineUtils; 8 | 9 | using Microsoft.Extensions.Configuration; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Hosting; 12 | using Microsoft.Extensions.Logging; 13 | 14 | using TextCopy; 15 | 16 | using Console = Colorful.Console; 17 | 18 | namespace AuthApp 19 | { 20 | internal static class HostBuilderExtensions 21 | { 22 | internal static IHostBuilder CreateDefaultBuilder(HostBuilderOptions options) 23 | { 24 | var builder = new HostBuilder(); 25 | 26 | builder.UseEnvironment(options.HostingEnviroment); 27 | var fullPath = Directory.GetCurrentDirectory(); 28 | 29 | if (!string.IsNullOrWhiteSpace(Path.GetDirectoryName(options.ConfigFile))) 30 | { 31 | fullPath = Path.GetDirectoryName(options.ConfigFile); 32 | } 33 | 34 | builder.UseContentRoot(fullPath); 35 | 36 | var defaultConfigName = !string.IsNullOrWhiteSpace(options.ConfigFile) ? Path.GetFileName(options.ConfigFile) : "appsettings.json"; 37 | 38 | if (options.Verbose) 39 | { 40 | Console.WriteLine($"ContentRoot:{fullPath}", color: Color.Green); 41 | } 42 | 43 | builder 44 | .UseOptionValidation() 45 | .ConfigureAppConfiguration((context, config) => 46 | { 47 | // appsettings file or others 48 | config.AddJsonFile(Path.Combine(fullPath, $"{defaultConfigName.Split(".")[0]}.json"), optional: true) 49 | .AddJsonFile(Path.Combine(fullPath, $"{defaultConfigName.Split(".")[0]}.{options.HostingEnviroment}.json"), optional: true); 50 | 51 | // add secrets if specified 52 | if (options.UserSecrets) 53 | { 54 | config.AddUserSecrets(ConsoleHandler.GetUserSecretsId()); 55 | } 56 | 57 | // configure Azure Vault from the other settings. 58 | var appAzureVaultUrl = config.Build().Bind("AzureVault", enableValidation: false); 59 | 60 | var inputValues = new Dictionary 61 | { 62 | { $"{options.SectionName}:ClientId", options?.Settings?.ClientId ?? string.Empty }, 63 | { $"{options.SectionName}:ClientSecret", options?.Settings?.ClientSecret ?? string.Empty }, 64 | { $"{options.SectionName}:LoginUrl", options?.Settings?.LoginUrl ?? string.Empty }, 65 | { $"{options.SectionName}:OAuthUri", options?.Settings?.OAuthUri ?? string.Empty }, 66 | { $"{options.SectionName}:OAuthorizeUri", options?.Settings?.OAuthorizeUri ?? string.Empty }, 67 | }; 68 | 69 | config.AddInMemoryCollection(inputValues); 70 | 71 | // build azure key vault from passed in parameter 72 | if (!string.IsNullOrWhiteSpace(options?.AzureVault)) 73 | { 74 | var dic = new Dictionary 75 | { 76 | { "AzureVault:BaseUrl", options.AzureVault } 77 | }; 78 | 79 | config.AddInMemoryCollection(dic); 80 | } 81 | 82 | // use appsettings vault information 83 | if (!string.IsNullOrWhiteSpace(appAzureVaultUrl.BaseUrl) 84 | || !string.IsNullOrWhiteSpace(options?.AzureVault)) 85 | { 86 | config.AddAzureKeyVault(hostingEnviromentName: options.HostingEnviroment, options.UseAzureKeyPrefix); 87 | } 88 | 89 | if ((options.Verbose && options.Level == LogLevel.Debug) 90 | || options.Level == LogLevel.Trace) 91 | { 92 | config.Build().DebugConfigurations(); 93 | } 94 | }); 95 | 96 | builder 97 | .ConfigureLogging((_, configureBuilder) => 98 | { 99 | if (options.Verbose) 100 | { 101 | configureBuilder.AddConsole(); 102 | configureBuilder.AddDebug(); 103 | } 104 | }); 105 | 106 | builder 107 | .ConfigureServices((context, services) => 108 | { 109 | services.AddSingleton(options); 110 | 111 | services.AddHostedService(); 112 | 113 | // disable hosting messages 114 | services.Configure(opt => opt.SuppressStatusMessages = true); 115 | 116 | if (options.Verbose) 117 | { 118 | services.AddLogging(x => x.AddFilter((loglevel) => 119 | { 120 | return loglevel == options.Level; 121 | })); 122 | } 123 | 124 | services.AddSingleton(); 125 | 126 | services.AddSingleton(PhysicalConsole.Singleton); 127 | }); 128 | 129 | return builder; 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/AuthApp/Internal/HttpServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.IO; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | using Microsoft.Extensions.Hosting; 11 | 12 | using NetCoreForce.Client; 13 | 14 | using TextCopy; 15 | 16 | using Console = Colorful.Console; 17 | 18 | namespace AuthApp.Internal 19 | { 20 | /// 21 | /// Web Server OAuth Authentication Flow 22 | /// https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_understanding_web_server_oauth_flow.htm. 23 | /// 24 | internal class HttpServer : BackgroundService 25 | { 26 | private readonly HostBuilderOptions _hostOptions; 27 | private readonly SfConfig _config; 28 | private readonly IHostApplicationLifetime _applicationLifetime; 29 | private readonly IClipboard _clipboard; 30 | private bool _isCompleted = false; 31 | 32 | public HttpServer( 33 | HostBuilderOptions hostOptions, 34 | SfConfig config, 35 | IHostApplicationLifetime applicationLifetime, 36 | IClipboard clipboard) 37 | { 38 | _hostOptions = hostOptions; 39 | _config = config; 40 | _applicationLifetime = applicationLifetime; 41 | _clipboard = clipboard; 42 | } 43 | 44 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 45 | { 46 | Console.WriteLine(); 47 | Console.WriteAscii("Token Generation Started...", Colorful.FigletFont.Default); 48 | Console.WriteLine(); 49 | 50 | if (_hostOptions.Verbose) 51 | { 52 | Console.WriteLine($"{nameof(HttpServer)} is starting."); 53 | } 54 | 55 | var http = new HttpListener(); 56 | var redirectURI = string.Format("http://{0}:{1}/", "localhost", GetRandomUnusedPort()); 57 | http.Prefixes.Add(redirectURI); 58 | http.Start(); 59 | 60 | var authUrl = GetAuthorizationUrl(redirectURI); 61 | 62 | if (_hostOptions.Verbose) 63 | { 64 | Console.WriteLine($"Opening a browser window with Url: {authUrl}", Color.Blue); 65 | } 66 | 67 | var process = ConsoleHandler.OpenBrowser(authUrl); 68 | 69 | var context = await http.GetContextAsync(); 70 | 71 | while (!stoppingToken.IsCancellationRequested) 72 | { 73 | if (_isCompleted) 74 | { 75 | _applicationLifetime.StopApplication(); 76 | return; 77 | } 78 | 79 | if (_hostOptions.Verbose) 80 | { 81 | Console.WriteLine($"{nameof(HttpServer)} is running"); 82 | } 83 | 84 | if (context != null) 85 | { 86 | var responseOutput = await ShowBrowserMessage(context); 87 | 88 | responseOutput.Close(); 89 | 90 | if (context.Request.QueryString.Get("error") != null) 91 | { 92 | Console.WriteLine($"OAuth authorization error: {context.Request.QueryString.Get("error")}.", Color.Red); 93 | } 94 | 95 | if (context.Request.QueryString.Get("code") == null) 96 | { 97 | Console.WriteLine($"Malformed authorization response {context.Request.QueryString}", Color.Red); 98 | } 99 | 100 | // Authorization code the consumer must use to obtain the access and refresh tokens. 101 | // The authorization code expires after 15 minutes. 102 | var code = context.Request.QueryString.Get("code"); 103 | Console.WriteLine($"The authorization code will expire in 15 minutes: {code}", Color.Blue); 104 | 105 | var auth = new AuthenticationClient(); 106 | await auth.WebServerAsync( 107 | _config.ClientId, 108 | _config.ClientSecret, 109 | redirectURI, 110 | code, 111 | $"{_config.LoginUrl}{_config.OAuthUri}"); 112 | 113 | Console.WriteLineFormatted("Access_token = {0}", Color.Green, Color.Yellow, auth.AccessInfo.AccessToken); 114 | 115 | Console.WriteLineFormatted("Refresh_token = {0}", Color.Green, Color.Yellow, auth.AccessInfo.RefreshToken); 116 | 117 | await _clipboard.SetTextAsync(auth.AccessInfo.RefreshToken); 118 | 119 | Console.WriteLine($"Refresh_token copied to the Clipboard", color: Color.Yellow); 120 | 121 | _isCompleted = true; 122 | 123 | http.Stop(); 124 | if (_hostOptions.Verbose) 125 | { 126 | Console.WriteLine($"{nameof(HttpServer)} is stopping."); 127 | } 128 | } 129 | 130 | await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); 131 | 132 | Console.WriteLine(); 133 | Console.Write("Thanks for using this cli tool"); 134 | } 135 | } 136 | 137 | private static async Task ShowBrowserMessage(HttpListenerContext context) 138 | { 139 | var response = context.Response; 140 | var responseString = string.Format(@" 141 | 142 | Please return to the console to retrieve access and refresh tokens. 143 | "); 144 | 145 | var buffer = Encoding.UTF8.GetBytes(responseString); 146 | response.ContentLength64 = buffer.Length; 147 | var responseOutput = response.OutputStream; 148 | await responseOutput.WriteAsync(buffer, 0, buffer.Length); 149 | return responseOutput; 150 | } 151 | 152 | private int GetRandomUnusedPort() 153 | { 154 | var listener = new TcpListener(IPAddress.Loopback, 5050); 155 | listener.Start(); 156 | var port = ((IPEndPoint)listener.LocalEndpoint).Port; 157 | listener.Stop(); 158 | return port; 159 | } 160 | 161 | private string GetAuthorizationUrl(string redirectURI) 162 | { 163 | var authEndpoint = $"{_config.LoginUrl}{_config.OAuthorizeUri}"; 164 | var url = $"{authEndpoint}?response_type=code&access_type=offline&scope=openid%20profile%20api%20refresh_token%20offline_access&redirect_uri={Uri.EscapeDataString(redirectURI)}&client_id={_config.ClientId}"; 165 | return url; 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | *appsettings.Development.json 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015/2017 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # Visual Studio 2017 auto generated files 34 | Generated\ Files/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # Benchmark Results 50 | BenchmarkDotNet.Artifacts/ 51 | 52 | # .NET Core 53 | project.lock.json 54 | project.fragment.lock.json 55 | artifacts/ 56 | #**/Properties/launchSettings.json 57 | 58 | # StyleCop 59 | StyleCopReport.xml 60 | 61 | # Files built by Visual Studio 62 | *_i.c 63 | *_p.c 64 | *_i.h 65 | *.ilk 66 | *.meta 67 | *.obj 68 | *.iobj 69 | *.pch 70 | *.pdb 71 | *.ipdb 72 | *.pgc 73 | *.pgd 74 | *.rsp 75 | *.sbr 76 | *.tlb 77 | *.tli 78 | *.tlh 79 | *.tmp 80 | *.tmp_proj 81 | *.log 82 | *.vspscc 83 | *.vssscc 84 | .builds 85 | *.pidb 86 | *.svclog 87 | *.scc 88 | 89 | # Chutzpah Test files 90 | _Chutzpah* 91 | 92 | # Visual C++ cache files 93 | ipch/ 94 | *.aps 95 | *.ncb 96 | *.opendb 97 | *.opensdf 98 | *.sdf 99 | *.cachefile 100 | *.VC.db 101 | *.VC.VC.opendb 102 | 103 | # Visual Studio profiler 104 | *.psess 105 | *.vsp 106 | *.vspx 107 | *.sap 108 | 109 | # Visual Studio Trace Files 110 | *.e2e 111 | 112 | # TFS 2012 Local Workspace 113 | $tf/ 114 | 115 | # Guidance Automation Toolkit 116 | *.gpState 117 | 118 | # ReSharper is a .NET coding add-in 119 | _ReSharper*/ 120 | *.[Rr]e[Ss]harper 121 | *.DotSettings.user 122 | 123 | # JustCode is a .NET coding add-in 124 | .JustCode 125 | 126 | # TeamCity is a build add-in 127 | _TeamCity* 128 | 129 | # DotCover is a Code Coverage Tool 130 | *.dotCover 131 | 132 | # AxoCover is a Code Coverage Tool 133 | .axoCover/* 134 | !.axoCover/settings.json 135 | 136 | # Visual Studio code coverage results 137 | *.coverage 138 | *.coveragexml 139 | 140 | # NCrunch 141 | _NCrunch_* 142 | .*crunch*.local.xml 143 | nCrunchTemp_* 144 | 145 | # MightyMoose 146 | *.mm.* 147 | AutoTest.Net/ 148 | 149 | # Web workbench (sass) 150 | .sass-cache/ 151 | 152 | # Installshield output folder 153 | [Ee]xpress/ 154 | 155 | # DocProject is a documentation generator add-in 156 | DocProject/buildhelp/ 157 | DocProject/Help/*.HxT 158 | DocProject/Help/*.HxC 159 | DocProject/Help/*.hhc 160 | DocProject/Help/*.hhk 161 | DocProject/Help/*.hhp 162 | DocProject/Help/Html2 163 | DocProject/Help/html 164 | 165 | # Click-Once directory 166 | publish/ 167 | 168 | # Publish Web Output 169 | *.[Pp]ublish.xml 170 | *.azurePubxml 171 | # Note: Comment the next line if you want to checkin your web deploy settings, 172 | # but database connection strings (with potential passwords) will be unencrypted 173 | *.pubxml 174 | *.publishproj 175 | 176 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 177 | # checkin your Azure Web App publish settings, but sensitive information contained 178 | # in these scripts will be unencrypted 179 | PublishScripts/ 180 | 181 | # NuGet Packages 182 | *.nupkg 183 | # The packages folder can be ignored because of Package Restore 184 | **/[Pp]ackages/* 185 | # except build/, which is used as an MSBuild target. 186 | !**/[Pp]ackages/build/ 187 | # Uncomment if necessary however generally it will be regenerated when needed 188 | #!**/[Pp]ackages/repositories.config 189 | # NuGet v3's project.json files produces more ignorable files 190 | *.nuget.props 191 | *.nuget.targets 192 | 193 | # Microsoft Azure Build Output 194 | csx/ 195 | *.build.csdef 196 | 197 | # Microsoft Azure Emulator 198 | ecf/ 199 | rcf/ 200 | 201 | # Windows Store app package directories and files 202 | AppPackages/ 203 | BundleArtifacts/ 204 | Package.StoreAssociation.xml 205 | _pkginfo.txt 206 | *.appx 207 | 208 | # Visual Studio cache files 209 | # files ending in .cache can be ignored 210 | *.[Cc]ache 211 | # but keep track of directories ending in .cache 212 | !*.[Cc]ache/ 213 | 214 | # Others 215 | ClientBin/ 216 | ~$* 217 | *~ 218 | *.dbmdl 219 | *.dbproj.schemaview 220 | *.jfm 221 | *.pfx 222 | *.publishsettings 223 | orleans.codegen.cs 224 | 225 | # Including strong name files can present a security risk 226 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 227 | #*.snk 228 | 229 | # Since there are multiple workflows, uncomment next line to ignore bower_components 230 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 231 | #bower_components/ 232 | 233 | # RIA/Silverlight projects 234 | Generated_Code/ 235 | 236 | # Backup & report files from converting an old project file 237 | # to a newer Visual Studio version. Backup files are not needed, 238 | # because we have git ;-) 239 | _UpgradeReport_Files/ 240 | Backup*/ 241 | UpgradeLog*.XML 242 | UpgradeLog*.htm 243 | ServiceFabricBackup/ 244 | *.rptproj.bak 245 | 246 | # SQL Server files 247 | *.mdf 248 | *.ldf 249 | *.ndf 250 | 251 | # Business Intelligence projects 252 | *.rdl.data 253 | *.bim.layout 254 | *.bim_*.settings 255 | *.rptproj.rsuser 256 | 257 | # Microsoft Fakes 258 | FakesAssemblies/ 259 | 260 | # GhostDoc plugin setting file 261 | *.GhostDoc.xml 262 | 263 | # Node.js Tools for Visual Studio 264 | .ntvs_analysis.dat 265 | node_modules/ 266 | 267 | # Visual Studio 6 build log 268 | *.plg 269 | 270 | # Visual Studio 6 workspace options file 271 | *.opt 272 | 273 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 274 | *.vbw 275 | 276 | # Visual Studio LightSwitch build output 277 | **/*.HTMLClient/GeneratedArtifacts 278 | **/*.DesktopClient/GeneratedArtifacts 279 | **/*.DesktopClient/ModelManifest.xml 280 | **/*.Server/GeneratedArtifacts 281 | **/*.Server/ModelManifest.xml 282 | _Pvt_Extensions 283 | 284 | # Paket dependency manager 285 | .paket/paket.exe 286 | paket-files/ 287 | 288 | # FAKE - F# Make 289 | .fake/ 290 | 291 | # JetBrains Rider 292 | .idea/ 293 | *.sln.iml 294 | 295 | # CodeRush 296 | .cr/ 297 | 298 | # Python Tools for Visual Studio (PTVS) 299 | __pycache__/ 300 | *.pyc 301 | 302 | # Cake - Uncomment if you are using it 303 | # tools/** 304 | # !tools/packages.config 305 | 306 | # Tabs Studio 307 | *.tss 308 | 309 | # Telerik's JustMock configuration file 310 | *.jmconfig 311 | 312 | # BizTalk build output 313 | *.btp.cs 314 | *.btm.cs 315 | *.odx.cs 316 | *.xsd.cs 317 | 318 | # OpenCover UI analysis results 319 | OpenCover/ 320 | 321 | # Azure Stream Analytics local run output 322 | ASALocalRun/ 323 | 324 | # MSBuild Binary and Structured Log 325 | *.binlog 326 | 327 | # NVidia Nsight GPU debugger configuration file 328 | *.nvuser 329 | 330 | # MFractors (Xamarin productivity tool) working folder 331 | .mfractor/ 332 | -------------------------------------------------------------------------------- /src/CometD.NetCore.Salesforce/DependencyInjection/StreamingClientServiceExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | using CometD.NetCore.Salesforce; 5 | using CometD.NetCore.Salesforce.Resilience; 6 | 7 | using Microsoft.Extensions.DependencyInjection.Extensions; 8 | using Microsoft.Extensions.Options; 9 | 10 | using NetCoreForce.Client; 11 | using NetCoreForce.Client.Models; 12 | 13 | using Polly; 14 | 15 | namespace Microsoft.Extensions.DependencyInjection 16 | { 17 | /// 18 | /// An extension method for . 19 | /// 20 | public static class StreamingClientServiceExtensions 21 | { 22 | /// 23 | /// Add custom implementation for . 24 | /// There can be only one implementation registered with DI at any time. 25 | /// 26 | /// The DI services. 27 | /// The section name for the root options configuration. The default value is Salesforce. 28 | /// 29 | /// The option configuration that can be override the configuration provides. 30 | /// 31 | public static IServiceCollection AddResilientStreamingClient( 32 | this IServiceCollection services, 33 | string sectionName = "Salesforce", 34 | string optionName = "", 35 | Action? configureOptions = default) 36 | where T : IStreamingClient 37 | { 38 | services.AddChangeTokenOptions( 39 | sectionName, 40 | optionName: optionName, 41 | configureAction: (o, sp) => configureOptions?.Invoke(o, sp)); 42 | 43 | services.AddResilentForceClient(optionName); 44 | 45 | services.TryAdd(new ServiceDescriptor(typeof(IStreamingClient), typeof(T), ServiceLifetime.Singleton)); 46 | 47 | return services; 48 | } 49 | 50 | 51 | /// 52 | /// Adds ForecClient Resilient version of it with Refresh Token Authentication. 53 | /// 54 | /// Can be used in the code with . 55 | /// 56 | /// The DI services. 57 | /// The section name for the root options configuration. The default value is Salesforce. 58 | /// 59 | /// The option configuration that can be override the configuration provides. 60 | /// 61 | public static IServiceCollection AddResilientStreamingClient( 62 | this IServiceCollection services, 63 | string sectionName = "Salesforce", 64 | string optionName = "", 65 | Action? configureOptions = default) 66 | { 67 | services.AddChangeTokenOptions( 68 | sectionName, 69 | optionName: optionName, 70 | configureAction: x => configureOptions?.Invoke(x)); 71 | 72 | services.AddResilentForceClient(optionName); 73 | 74 | services.TryAddSingleton(); 75 | 76 | return services; 77 | } 78 | 79 | private static IServiceCollection AddResilentForceClient(this IServiceCollection services, string optionName) 80 | { 81 | services.TryAddSingleton(); 82 | 83 | services.TryAddSingleton>>(sp => () => 84 | { 85 | var options = sp.GetRequiredService>().Get(optionName); 86 | 87 | if (!TimeSpan.TryParse(options.TokenExpiration, out var tokenExpiration)) 88 | { 89 | tokenExpiration = TimeSpan.FromHours(1); 90 | } 91 | 92 | return new AsyncExpiringLazy(async data => 93 | { 94 | if (data.Result == null 95 | || DateTime.UtcNow > data.ValidUntil.Subtract(TimeSpan.FromSeconds(30))) 96 | { 97 | var policy = Policy 98 | .Handle() 99 | .WaitAndRetryAsync( 100 | retryCount: options.Retry, 101 | sleepDurationProvider: (retryAttempt) => TimeSpan.FromSeconds(Math.Pow(options.BackoffPower, retryAttempt))); 102 | 103 | var authClient = await policy.ExecuteAsync(async () => 104 | { 105 | var auth = new AuthenticationClient(); 106 | 107 | if (string.IsNullOrWhiteSpace(options.RefreshToken) && 108 | !string.IsNullOrWhiteSpace(options.Username)) 109 | { 110 | await auth.UsernamePasswordAsync( 111 | options.ClientId, 112 | options.ClientSecret, 113 | options.Username, 114 | options.Password + options.UserPasswordToken, 115 | $"{options.LoginUrl}{options.OAuthUri}"); 116 | } 117 | else 118 | { 119 | await auth.TokenRefreshAsync( 120 | options.RefreshToken, 121 | options.ClientId, 122 | options.ClientSecret, 123 | $"{options.LoginUrl}{options.OAuthUri}"); 124 | } 125 | 126 | return auth; 127 | }); 128 | 129 | return new AsyncExpirationValue 130 | { 131 | Result = authClient.AccessInfo, 132 | ValidUntil = DateTimeOffset.UtcNow.Add(tokenExpiration) 133 | }; 134 | } 135 | 136 | return data; 137 | }); 138 | }); 139 | 140 | services.AddSingleton>>(sp => () => 141 | { 142 | var options = sp.GetRequiredService>().Get(optionName); 143 | 144 | if (!TimeSpan.TryParse(options.TokenExpiration, out var tokenExpiration)) 145 | { 146 | tokenExpiration = TimeSpan.FromHours(1); 147 | } 148 | 149 | return new AsyncExpiringLazy(async data => 150 | { 151 | if (data.Result == null 152 | || DateTime.UtcNow > data.ValidUntil.Subtract(TimeSpan.FromSeconds(30))) 153 | { 154 | var policy = Policy 155 | .Handle() 156 | .WaitAndRetryAsync( 157 | retryCount: options.Retry, 158 | sleepDurationProvider: (retryAttempt) => TimeSpan.FromSeconds(Math.Pow(options.BackoffPower, retryAttempt))); 159 | 160 | var client = await policy.ExecuteAsync(async () => 161 | { 162 | var authClient = new AuthenticationClient(); 163 | 164 | if (string.IsNullOrWhiteSpace(options.RefreshToken) && 165 | !string.IsNullOrWhiteSpace(options.Username)) 166 | { 167 | await authClient.UsernamePasswordAsync( 168 | options.ClientId, 169 | options.ClientSecret, 170 | options.Username, 171 | options.Password + options.UserPasswordToken, 172 | $"{options.LoginUrl}{options.OAuthUri}"); 173 | } 174 | else 175 | { 176 | await authClient.TokenRefreshAsync( 177 | options.RefreshToken, 178 | options.ClientId, 179 | options.ClientSecret, 180 | $"{options.LoginUrl}{options.OAuthUri}"); 181 | } 182 | 183 | return new ForceClient( 184 | authClient.AccessInfo.InstanceUrl, 185 | authClient.ApiVersion, 186 | authClient.AccessInfo.AccessToken); 187 | }); 188 | 189 | return new AsyncExpirationValue 190 | { 191 | Result = client, 192 | ValidUntil = DateTimeOffset.UtcNow.Add(tokenExpiration) 193 | }; 194 | } 195 | 196 | return data; 197 | }); 198 | }); 199 | 200 | return services; 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/CometD.NetCore.Salesforce/ResilientStreamingClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Specialized; 4 | using System.Net; 5 | using System.Threading; 6 | 7 | using CometD.NetCore.Bayeux.Client; 8 | using CometD.NetCore.Client; 9 | using CometD.NetCore.Client.Extension; 10 | using CometD.NetCore.Client.Transport; 11 | 12 | using Microsoft.Extensions.Logging; 13 | using Microsoft.Extensions.Options; 14 | 15 | using NetCoreForce.Client.Models; 16 | 17 | namespace CometD.NetCore.Salesforce 18 | { 19 | public class ResilientStreamingClient : IStreamingClient 20 | { 21 | private readonly ILogger _logger; 22 | private readonly SalesforceConfiguration _options; 23 | private readonly AsyncExpiringLazy _tokenResponse; 24 | 25 | // long polling duration 26 | private readonly int _readTimeOut = 120 * 1000; 27 | 28 | private BayeuxClient? _bayeuxClient = null; 29 | private bool _isDisposed = false; 30 | private ErrorExtension? _errorExtension; 31 | private LongPollingTransport? _clientTransport; 32 | private ReplayExtension? _replayIdExtension; 33 | 34 | public Action InvalidReplayIdStrategy { get; set; } 35 | 36 | /// 37 | /// Initializes a new instance of the class. 38 | /// 39 | /// 40 | /// 41 | /// 42 | public ResilientStreamingClient( 43 | Func> tokenResponse, 44 | IOptions options, 45 | ILogger logger) 46 | { 47 | _logger = logger; 48 | _options = options.Value; 49 | _tokenResponse = tokenResponse(); 50 | 51 | CreateBayeuxClient(); 52 | } 53 | 54 | /// 55 | /// Finalizes an instance of the class. 56 | /// 57 | ~ResilientStreamingClient() 58 | { 59 | Dispose(false); 60 | } 61 | 62 | public event EventHandler? Reconnect; 63 | 64 | public bool IsConnected 65 | { 66 | get 67 | { 68 | if (_bayeuxClient == null) 69 | { 70 | return false; 71 | } 72 | 73 | return _bayeuxClient.Connected; 74 | } 75 | } 76 | 77 | /// 78 | public void Disconnect() 79 | { 80 | Disconnect(1000); 81 | } 82 | 83 | /// 84 | public void Disconnect(int timeout) 85 | { 86 | if (_isDisposed) 87 | { 88 | throw new ObjectDisposedException("Cannot disconnect when disposed"); 89 | } 90 | 91 | _bayeuxClient?.ResetSubscriptions(); 92 | 93 | _logger.LogDebug("Disconnecting..."); 94 | _bayeuxClient?.Disconnect(); 95 | _bayeuxClient?.WaitFor(timeout, new[] { BayeuxClient.State.DISCONNECTED }); 96 | 97 | if (_errorExtension != null) 98 | { 99 | _errorExtension.ConnectionError -= ErrorExtension_ConnectionError; 100 | _errorExtension.ConnectionException -= ErrorExtension_ConnectionException; 101 | _errorExtension.ConnectionMessage -= ErrorExtension_ConnectionMessage; 102 | } 103 | 104 | _logger.LogDebug("Disconnected..."); 105 | } 106 | 107 | /// 108 | /// The Handshake uses the default value of 1000 ms. 109 | /// 110 | public void Handshake() 111 | { 112 | Handshake(1000); 113 | } 114 | 115 | /// 116 | public void Handshake(int timeout) 117 | { 118 | if (_bayeuxClient == null) 119 | { 120 | return; 121 | } 122 | 123 | if (_isDisposed) 124 | { 125 | throw new ObjectDisposedException("Cannot connect when disposed"); 126 | } 127 | 128 | _logger.LogDebug("Handshaking..."); 129 | _bayeuxClient.Handshake(); 130 | _bayeuxClient.WaitFor(timeout, new[] { BayeuxClient.State.CONNECTED }); 131 | _logger.LogDebug("Connected"); 132 | } 133 | 134 | /// 135 | public void SubscribeTopic( 136 | string topicName, 137 | IMessageListener listener, 138 | long replayId = -1) 139 | { 140 | if (topicName == null || (topicName = topicName.Trim()).Length == 0) 141 | { 142 | throw new ArgumentNullException(nameof(topicName)); 143 | } 144 | 145 | if (listener == null) 146 | { 147 | throw new ArgumentNullException(nameof(listener)); 148 | } 149 | 150 | var channel = _bayeuxClient?.GetChannel(topicName, replayId); 151 | channel?.Subscribe(listener); 152 | } 153 | 154 | /// 155 | public bool UnsubscribeTopic( 156 | string topicName, 157 | IMessageListener? listener = null, 158 | long replayId = -1) 159 | { 160 | if (topicName == null || (topicName = topicName.Trim()).Length == 0) 161 | { 162 | throw new ArgumentNullException(nameof(topicName)); 163 | } 164 | 165 | var channel = _bayeuxClient?.GetChannel(topicName, replayId); 166 | if (channel != null) 167 | { 168 | if (listener != null) 169 | { 170 | channel.Unsubscribe(listener); 171 | } 172 | else 173 | { 174 | channel.Unsubscribe(); 175 | } 176 | 177 | return true; 178 | } 179 | 180 | return false; 181 | } 182 | 183 | /// 184 | /// Disposing of the resources. 185 | /// 186 | public void Dispose() 187 | { 188 | Dispose(true); 189 | GC.SuppressFinalize(this); 190 | } 191 | 192 | /// 193 | /// Disposing of the resources. 194 | /// 195 | /// 196 | protected virtual void Dispose(bool disposing) 197 | { 198 | if (disposing && !_isDisposed) 199 | { 200 | Disconnect(); 201 | _isDisposed = true; 202 | } 203 | } 204 | 205 | protected virtual void ErrorExtension_ConnectionError( 206 | object sender, 207 | string e) 208 | { 209 | // authentication failure 210 | if (string.Equals(e, "403::Handshake denied", StringComparison.OrdinalIgnoreCase) 211 | || string.Equals(e, "403:denied_by_security_policy:create_denied", StringComparison.OrdinalIgnoreCase) 212 | || string.Equals(e, "403::unknown client", StringComparison.OrdinalIgnoreCase) 213 | || string.Equals(e, "401::Authentication invalid", StringComparison.OrdinalIgnoreCase)) 214 | { 215 | _logger.LogWarning("Handled CometD Exception: {message}", e); 216 | 217 | // 1. Disconnect existing client. 218 | Disconnect(); 219 | 220 | // 2. Invalidate the access token. 221 | _tokenResponse.Invalidate(); 222 | 223 | _logger.LogDebug("Invalidate token for {name} ...", nameof(BayeuxClient)); 224 | 225 | // 3. Recreate BayeuxClient and populate it with a new transport with new security headers. 226 | CreateBayeuxClient(); 227 | 228 | // 4. Invoke the Reconnect Event 229 | Reconnect?.Invoke(this, true); 230 | } 231 | else if (e.Contains("you provided was invalid")) 232 | { 233 | var start = e.IndexOf('{'); 234 | var end = e.IndexOf('}'); 235 | var replayIdString = e.Substring(start + 1, end - (start + 1)); 236 | 237 | if (int.TryParse(replayIdString, out var replayId)) 238 | { 239 | InvalidReplayIdStrategy(replayId); 240 | } 241 | } 242 | else 243 | { 244 | _logger.LogError("{name} failed with the following message: {message}", nameof(ResilientStreamingClient), e); 245 | } 246 | } 247 | 248 | protected virtual void ErrorExtension_ConnectionException( 249 | object sender, 250 | Exception ex) 251 | { 252 | // ongoing time out issue not to be considered as error in the log. 253 | if (ex?.Message == "The operation has timed out.") 254 | { 255 | _logger.LogDebug(ex.Message); 256 | } 257 | else if (ex != null) 258 | { 259 | _logger.LogError(ex.ToString()); 260 | } 261 | } 262 | 263 | protected virtual void ErrorExtension_ConnectionMessage( 264 | object sender, 265 | string message) 266 | { 267 | _logger.LogDebug(message); 268 | } 269 | 270 | private void CreateBayeuxClient() 271 | { 272 | if (_isDisposed) 273 | { 274 | throw new ObjectDisposedException("Cannot create connection when disposed"); 275 | } 276 | 277 | _logger.LogDebug("Creating {name} ...", nameof(BayeuxClient)); 278 | 279 | var accessToken = _tokenResponse.Value().Result; 280 | 281 | // only need the scheme and host, strip out the rest 282 | var serverUri = new Uri(accessToken.InstanceUrl); 283 | var endpoint = $"{serverUri.Scheme}://{serverUri.Host}{_options.CometDUri}"; 284 | 285 | var headers = new NameValueCollection { { nameof(HttpRequestHeader.Authorization), $"{_options.TokenType} {accessToken.AccessToken}" } }; 286 | 287 | // Salesforce socket timeout during connection(CometD session) = 110 seconds 288 | var options = new Dictionary(StringComparer.OrdinalIgnoreCase) 289 | { 290 | { ClientTransport.TIMEOUT_OPTION, _options.ReadTimeOut ?? _readTimeOut }, 291 | { ClientTransport.MAX_NETWORK_DELAY_OPTION, _options.ReadTimeOut ?? _readTimeOut } 292 | }; 293 | 294 | _clientTransport = new LongPollingTransport(options, headers); 295 | 296 | _bayeuxClient = new BayeuxClient(endpoint, _clientTransport); 297 | 298 | // adds logging and also raises an event to process reconnection to the server. 299 | _errorExtension = new ErrorExtension(); 300 | _errorExtension.ConnectionError += ErrorExtension_ConnectionError; 301 | _errorExtension.ConnectionException += ErrorExtension_ConnectionException; 302 | _errorExtension.ConnectionMessage += ErrorExtension_ConnectionMessage; 303 | _bayeuxClient.AddExtension(_errorExtension); 304 | 305 | _replayIdExtension = new ReplayExtension(); 306 | _bayeuxClient.AddExtension(_replayIdExtension); 307 | 308 | _logger.LogDebug("{name} was created...", nameof(BayeuxClient)); 309 | } 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/CometD.NetCore.Salesforce/Resilience/IResilientForceClient.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | using NetCoreForce.Client; 6 | using NetCoreForce.Client.Models; 7 | 8 | namespace CometD.NetCore.Salesforce.Resilience 9 | { 10 | /// 11 | /// Resilient Library for . 12 | /// 13 | public interface IResilientForceClient 14 | { 15 | /// 16 | /// Get a basic SOQL COUNT() query result 17 | /// The query must start with SELECT COUNT() FROM, with no named field in the count 18 | /// clause. COUNT() must be the only element in the SELECT list. 19 | /// 20 | /// SOQL query string starting with SELECT COUNT() FROM. 21 | /// True if deleted records are to be included. The default is false. 22 | /// 23 | /// The System.Threading.Tasks.Task`1 returning the count. 24 | Task CountQueryAsync( 25 | string queryString, 26 | bool queryAll = false, 27 | CancellationToken cancellationToken = default); 28 | 29 | /// 30 | /// Creates a new record. 31 | /// 32 | /// The type for the SObject name, e.g. "Account". 33 | /// 34 | /// The SObject name, e.g. "Account". 35 | /// Custom headers to include in request (Optional). await The HeaderFormatter helper 36 | /// class can be used to generate the custom header as needed. 37 | /// 38 | /// The Cancellation Token. 39 | /// CreateResponse object, includes new object's ID. 40 | Task CreateRecordAsync( 41 | string sObjectTypeName, 42 | T sObject, 43 | Dictionary? customHeaders = null, 44 | CancellationToken cancellationToken = default); 45 | 46 | /// 47 | /// Delete record. 48 | /// 49 | /// SObject name, e.g. "Account". 50 | /// Id of Object to update. 51 | /// 52 | /// void, API returns 204/NoContent. 53 | Task DeleteRecordAsync( 54 | string sObjectTypeName, 55 | string objectId, 56 | CancellationToken cancellationToken = default); 57 | 58 | /// 59 | /// Get a List of Objects 60 | /// Use the Describe Global resource to list the objects available in your org and 61 | /// available to the logged-in user. This resource also returns the org encoding, 62 | /// as well as maximum batch size permitted in queries. ///. 63 | /// 64 | /// Returns DescribeGlobal object with a SObjectDescribe collection. 65 | Task DescribeGlobalAsync(CancellationToken cancellationToken = default); 66 | 67 | /// 68 | /// List summary information about each REST API version currently available, including 69 | /// the version, label, and a link to each version's root. You do not need authentication 70 | /// to retrieve the list of versions. 71 | /// 72 | /// 73 | /// Current instance URL. If the client has been initialized, the parameter is optional 74 | /// and the client's current instance URL will be used. 75 | /// 76 | /// 77 | /// 78 | Task> GetAvailableRestApiVersionsAsync( 79 | string? currentInstanceUrl = null, 80 | CancellationToken cancellationToken = default); 81 | 82 | /// 83 | /// Retrieve(basic) metadata for an object. 84 | /// Use the SObject Basic Information resource to retrieve metadata for an object. 85 | /// 86 | /// SObject name, e.g. Account. 87 | /// 88 | /// 89 | Task GetObjectBasicInfoAsync( 90 | string objectTypeName, 91 | CancellationToken cancellationToken = default); 92 | 93 | /// 94 | /// Get SObject by ID. 95 | /// 96 | /// 97 | /// SObject name, e.g. "Account". 98 | /// SObject ID. 99 | /// (optional) List of fields to retrieve, if not supplied, all fields are retrieved. 100 | /// 101 | /// 102 | Task GetObjectByIdAsync( 103 | string sObjectTypeName, 104 | string objectId, 105 | List? fields = null, 106 | CancellationToken cancellationToken = default); 107 | 108 | /// 109 | /// Get field and other metadata for an object. 110 | /// Use the SObject Describe resource to retrieve all the metadata for an object, 111 | /// including information about each field, URLs, and child relationships. 112 | /// 113 | /// 114 | /// 115 | /// Returns SObjectMetadataAll with full object meta including field metadata. 116 | Task GetObjectDescribeAsync( 117 | string objectTypeName, 118 | CancellationToken cancellationToken = default); 119 | 120 | /// 121 | /// Lists information about limits in your org. 122 | /// This resource is available in REST API version 29.0 and later for API users with 123 | /// the View Setup and Configuration permission. 124 | /// 125 | /// 126 | /// 127 | Task GetOrganizationLimitsAsync(CancellationToken cancellationToken = default); 128 | 129 | /// 130 | /// Get current user's info via Identity URL 131 | /// https://developer.salesforce.com/docs/atlas.en-us.mobile_sdk.meta/mobile_sdk/oauth_using_identity_urls.htm. 132 | /// 133 | /// 134 | /// 135 | /// UserInfo. 136 | Task GetUserInfoAsync( 137 | string identityUrl, 138 | CancellationToken cancellationToken = default); 139 | 140 | /// 141 | /// Inserts or Updates a records based on external id. 142 | /// 143 | /// 144 | /// SObject name, e.g. "Account". 145 | /// External ID field name. 146 | /// External ID field value. 147 | /// Object to update. 148 | /// Custom headers to include in request (Optional). await The HeaderFormatter helper class 149 | /// can be used to generate the custom header as needed. 150 | /// . 151 | /// 152 | Task InsertOrUpdateRecordAsync( 153 | string sObjectTypeName, 154 | string fieldName, 155 | string fieldValue, 156 | T sObject, 157 | Dictionary? customHeaders = null, 158 | CancellationToken cancellationToken = default); 159 | 160 | /// 161 | /// Retrieve records using a SOQL query. 162 | /// Will automatically retrieve the complete result set if split into batches. If 163 | /// you want to limit results, use the LIMIT operator in your query. 164 | /// 165 | /// 166 | /// SOQL query string, without any URL escaping/encoding. 167 | /// True if deleted records are to be included. 168 | /// 169 | /// 170 | Task> QueryAsync( 171 | string queryString, 172 | bool queryAll = false, 173 | CancellationToken cancellationToken = default); 174 | 175 | /// 176 | /// Retrieve a single record using a SOQL query. 177 | /// Will throw an exception if multiple rows are retrieved by the query - if you 178 | /// are note sure of a single result, use Query{T} instead. 179 | /// 180 | /// 181 | /// SOQL query string, without any URL escaping/encoding. 182 | /// True if deleted records are to be included. 183 | /// 184 | /// result object. 185 | Task QuerySingleAsync( 186 | string queryString, 187 | bool queryAll = false, 188 | CancellationToken cancellationToken = default); 189 | 190 | /// 191 | /// Executes a SOSL search, returning a type T, e.g. when using "RETURNING Account" 192 | /// in the SOSL query. 193 | /// Not properly matching the return type T and the RETURNING clause of the SOSL 194 | /// query may return unexpected results. 195 | /// 196 | /// 197 | /// 198 | /// 199 | /// SearchResult{T}. 200 | Task> SearchAsync( 201 | string searchString, 202 | CancellationToken cancellationToken = default); 203 | 204 | /// 205 | /// Does a basic test of the client's connection to the current Salesforce instance, 206 | /// and that the API is responding to requests. 207 | /// This does not validate authentication. 208 | /// Makes a call to the Versions resource, since it requires no authentication or 209 | /// permissions. 210 | /// 211 | /// 212 | /// 213 | /// True or false. Does not throw exceptions, only false in case of any errors. 214 | Task TestConnectionAsync( 215 | string? currentInstanceUrl = null, 216 | CancellationToken cancellationToken = default); 217 | 218 | /// 219 | /// Updates. 220 | /// 221 | /// 222 | /// SObject name, e.g. "Account". 223 | /// Id of Object to update. 224 | /// Object to update. 225 | /// 226 | /// Custom headers to include in request (Optional). await The HeaderFormatter helper 227 | /// class can be used to generate the custom header as needed. 228 | /// 229 | /// 230 | /// 231 | Task UpdateRecordAsync( 232 | string sObjectTypeName, 233 | string objectId, 234 | T sObject, 235 | Dictionary? customHeaders = null, 236 | CancellationToken cancellationToken = default); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/AuthApp/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | # top-most EditorConfig file 3 | root = true 4 | 5 | # Unix-style newlines with a newline ending every file 6 | [*] 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | # Matches multiple files with brace expansion notation 12 | # Set default charset 13 | [*.js] 14 | charset = utf-8 15 | 16 | # Solution Files 17 | [*.sln] 18 | indent_style = tab 19 | 20 | # XML Project Files 21 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 22 | indent_size = 2 23 | 24 | # YAML Files 25 | [*.{yml,yaml}] 26 | indent_size = 2 27 | indent_style = space 28 | 29 | # Markdown Files 30 | [*.md] 31 | trim_trailing_whitespace = false 32 | 33 | # .NET Code Style Settings 34 | [*.{cs,csx,cake,vb}] 35 | 36 | [*.cshtml.cs] 37 | dotnet_diagnostic.SA1649.severity = none 38 | 39 | # "this." and "Me." qualifiers 40 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#this_and_me 41 | dotnet_style_qualification_for_field = false:warning 42 | dotnet_style_qualification_for_property = false:warning 43 | dotnet_style_qualification_for_method = false:warning 44 | dotnet_style_qualification_for_event = false:warning 45 | 46 | # Language keywords instead of framework type names for type references 47 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#language_keywords 48 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning 49 | dotnet_style_predefined_type_for_member_access = true:warning 50 | 51 | # Modifier preferences 52 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#normalize_modifiers 53 | dotnet_style_require_accessibility_modifiers = always:warning 54 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning 55 | dotnet_style_readonly_field = true:warning 56 | 57 | # Code-block preferences 58 | csharp_prefer_braces = true:silent 59 | csharp_prefer_simple_using_statement = true:suggestion 60 | 61 | # Expression-level preferences 62 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_level 63 | dotnet_style_object_initializer = true:warning 64 | dotnet_style_collection_initializer = true:warning 65 | dotnet_style_explicit_tuple_names = true:warning 66 | dotnet_style_prefer_inferred_tuple_names = true:warning 67 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning 68 | dotnet_style_prefer_auto_properties = true:warning 69 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning 70 | 71 | # 'using' directive preferences 72 | csharp_using_directive_placement = outside_namespace:silent 73 | 74 | # Null-checking preferences 75 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#null_checking 76 | dotnet_style_coalesce_expression = true:warning 77 | dotnet_style_null_propagation = true:warning 78 | 79 | # Other (Undocumented) 80 | dotnet_style_prefer_conditional_expression_over_return = false 81 | dotnet_style_prefer_conditional_expression_over_assignment = false 82 | 83 | # C# Code Style Settings 84 | [*.{cs,csx,cake}] 85 | # Implicit and explicit types 86 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#implicit-and-explicit-types 87 | csharp_style_var_for_built_in_types = true:warning 88 | csharp_style_var_when_type_is_apparent = true:warning 89 | csharp_style_var_elsewhere = true:warning 90 | 91 | # Expression-bodied members 92 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_bodied_members 93 | csharp_style_expression_bodied_methods = false:warning 94 | csharp_style_expression_bodied_constructors = false:warning 95 | csharp_style_expression_bodied_operators = true:warning 96 | csharp_style_expression_bodied_properties = true:warning 97 | csharp_style_expression_bodied_indexers = true:warning 98 | csharp_style_expression_bodied_accessors = true:warning 99 | 100 | # Pattern matching 101 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#pattern_matching 102 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning 103 | csharp_style_pattern_matching_over_as_with_null_check = true:warning 104 | 105 | # Inlined variable declarations 106 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#inlined_variable_declarations 107 | csharp_style_inlined_variable_declaration = true:warning 108 | 109 | # Expression-level preferences 110 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_level_csharp 111 | csharp_prefer_simple_default_expression = true:warning 112 | csharp_style_deconstructed_variable_declaration = true:warning 113 | csharp_style_pattern_local_over_anonymous_function = true:warning 114 | 115 | # "Null" checking preferences 116 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#null_checking_csharp 117 | csharp_style_throw_expression = true:warning 118 | csharp_style_conditional_delegate_call = true:warning 119 | 120 | # Code block preferences 121 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#code_block 122 | csharp_prefer_braces = true:warning 123 | 124 | ############################# 125 | # .NET Formatting Conventions 126 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions 127 | ############################# 128 | 129 | # Organize usings 130 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#usings 131 | dotnet_sort_system_directives_first = true 132 | dotnet_separate_import_directive_groups = true 133 | 134 | # C# formatting settings 135 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#c-formatting-settings 136 | csharp_new_line_before_open_brace = all 137 | csharp_new_line_before_else = true:warning 138 | csharp_new_line_before_catch = true:warning 139 | csharp_new_line_before_finally = true:warning 140 | csharp_new_line_before_members_in_object_initializers = true:warning 141 | csharp_new_line_before_members_in_anonymous_types = true:warning 142 | csharp_new_line_between_query_expression_clauses = true:warning 143 | 144 | # Indentation options 145 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#indent 146 | csharp_indent_case_contents = true:warning 147 | csharp_indent_switch_labels = true:warning 148 | csharp_indent_labels = no_change:warning 149 | 150 | # Spacing options 151 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#spacing 152 | csharp_space_after_cast = false:warning 153 | csharp_space_after_keywords_in_control_flow_statements = true:warning 154 | csharp_space_between_method_declaration_parameter_list_parentheses = false:warning 155 | csharp_space_between_method_call_parameter_list_parentheses = false:warning 156 | csharp_space_between_parentheses = expressions:warning 157 | csharp_space_before_colon_in_inheritance_clause = true:warning 158 | csharp_space_after_colon_in_inheritance_clause = true:warning 159 | csharp_space_around_binary_operators = before_and_after:warning 160 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false:warning 161 | csharp_space_between_method_call_name_and_opening_parenthesis = false:warning 162 | csharp_space_between_method_call_empty_parameter_list_parentheses = false:warning 163 | 164 | # Wrapping options 165 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#wrapping 166 | csharp_preserve_single_line_statements = false:warning 167 | csharp_preserve_single_line_blocks = true:warning 168 | 169 | # More Indentation options (Undocumented) 170 | csharp_indent_block_contents = true:warning 171 | csharp_indent_braces = false:warning 172 | 173 | # Spacing Options (Undocumented) 174 | csharp_space_after_comma = true:warning 175 | csharp_space_after_dot = false:warning 176 | csharp_space_after_semicolon_in_for_statement = true:warning 177 | csharp_space_around_declaration_statements = do_not_ignore:warning 178 | csharp_space_before_comma = false:warning 179 | csharp_space_before_dot = false:warning 180 | csharp_space_before_semicolon_in_for_statement = false:warning 181 | csharp_space_before_open_square_brackets = false:warning 182 | csharp_space_between_empty_square_brackets = false:warning 183 | csharp_space_between_method_declaration_name_and_open_parenthesis = false:warning 184 | csharp_space_between_square_brackets = false:warning 185 | 186 | ######################### 187 | # .NET Naming conventions 188 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions 189 | ######################### 190 | 191 | [*.{cs,csx,cake,vb}] 192 | # Naming Symbols 193 | # constant_fields - Define constant fields 194 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 195 | dotnet_naming_symbols.constant_fields.required_modifiers = const 196 | 197 | # non_private_readonly_fields - Define public, internal and protected readonly fields 198 | dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, internal, protected 199 | dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field 200 | dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly 201 | 202 | # static_readonly_fields - Define static and readonly fields 203 | dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field 204 | dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly 205 | 206 | # private_readonly_fields - Define private readonly fields 207 | dotnet_naming_symbols.private_readonly_fields.applicable_accessibilities = private 208 | dotnet_naming_symbols.private_readonly_fields.applicable_kinds = field 209 | dotnet_naming_symbols.private_readonly_fields.required_modifiers = readonly 210 | 211 | # public_internal_fields - Define public and internal fields 212 | dotnet_naming_symbols.public_internal_fields.applicable_accessibilities = public, internal 213 | dotnet_naming_symbols.public_internal_fields.applicable_kinds = field 214 | 215 | # private_protected_fields - Define private and protected fields 216 | dotnet_naming_symbols.private_protected_fields.applicable_accessibilities = private, protected 217 | dotnet_naming_symbols.private_protected_fields.applicable_kinds = field 218 | 219 | # public_symbols - Define any public symbol 220 | dotnet_naming_symbols.public_symbols.applicable_accessibilities = public, internal, protected, protected_internal 221 | dotnet_naming_symbols.public_symbols.applicable_kinds = method, property, event, delegate 222 | 223 | # parameters - Defines any parameter 224 | dotnet_naming_symbols.parameters.applicable_kinds = parameter 225 | 226 | # non_interface_types - Defines class, struct, enum and delegate types 227 | dotnet_naming_symbols.non_interface_types.applicable_kinds = class, struct, enum, delegate 228 | 229 | # interface_types - Defines interfaces 230 | dotnet_naming_symbols.interface_types.applicable_kinds = interface 231 | 232 | ######################### 233 | # Naming Styles 234 | ######################### 235 | 236 | # camel_case - Define the camelCase style 237 | dotnet_naming_style.camel_case.capitalization = camel_case 238 | 239 | # pascal_case - Define the Pascal_case style 240 | dotnet_naming_style.pascal_case.capitalization = pascal_case 241 | 242 | # first_upper - The first character must start with an upper-case character 243 | dotnet_naming_style.first_upper.capitalization = first_word_upper 244 | 245 | # prefix_interface_interface_with_i - Interfaces must be PascalCase and the first character of an interface must be an 'I' 246 | dotnet_naming_style.prefix_interface_interface_with_i.capitalization = pascal_case 247 | dotnet_naming_style.prefix_interface_interface_with_i.required_prefix = I 248 | 249 | ######################### 250 | # Naming Rules 251 | ######################### 252 | 253 | # Constant fields must be PascalCase 254 | dotnet_naming_rule.constant_fields_must_be_pascal_case.severity = warning 255 | dotnet_naming_rule.constant_fields_must_be_pascal_case.symbols = constant_fields 256 | dotnet_naming_rule.constant_fields_must_be_pascal_case.style = pascal_case 257 | 258 | # Public, internal and protected readonly fields must be PascalCase 259 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.severity = warning 260 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.symbols = non_private_readonly_fields 261 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.style = pascal_case 262 | 263 | # Static readonly fields must be PascalCase 264 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.severity = warning 265 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.symbols = static_readonly_fields 266 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.style = pascal_case 267 | 268 | # Public and internal fields must be PascalCase 269 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.severity = warning 270 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.symbols = public_internal_fields 271 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.style = pascal_case 272 | 273 | # Public members must be capitalized 274 | dotnet_naming_rule.public_members_must_be_capitalized.severity = warning 275 | dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols 276 | dotnet_naming_rule.public_members_must_be_capitalized.style = first_upper 277 | 278 | # Parameters must be camelCase 279 | dotnet_naming_rule.parameters_must_be_camel_case.severity = warning 280 | dotnet_naming_rule.parameters_must_be_camel_case.symbols = parameters 281 | dotnet_naming_rule.parameters_must_be_camel_case.style = camel_case 282 | 283 | # Class, struct, enum and delegates must be PascalCase 284 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.severity = warning 285 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.symbols = non_interface_types 286 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.style = pascal_case 287 | 288 | # Interfaces must be PascalCase and start with an 'I' 289 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.severity = warning 290 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.symbols = interface_types 291 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.style = prefix_interface_interface_with_i 292 | 293 | dotnet_naming_symbols.private_fields.applicable_kinds = field 294 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private, internal 295 | 296 | # internal and private fields should be _camelCase 297 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion 298 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields 299 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style 300 | 301 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field 302 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal, protected 303 | 304 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _ 305 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case 306 | 307 | 308 | [*.cs] 309 | 310 | # CA1815: Override equals and operator equals on value types 311 | dotnet_diagnostic.CA1815.severity = silent 312 | -------------------------------------------------------------------------------- /src/TestApp/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | # top-most EditorConfig file 3 | root = true 4 | 5 | # Unix-style newlines with a newline ending every file 6 | [*] 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | # Matches multiple files with brace expansion notation 12 | # Set default charset 13 | [*.js] 14 | charset = utf-8 15 | 16 | # Solution Files 17 | [*.sln] 18 | indent_style = tab 19 | 20 | # XML Project Files 21 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 22 | indent_size = 2 23 | 24 | # YAML Files 25 | [*.{yml,yaml}] 26 | indent_size = 2 27 | indent_style = space 28 | 29 | # Markdown Files 30 | [*.md] 31 | trim_trailing_whitespace = false 32 | 33 | # .NET Code Style Settings 34 | [*.{cs,csx,cake,vb}] 35 | 36 | [*.cshtml.cs] 37 | dotnet_diagnostic.SA1649.severity = none 38 | 39 | # "this." and "Me." qualifiers 40 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#this_and_me 41 | dotnet_style_qualification_for_field = false:warning 42 | dotnet_style_qualification_for_property = false:warning 43 | dotnet_style_qualification_for_method = false:warning 44 | dotnet_style_qualification_for_event = false:warning 45 | 46 | # Language keywords instead of framework type names for type references 47 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#language_keywords 48 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning 49 | dotnet_style_predefined_type_for_member_access = true:warning 50 | 51 | # Modifier preferences 52 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#normalize_modifiers 53 | dotnet_style_require_accessibility_modifiers = always:warning 54 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning 55 | dotnet_style_readonly_field = true:warning 56 | 57 | # Code-block preferences 58 | csharp_prefer_braces = true:silent 59 | csharp_prefer_simple_using_statement = true:suggestion 60 | 61 | # Expression-level preferences 62 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_level 63 | dotnet_style_object_initializer = true:warning 64 | dotnet_style_collection_initializer = true:warning 65 | dotnet_style_explicit_tuple_names = true:warning 66 | dotnet_style_prefer_inferred_tuple_names = true:warning 67 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning 68 | dotnet_style_prefer_auto_properties = true:warning 69 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning 70 | 71 | # 'using' directive preferences 72 | csharp_using_directive_placement = outside_namespace:silent 73 | 74 | # Null-checking preferences 75 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#null_checking 76 | dotnet_style_coalesce_expression = true:warning 77 | dotnet_style_null_propagation = true:warning 78 | 79 | # Other (Undocumented) 80 | dotnet_style_prefer_conditional_expression_over_return = false 81 | dotnet_style_prefer_conditional_expression_over_assignment = false 82 | 83 | # C# Code Style Settings 84 | [*.{cs,csx,cake}] 85 | # Implicit and explicit types 86 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#implicit-and-explicit-types 87 | csharp_style_var_for_built_in_types = true:warning 88 | csharp_style_var_when_type_is_apparent = true:warning 89 | csharp_style_var_elsewhere = true:warning 90 | 91 | # Expression-bodied members 92 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_bodied_members 93 | csharp_style_expression_bodied_methods = false:warning 94 | csharp_style_expression_bodied_constructors = false:warning 95 | csharp_style_expression_bodied_operators = true:warning 96 | csharp_style_expression_bodied_properties = true:warning 97 | csharp_style_expression_bodied_indexers = true:warning 98 | csharp_style_expression_bodied_accessors = true:warning 99 | 100 | # Pattern matching 101 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#pattern_matching 102 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning 103 | csharp_style_pattern_matching_over_as_with_null_check = true:warning 104 | 105 | # Inlined variable declarations 106 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#inlined_variable_declarations 107 | csharp_style_inlined_variable_declaration = true:warning 108 | 109 | # Expression-level preferences 110 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_level_csharp 111 | csharp_prefer_simple_default_expression = true:warning 112 | csharp_style_deconstructed_variable_declaration = true:warning 113 | csharp_style_pattern_local_over_anonymous_function = true:warning 114 | 115 | # "Null" checking preferences 116 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#null_checking_csharp 117 | csharp_style_throw_expression = true:warning 118 | csharp_style_conditional_delegate_call = true:warning 119 | 120 | # Code block preferences 121 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#code_block 122 | csharp_prefer_braces = true:warning 123 | 124 | ############################# 125 | # .NET Formatting Conventions 126 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions 127 | ############################# 128 | 129 | # Organize usings 130 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#usings 131 | dotnet_sort_system_directives_first = true 132 | dotnet_separate_import_directive_groups = true 133 | 134 | # C# formatting settings 135 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#c-formatting-settings 136 | csharp_new_line_before_open_brace = all 137 | csharp_new_line_before_else = true:warning 138 | csharp_new_line_before_catch = true:warning 139 | csharp_new_line_before_finally = true:warning 140 | csharp_new_line_before_members_in_object_initializers = true:warning 141 | csharp_new_line_before_members_in_anonymous_types = true:warning 142 | csharp_new_line_between_query_expression_clauses = true:warning 143 | 144 | # Indentation options 145 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#indent 146 | csharp_indent_case_contents = true:warning 147 | csharp_indent_switch_labels = true:warning 148 | csharp_indent_labels = no_change:warning 149 | 150 | # Spacing options 151 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#spacing 152 | csharp_space_after_cast = false:warning 153 | csharp_space_after_keywords_in_control_flow_statements = true:warning 154 | csharp_space_between_method_declaration_parameter_list_parentheses = false:warning 155 | csharp_space_between_method_call_parameter_list_parentheses = false:warning 156 | csharp_space_between_parentheses = expressions:warning 157 | csharp_space_before_colon_in_inheritance_clause = true:warning 158 | csharp_space_after_colon_in_inheritance_clause = true:warning 159 | csharp_space_around_binary_operators = before_and_after:warning 160 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false:warning 161 | csharp_space_between_method_call_name_and_opening_parenthesis = false:warning 162 | csharp_space_between_method_call_empty_parameter_list_parentheses = false:warning 163 | 164 | # Wrapping options 165 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#wrapping 166 | csharp_preserve_single_line_statements = false:warning 167 | csharp_preserve_single_line_blocks = true:warning 168 | 169 | # More Indentation options (Undocumented) 170 | csharp_indent_block_contents = true:warning 171 | csharp_indent_braces = false:warning 172 | 173 | # Spacing Options (Undocumented) 174 | csharp_space_after_comma = true:warning 175 | csharp_space_after_dot = false:warning 176 | csharp_space_after_semicolon_in_for_statement = true:warning 177 | csharp_space_around_declaration_statements = do_not_ignore:warning 178 | csharp_space_before_comma = false:warning 179 | csharp_space_before_dot = false:warning 180 | csharp_space_before_semicolon_in_for_statement = false:warning 181 | csharp_space_before_open_square_brackets = false:warning 182 | csharp_space_between_empty_square_brackets = false:warning 183 | csharp_space_between_method_declaration_name_and_open_parenthesis = false:warning 184 | csharp_space_between_square_brackets = false:warning 185 | 186 | ######################### 187 | # .NET Naming conventions 188 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions 189 | ######################### 190 | 191 | [*.{cs,csx,cake,vb}] 192 | # Naming Symbols 193 | # constant_fields - Define constant fields 194 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 195 | dotnet_naming_symbols.constant_fields.required_modifiers = const 196 | 197 | # non_private_readonly_fields - Define public, internal and protected readonly fields 198 | dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, internal, protected 199 | dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field 200 | dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly 201 | 202 | # static_readonly_fields - Define static and readonly fields 203 | dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field 204 | dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly 205 | 206 | # private_readonly_fields - Define private readonly fields 207 | dotnet_naming_symbols.private_readonly_fields.applicable_accessibilities = private 208 | dotnet_naming_symbols.private_readonly_fields.applicable_kinds = field 209 | dotnet_naming_symbols.private_readonly_fields.required_modifiers = readonly 210 | 211 | # public_internal_fields - Define public and internal fields 212 | dotnet_naming_symbols.public_internal_fields.applicable_accessibilities = public, internal 213 | dotnet_naming_symbols.public_internal_fields.applicable_kinds = field 214 | 215 | # private_protected_fields - Define private and protected fields 216 | dotnet_naming_symbols.private_protected_fields.applicable_accessibilities = private, protected 217 | dotnet_naming_symbols.private_protected_fields.applicable_kinds = field 218 | 219 | # public_symbols - Define any public symbol 220 | dotnet_naming_symbols.public_symbols.applicable_accessibilities = public, internal, protected, protected_internal 221 | dotnet_naming_symbols.public_symbols.applicable_kinds = method, property, event, delegate 222 | 223 | # parameters - Defines any parameter 224 | dotnet_naming_symbols.parameters.applicable_kinds = parameter 225 | 226 | # non_interface_types - Defines class, struct, enum and delegate types 227 | dotnet_naming_symbols.non_interface_types.applicable_kinds = class, struct, enum, delegate 228 | 229 | # interface_types - Defines interfaces 230 | dotnet_naming_symbols.interface_types.applicable_kinds = interface 231 | 232 | ######################### 233 | # Naming Styles 234 | ######################### 235 | 236 | # camel_case - Define the camelCase style 237 | dotnet_naming_style.camel_case.capitalization = camel_case 238 | 239 | # pascal_case - Define the Pascal_case style 240 | dotnet_naming_style.pascal_case.capitalization = pascal_case 241 | 242 | # first_upper - The first character must start with an upper-case character 243 | dotnet_naming_style.first_upper.capitalization = first_word_upper 244 | 245 | # prefix_interface_interface_with_i - Interfaces must be PascalCase and the first character of an interface must be an 'I' 246 | dotnet_naming_style.prefix_interface_interface_with_i.capitalization = pascal_case 247 | dotnet_naming_style.prefix_interface_interface_with_i.required_prefix = I 248 | 249 | ######################### 250 | # Naming Rules 251 | ######################### 252 | 253 | # Constant fields must be PascalCase 254 | dotnet_naming_rule.constant_fields_must_be_pascal_case.severity = warning 255 | dotnet_naming_rule.constant_fields_must_be_pascal_case.symbols = constant_fields 256 | dotnet_naming_rule.constant_fields_must_be_pascal_case.style = pascal_case 257 | 258 | # Public, internal and protected readonly fields must be PascalCase 259 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.severity = warning 260 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.symbols = non_private_readonly_fields 261 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.style = pascal_case 262 | 263 | # Static readonly fields must be PascalCase 264 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.severity = warning 265 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.symbols = static_readonly_fields 266 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.style = pascal_case 267 | 268 | # Public and internal fields must be PascalCase 269 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.severity = warning 270 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.symbols = public_internal_fields 271 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.style = pascal_case 272 | 273 | # Public members must be capitalized 274 | dotnet_naming_rule.public_members_must_be_capitalized.severity = warning 275 | dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols 276 | dotnet_naming_rule.public_members_must_be_capitalized.style = first_upper 277 | 278 | # Parameters must be camelCase 279 | dotnet_naming_rule.parameters_must_be_camel_case.severity = warning 280 | dotnet_naming_rule.parameters_must_be_camel_case.symbols = parameters 281 | dotnet_naming_rule.parameters_must_be_camel_case.style = camel_case 282 | 283 | # Class, struct, enum and delegates must be PascalCase 284 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.severity = warning 285 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.symbols = non_interface_types 286 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.style = pascal_case 287 | 288 | # Interfaces must be PascalCase and start with an 'I' 289 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.severity = warning 290 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.symbols = interface_types 291 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.style = prefix_interface_interface_with_i 292 | 293 | dotnet_naming_symbols.private_fields.applicable_kinds = field 294 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private, internal 295 | 296 | # internal and private fields should be _camelCase 297 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion 298 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields 299 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style 300 | 301 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field 302 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal, protected 303 | 304 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _ 305 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case 306 | 307 | 308 | [*.cs] 309 | 310 | # CA1815: Override equals and operator equals on value types 311 | dotnet_diagnostic.CA1815.severity = silent 312 | -------------------------------------------------------------------------------- /test/CometD.UnitTest/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | # top-most EditorConfig file 3 | root = true 4 | 5 | # Unix-style newlines with a newline ending every file 6 | [*] 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | # Matches multiple files with brace expansion notation 12 | # Set default charset 13 | [*.js] 14 | charset = utf-8 15 | 16 | # Solution Files 17 | [*.sln] 18 | indent_style = tab 19 | 20 | # XML Project Files 21 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 22 | indent_size = 2 23 | 24 | # YAML Files 25 | [*.{yml,yaml}] 26 | indent_size = 2 27 | indent_style = space 28 | 29 | # Markdown Files 30 | [*.md] 31 | trim_trailing_whitespace = false 32 | 33 | # .NET Code Style Settings 34 | [*.{cs,csx,cake,vb}] 35 | 36 | [*.cshtml.cs] 37 | dotnet_diagnostic.SA1649.severity = none 38 | 39 | # "this." and "Me." qualifiers 40 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#this_and_me 41 | dotnet_style_qualification_for_field = false:warning 42 | dotnet_style_qualification_for_property = false:warning 43 | dotnet_style_qualification_for_method = false:warning 44 | dotnet_style_qualification_for_event = false:warning 45 | 46 | # Language keywords instead of framework type names for type references 47 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#language_keywords 48 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning 49 | dotnet_style_predefined_type_for_member_access = true:warning 50 | 51 | # Modifier preferences 52 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#normalize_modifiers 53 | dotnet_style_require_accessibility_modifiers = always:warning 54 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning 55 | dotnet_style_readonly_field = true:warning 56 | 57 | # Code-block preferences 58 | csharp_prefer_braces = true:silent 59 | csharp_prefer_simple_using_statement = true:suggestion 60 | 61 | # Expression-level preferences 62 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_level 63 | dotnet_style_object_initializer = true:warning 64 | dotnet_style_collection_initializer = true:warning 65 | dotnet_style_explicit_tuple_names = true:warning 66 | dotnet_style_prefer_inferred_tuple_names = true:warning 67 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning 68 | dotnet_style_prefer_auto_properties = true:warning 69 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning 70 | 71 | # 'using' directive preferences 72 | csharp_using_directive_placement = outside_namespace:silent 73 | 74 | # Null-checking preferences 75 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#null_checking 76 | dotnet_style_coalesce_expression = true:warning 77 | dotnet_style_null_propagation = true:warning 78 | 79 | # Other (Undocumented) 80 | dotnet_style_prefer_conditional_expression_over_return = false 81 | dotnet_style_prefer_conditional_expression_over_assignment = false 82 | 83 | # C# Code Style Settings 84 | [*.{cs,csx,cake}] 85 | # Implicit and explicit types 86 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#implicit-and-explicit-types 87 | csharp_style_var_for_built_in_types = true:warning 88 | csharp_style_var_when_type_is_apparent = true:warning 89 | csharp_style_var_elsewhere = true:warning 90 | 91 | # Expression-bodied members 92 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_bodied_members 93 | csharp_style_expression_bodied_methods = false:warning 94 | csharp_style_expression_bodied_constructors = false:warning 95 | csharp_style_expression_bodied_operators = true:warning 96 | csharp_style_expression_bodied_properties = true:warning 97 | csharp_style_expression_bodied_indexers = true:warning 98 | csharp_style_expression_bodied_accessors = true:warning 99 | 100 | # Pattern matching 101 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#pattern_matching 102 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning 103 | csharp_style_pattern_matching_over_as_with_null_check = true:warning 104 | 105 | # Inlined variable declarations 106 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#inlined_variable_declarations 107 | csharp_style_inlined_variable_declaration = true:warning 108 | 109 | # Expression-level preferences 110 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_level_csharp 111 | csharp_prefer_simple_default_expression = true:warning 112 | csharp_style_deconstructed_variable_declaration = true:warning 113 | csharp_style_pattern_local_over_anonymous_function = true:warning 114 | 115 | # "Null" checking preferences 116 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#null_checking_csharp 117 | csharp_style_throw_expression = true:warning 118 | csharp_style_conditional_delegate_call = true:warning 119 | 120 | # Code block preferences 121 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#code_block 122 | csharp_prefer_braces = true:warning 123 | 124 | ############################# 125 | # .NET Formatting Conventions 126 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions 127 | ############################# 128 | 129 | # Organize usings 130 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#usings 131 | dotnet_sort_system_directives_first = true 132 | dotnet_separate_import_directive_groups = true 133 | 134 | # C# formatting settings 135 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#c-formatting-settings 136 | csharp_new_line_before_open_brace = all 137 | csharp_new_line_before_else = true:warning 138 | csharp_new_line_before_catch = true:warning 139 | csharp_new_line_before_finally = true:warning 140 | csharp_new_line_before_members_in_object_initializers = true:warning 141 | csharp_new_line_before_members_in_anonymous_types = true:warning 142 | csharp_new_line_between_query_expression_clauses = true:warning 143 | 144 | # Indentation options 145 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#indent 146 | csharp_indent_case_contents = true:warning 147 | csharp_indent_switch_labels = true:warning 148 | csharp_indent_labels = no_change:warning 149 | 150 | # Spacing options 151 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#spacing 152 | csharp_space_after_cast = false:warning 153 | csharp_space_after_keywords_in_control_flow_statements = true:warning 154 | csharp_space_between_method_declaration_parameter_list_parentheses = false:warning 155 | csharp_space_between_method_call_parameter_list_parentheses = false:warning 156 | csharp_space_between_parentheses = expressions:warning 157 | csharp_space_before_colon_in_inheritance_clause = true:warning 158 | csharp_space_after_colon_in_inheritance_clause = true:warning 159 | csharp_space_around_binary_operators = before_and_after:warning 160 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false:warning 161 | csharp_space_between_method_call_name_and_opening_parenthesis = false:warning 162 | csharp_space_between_method_call_empty_parameter_list_parentheses = false:warning 163 | 164 | # Wrapping options 165 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#wrapping 166 | csharp_preserve_single_line_statements = false:warning 167 | csharp_preserve_single_line_blocks = true:warning 168 | 169 | # More Indentation options (Undocumented) 170 | csharp_indent_block_contents = true:warning 171 | csharp_indent_braces = false:warning 172 | 173 | # Spacing Options (Undocumented) 174 | csharp_space_after_comma = true:warning 175 | csharp_space_after_dot = false:warning 176 | csharp_space_after_semicolon_in_for_statement = true:warning 177 | csharp_space_around_declaration_statements = do_not_ignore:warning 178 | csharp_space_before_comma = false:warning 179 | csharp_space_before_dot = false:warning 180 | csharp_space_before_semicolon_in_for_statement = false:warning 181 | csharp_space_before_open_square_brackets = false:warning 182 | csharp_space_between_empty_square_brackets = false:warning 183 | csharp_space_between_method_declaration_name_and_open_parenthesis = false:warning 184 | csharp_space_between_square_brackets = false:warning 185 | 186 | ######################### 187 | # .NET Naming conventions 188 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions 189 | ######################### 190 | 191 | [*.{cs,csx,cake,vb}] 192 | # Naming Symbols 193 | # constant_fields - Define constant fields 194 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 195 | dotnet_naming_symbols.constant_fields.required_modifiers = const 196 | 197 | # non_private_readonly_fields - Define public, internal and protected readonly fields 198 | dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, internal, protected 199 | dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field 200 | dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly 201 | 202 | # static_readonly_fields - Define static and readonly fields 203 | dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field 204 | dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly 205 | 206 | # private_readonly_fields - Define private readonly fields 207 | dotnet_naming_symbols.private_readonly_fields.applicable_accessibilities = private 208 | dotnet_naming_symbols.private_readonly_fields.applicable_kinds = field 209 | dotnet_naming_symbols.private_readonly_fields.required_modifiers = readonly 210 | 211 | # public_internal_fields - Define public and internal fields 212 | dotnet_naming_symbols.public_internal_fields.applicable_accessibilities = public, internal 213 | dotnet_naming_symbols.public_internal_fields.applicable_kinds = field 214 | 215 | # private_protected_fields - Define private and protected fields 216 | dotnet_naming_symbols.private_protected_fields.applicable_accessibilities = private, protected 217 | dotnet_naming_symbols.private_protected_fields.applicable_kinds = field 218 | 219 | # public_symbols - Define any public symbol 220 | dotnet_naming_symbols.public_symbols.applicable_accessibilities = public, internal, protected, protected_internal 221 | dotnet_naming_symbols.public_symbols.applicable_kinds = method, property, event, delegate 222 | 223 | # parameters - Defines any parameter 224 | dotnet_naming_symbols.parameters.applicable_kinds = parameter 225 | 226 | # non_interface_types - Defines class, struct, enum and delegate types 227 | dotnet_naming_symbols.non_interface_types.applicable_kinds = class, struct, enum, delegate 228 | 229 | # interface_types - Defines interfaces 230 | dotnet_naming_symbols.interface_types.applicable_kinds = interface 231 | 232 | ######################### 233 | # Naming Styles 234 | ######################### 235 | 236 | # camel_case - Define the camelCase style 237 | dotnet_naming_style.camel_case.capitalization = camel_case 238 | 239 | # pascal_case - Define the Pascal_case style 240 | dotnet_naming_style.pascal_case.capitalization = pascal_case 241 | 242 | # first_upper - The first character must start with an upper-case character 243 | dotnet_naming_style.first_upper.capitalization = first_word_upper 244 | 245 | # prefix_interface_interface_with_i - Interfaces must be PascalCase and the first character of an interface must be an 'I' 246 | dotnet_naming_style.prefix_interface_interface_with_i.capitalization = pascal_case 247 | dotnet_naming_style.prefix_interface_interface_with_i.required_prefix = I 248 | 249 | ######################### 250 | # Naming Rules 251 | ######################### 252 | 253 | # Constant fields must be PascalCase 254 | dotnet_naming_rule.constant_fields_must_be_pascal_case.severity = warning 255 | dotnet_naming_rule.constant_fields_must_be_pascal_case.symbols = constant_fields 256 | dotnet_naming_rule.constant_fields_must_be_pascal_case.style = pascal_case 257 | 258 | # Public, internal and protected readonly fields must be PascalCase 259 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.severity = warning 260 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.symbols = non_private_readonly_fields 261 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.style = pascal_case 262 | 263 | # Static readonly fields must be PascalCase 264 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.severity = warning 265 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.symbols = static_readonly_fields 266 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.style = pascal_case 267 | 268 | # Public and internal fields must be PascalCase 269 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.severity = warning 270 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.symbols = public_internal_fields 271 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.style = pascal_case 272 | 273 | # Public members must be capitalized 274 | dotnet_naming_rule.public_members_must_be_capitalized.severity = warning 275 | dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols 276 | dotnet_naming_rule.public_members_must_be_capitalized.style = first_upper 277 | 278 | # Parameters must be camelCase 279 | dotnet_naming_rule.parameters_must_be_camel_case.severity = warning 280 | dotnet_naming_rule.parameters_must_be_camel_case.symbols = parameters 281 | dotnet_naming_rule.parameters_must_be_camel_case.style = camel_case 282 | 283 | # Class, struct, enum and delegates must be PascalCase 284 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.severity = warning 285 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.symbols = non_interface_types 286 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.style = pascal_case 287 | 288 | # Interfaces must be PascalCase and start with an 'I' 289 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.severity = warning 290 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.symbols = interface_types 291 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.style = prefix_interface_interface_with_i 292 | 293 | dotnet_naming_symbols.private_fields.applicable_kinds = field 294 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private, internal 295 | 296 | # internal and private fields should be _camelCase 297 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion 298 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields 299 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style 300 | 301 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field 302 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal, protected 303 | 304 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _ 305 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case 306 | 307 | 308 | [*.cs] 309 | 310 | # CA1815: Override equals and operator equals on value types 311 | dotnet_diagnostic.CA1815.severity = silent 312 | -------------------------------------------------------------------------------- /src/CometD.NetCore.Salesforce/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | # top-most EditorConfig file 3 | root = true 4 | 5 | # Unix-style newlines with a newline ending every file 6 | [*] 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | # Matches multiple files with brace expansion notation 12 | # Set default charset 13 | [*.js] 14 | charset = utf-8 15 | 16 | # Solution Files 17 | [*.sln] 18 | indent_style = tab 19 | 20 | # XML Project Files 21 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 22 | indent_size = 2 23 | 24 | # YAML Files 25 | [*.{yml,yaml}] 26 | indent_size = 2 27 | indent_style = space 28 | 29 | # Markdown Files 30 | [*.md] 31 | trim_trailing_whitespace = false 32 | 33 | # .NET Code Style Settings 34 | [*.{cs,csx,cake,vb}] 35 | 36 | [*.cshtml.cs] 37 | dotnet_diagnostic.SA1649.severity = none 38 | 39 | # "this." and "Me." qualifiers 40 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#this_and_me 41 | dotnet_style_qualification_for_field = false:warning 42 | dotnet_style_qualification_for_property = false:warning 43 | dotnet_style_qualification_for_method = false:warning 44 | dotnet_style_qualification_for_event = false:warning 45 | 46 | # Language keywords instead of framework type names for type references 47 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#language_keywords 48 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning 49 | dotnet_style_predefined_type_for_member_access = true:warning 50 | 51 | # Modifier preferences 52 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#normalize_modifiers 53 | dotnet_style_require_accessibility_modifiers = always:warning 54 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning 55 | dotnet_style_readonly_field = true:warning 56 | 57 | # Code-block preferences 58 | csharp_prefer_braces = true:silent 59 | csharp_prefer_simple_using_statement = true:suggestion 60 | 61 | # Expression-level preferences 62 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_level 63 | dotnet_style_object_initializer = true:warning 64 | dotnet_style_collection_initializer = true:warning 65 | dotnet_style_explicit_tuple_names = true:warning 66 | dotnet_style_prefer_inferred_tuple_names = true:warning 67 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning 68 | dotnet_style_prefer_auto_properties = true:warning 69 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning 70 | 71 | # 'using' directive preferences 72 | csharp_using_directive_placement = outside_namespace:silent 73 | 74 | # Null-checking preferences 75 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#null_checking 76 | dotnet_style_coalesce_expression = true:warning 77 | dotnet_style_null_propagation = true:warning 78 | 79 | # Other (Undocumented) 80 | dotnet_style_prefer_conditional_expression_over_return = false 81 | dotnet_style_prefer_conditional_expression_over_assignment = false 82 | 83 | # C# Code Style Settings 84 | [*.{cs,csx,cake}] 85 | # Implicit and explicit types 86 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#implicit-and-explicit-types 87 | csharp_style_var_for_built_in_types = true:warning 88 | csharp_style_var_when_type_is_apparent = true:warning 89 | csharp_style_var_elsewhere = true:warning 90 | 91 | # Expression-bodied members 92 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_bodied_members 93 | csharp_style_expression_bodied_methods = false:warning 94 | csharp_style_expression_bodied_constructors = false:warning 95 | csharp_style_expression_bodied_operators = true:warning 96 | csharp_style_expression_bodied_properties = true:warning 97 | csharp_style_expression_bodied_indexers = true:warning 98 | csharp_style_expression_bodied_accessors = true:warning 99 | 100 | # Pattern matching 101 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#pattern_matching 102 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning 103 | csharp_style_pattern_matching_over_as_with_null_check = true:warning 104 | 105 | # Inlined variable declarations 106 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#inlined_variable_declarations 107 | csharp_style_inlined_variable_declaration = true:warning 108 | 109 | # Expression-level preferences 110 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_level_csharp 111 | csharp_prefer_simple_default_expression = true:warning 112 | csharp_style_deconstructed_variable_declaration = true:warning 113 | csharp_style_pattern_local_over_anonymous_function = true:warning 114 | 115 | # "Null" checking preferences 116 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#null_checking_csharp 117 | csharp_style_throw_expression = true:warning 118 | csharp_style_conditional_delegate_call = true:warning 119 | 120 | # Code block preferences 121 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#code_block 122 | csharp_prefer_braces = true:warning 123 | 124 | ############################# 125 | # .NET Formatting Conventions 126 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions 127 | ############################# 128 | 129 | # Organize usings 130 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#usings 131 | dotnet_sort_system_directives_first = true 132 | dotnet_separate_import_directive_groups = true 133 | 134 | # C# formatting settings 135 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#c-formatting-settings 136 | csharp_new_line_before_open_brace = all 137 | csharp_new_line_before_else = true:warning 138 | csharp_new_line_before_catch = true:warning 139 | csharp_new_line_before_finally = true:warning 140 | csharp_new_line_before_members_in_object_initializers = true:warning 141 | csharp_new_line_before_members_in_anonymous_types = true:warning 142 | csharp_new_line_between_query_expression_clauses = true:warning 143 | 144 | # Indentation options 145 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#indent 146 | csharp_indent_case_contents = true:warning 147 | csharp_indent_switch_labels = true:warning 148 | csharp_indent_labels = no_change:warning 149 | 150 | # Spacing options 151 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#spacing 152 | csharp_space_after_cast = false:warning 153 | csharp_space_after_keywords_in_control_flow_statements = true:warning 154 | csharp_space_between_method_declaration_parameter_list_parentheses = false:warning 155 | csharp_space_between_method_call_parameter_list_parentheses = false:warning 156 | csharp_space_between_parentheses = expressions:warning 157 | csharp_space_before_colon_in_inheritance_clause = true:warning 158 | csharp_space_after_colon_in_inheritance_clause = true:warning 159 | csharp_space_around_binary_operators = before_and_after:warning 160 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false:warning 161 | csharp_space_between_method_call_name_and_opening_parenthesis = false:warning 162 | csharp_space_between_method_call_empty_parameter_list_parentheses = false:warning 163 | 164 | # Wrapping options 165 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#wrapping 166 | csharp_preserve_single_line_statements = false:warning 167 | csharp_preserve_single_line_blocks = true:warning 168 | 169 | # More Indentation options (Undocumented) 170 | csharp_indent_block_contents = true:warning 171 | csharp_indent_braces = false:warning 172 | 173 | # Spacing Options (Undocumented) 174 | csharp_space_after_comma = true:warning 175 | csharp_space_after_dot = false:warning 176 | csharp_space_after_semicolon_in_for_statement = true:warning 177 | csharp_space_around_declaration_statements = do_not_ignore:warning 178 | csharp_space_before_comma = false:warning 179 | csharp_space_before_dot = false:warning 180 | csharp_space_before_semicolon_in_for_statement = false:warning 181 | csharp_space_before_open_square_brackets = false:warning 182 | csharp_space_between_empty_square_brackets = false:warning 183 | csharp_space_between_method_declaration_name_and_open_parenthesis = false:warning 184 | csharp_space_between_square_brackets = false:warning 185 | 186 | ######################### 187 | # .NET Naming conventions 188 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions 189 | ######################### 190 | 191 | [*.{cs,csx,cake,vb}] 192 | # Naming Symbols 193 | # constant_fields - Define constant fields 194 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 195 | dotnet_naming_symbols.constant_fields.required_modifiers = const 196 | 197 | # non_private_readonly_fields - Define public, internal and protected readonly fields 198 | dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, internal, protected 199 | dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field 200 | dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly 201 | 202 | # static_readonly_fields - Define static and readonly fields 203 | dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field 204 | dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly 205 | 206 | # private_readonly_fields - Define private readonly fields 207 | dotnet_naming_symbols.private_readonly_fields.applicable_accessibilities = private 208 | dotnet_naming_symbols.private_readonly_fields.applicable_kinds = field 209 | dotnet_naming_symbols.private_readonly_fields.required_modifiers = readonly 210 | 211 | # public_internal_fields - Define public and internal fields 212 | dotnet_naming_symbols.public_internal_fields.applicable_accessibilities = public, internal 213 | dotnet_naming_symbols.public_internal_fields.applicable_kinds = field 214 | 215 | # private_protected_fields - Define private and protected fields 216 | dotnet_naming_symbols.private_protected_fields.applicable_accessibilities = private, protected 217 | dotnet_naming_symbols.private_protected_fields.applicable_kinds = field 218 | 219 | # public_symbols - Define any public symbol 220 | dotnet_naming_symbols.public_symbols.applicable_accessibilities = public, internal, protected, protected_internal 221 | dotnet_naming_symbols.public_symbols.applicable_kinds = method, property, event, delegate 222 | 223 | # parameters - Defines any parameter 224 | dotnet_naming_symbols.parameters.applicable_kinds = parameter 225 | 226 | # non_interface_types - Defines class, struct, enum and delegate types 227 | dotnet_naming_symbols.non_interface_types.applicable_kinds = class, struct, enum, delegate 228 | 229 | # interface_types - Defines interfaces 230 | dotnet_naming_symbols.interface_types.applicable_kinds = interface 231 | 232 | ######################### 233 | # Naming Styles 234 | ######################### 235 | 236 | # camel_case - Define the camelCase style 237 | dotnet_naming_style.camel_case.capitalization = camel_case 238 | 239 | # pascal_case - Define the Pascal_case style 240 | dotnet_naming_style.pascal_case.capitalization = pascal_case 241 | 242 | # first_upper - The first character must start with an upper-case character 243 | dotnet_naming_style.first_upper.capitalization = first_word_upper 244 | 245 | # prefix_interface_interface_with_i - Interfaces must be PascalCase and the first character of an interface must be an 'I' 246 | dotnet_naming_style.prefix_interface_interface_with_i.capitalization = pascal_case 247 | dotnet_naming_style.prefix_interface_interface_with_i.required_prefix = I 248 | 249 | ######################### 250 | # Naming Rules 251 | ######################### 252 | 253 | # Constant fields must be PascalCase 254 | dotnet_naming_rule.constant_fields_must_be_pascal_case.severity = warning 255 | dotnet_naming_rule.constant_fields_must_be_pascal_case.symbols = constant_fields 256 | dotnet_naming_rule.constant_fields_must_be_pascal_case.style = pascal_case 257 | 258 | # Public, internal and protected readonly fields must be PascalCase 259 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.severity = warning 260 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.symbols = non_private_readonly_fields 261 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.style = pascal_case 262 | 263 | # Static readonly fields must be PascalCase 264 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.severity = warning 265 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.symbols = static_readonly_fields 266 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.style = pascal_case 267 | 268 | # Public and internal fields must be PascalCase 269 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.severity = warning 270 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.symbols = public_internal_fields 271 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.style = pascal_case 272 | 273 | # Public members must be capitalized 274 | dotnet_naming_rule.public_members_must_be_capitalized.severity = warning 275 | dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols 276 | dotnet_naming_rule.public_members_must_be_capitalized.style = first_upper 277 | 278 | # Parameters must be camelCase 279 | dotnet_naming_rule.parameters_must_be_camel_case.severity = warning 280 | dotnet_naming_rule.parameters_must_be_camel_case.symbols = parameters 281 | dotnet_naming_rule.parameters_must_be_camel_case.style = camel_case 282 | 283 | # Class, struct, enum and delegates must be PascalCase 284 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.severity = warning 285 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.symbols = non_interface_types 286 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.style = pascal_case 287 | 288 | # Interfaces must be PascalCase and start with an 'I' 289 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.severity = warning 290 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.symbols = interface_types 291 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.style = prefix_interface_interface_with_i 292 | 293 | dotnet_naming_symbols.private_fields.applicable_kinds = field 294 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private, internal 295 | 296 | # internal and private fields should be _camelCase 297 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion 298 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields 299 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style 300 | 301 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field 302 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal, protected 303 | 304 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _ 305 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case 306 | 307 | 308 | [*.cs] 309 | 310 | # CA1815: Override equals and operator equals on value types 311 | dotnet_diagnostic.CA1815.severity = silent 312 | -------------------------------------------------------------------------------- /src/CometD.NetCore.Salesforce/Resilience/ResilientForceClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | using Microsoft.Extensions.Logging; 7 | using Microsoft.Extensions.Options; 8 | 9 | using NetCoreForce.Client; 10 | using NetCoreForce.Client.Models; 11 | 12 | using Polly; 13 | using Polly.Wrap; 14 | 15 | namespace CometD.NetCore.Salesforce.Resilience 16 | { 17 | public class ResilientForceClient : IResilientForceClient 18 | { 19 | private readonly AsyncExpiringLazy _forceClient; 20 | private readonly SalesforceConfiguration _options; 21 | private readonly ILogger _logger; 22 | private readonly AsyncPolicyWrap _policy; 23 | 24 | private readonly string _policyContextMethod = nameof(_policyContextMethod); 25 | 26 | /// 27 | /// Initializes a new instance of the class. 28 | /// 29 | /// 30 | /// 31 | /// 32 | public ResilientForceClient( 33 | Func> forceClient, 34 | IOptions options, 35 | ILogger logger) 36 | { 37 | if (forceClient == null) 38 | { 39 | throw new ArgumentNullException(nameof(forceClient)); 40 | } 41 | 42 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 43 | 44 | _forceClient = forceClient(); 45 | _options = options.Value; 46 | 47 | _policy = Policy.WrapAsync(GetAuthenticationRetryPolicy(), GetWaitAndRetryPolicy()); 48 | } 49 | 50 | /// 51 | public async Task CountQueryAsync( 52 | string queryString, 53 | bool queryAll = false, 54 | CancellationToken cancellationToken = default) 55 | { 56 | var mContext = new Context 57 | { 58 | [_policyContextMethod] = nameof(CountQueryAsync) 59 | }; 60 | 61 | return await _policy.ExecuteAsync( 62 | async (context, token) => 63 | { 64 | var client = _forceClient.Value().Result; 65 | return await client.CountQuery(queryString, queryAll); 66 | }, 67 | mContext, 68 | cancellationToken); 69 | } 70 | 71 | /// 72 | public async Task CreateRecordAsync( 73 | string sObjectTypeName, 74 | T sObject, 75 | Dictionary? customHeaders = null, 76 | CancellationToken cancellationToken = default) 77 | { 78 | var mContext = new Context 79 | { 80 | [_policyContextMethod] = nameof(CreateRecordAsync) 81 | }; 82 | 83 | return await _policy.ExecuteAsync( 84 | async (context, token) => 85 | { 86 | var client = _forceClient.Value().Result; 87 | return await client.CreateRecord( 88 | sObjectTypeName, 89 | sObject, 90 | customHeaders); 91 | }, 92 | mContext, 93 | cancellationToken); 94 | } 95 | 96 | /// 97 | public Task DeleteRecordAsync( 98 | string sObjectTypeName, 99 | string objectId, 100 | CancellationToken cancellationToken = default) 101 | { 102 | var mContext = new Context 103 | { 104 | [_policyContextMethod] = nameof(DeleteRecordAsync) 105 | }; 106 | 107 | return _policy.ExecuteAsync( 108 | (context, token) => 109 | { 110 | var client = _forceClient.Value().Result; 111 | return client.DeleteRecord(sObjectTypeName, objectId); 112 | }, 113 | mContext, 114 | cancellationToken); 115 | } 116 | 117 | /// 118 | public async Task DescribeGlobalAsync(CancellationToken cancellationToken = default) 119 | { 120 | var mContext = new Context 121 | { 122 | [_policyContextMethod] = nameof(DescribeGlobalAsync) 123 | }; 124 | 125 | return await _policy.ExecuteAsync( 126 | async (context, token) => 127 | { 128 | var client = _forceClient.Value().Result; 129 | return await client.DescribeGlobal(); 130 | }, 131 | mContext, 132 | cancellationToken); 133 | } 134 | 135 | /// 136 | public async Task> GetAvailableRestApiVersionsAsync( 137 | string? currentInstanceUrl = null, 138 | CancellationToken cancellationToken = default) 139 | { 140 | var mContext = new Context 141 | { 142 | [_policyContextMethod] = nameof(GetAvailableRestApiVersionsAsync) 143 | }; 144 | 145 | return await _policy.ExecuteAsync( 146 | async (context, token) => 147 | { 148 | var client = _forceClient.Value().Result; 149 | return await client.GetAvailableRestApiVersions(currentInstanceUrl); 150 | }, 151 | mContext, 152 | cancellationToken); 153 | } 154 | 155 | /// 156 | public async Task GetObjectBasicInfoAsync( 157 | string objectTypeName, 158 | CancellationToken cancellationToken = default) 159 | { 160 | var mContext = new Context 161 | { 162 | [_policyContextMethod] = nameof(GetObjectBasicInfoAsync) 163 | }; 164 | 165 | return await _policy.ExecuteAsync( 166 | async (context, token) => 167 | { 168 | var client = _forceClient.Value().Result; 169 | return await client.GetObjectBasicInfo(objectTypeName); 170 | }, 171 | mContext, 172 | cancellationToken); 173 | } 174 | 175 | /// 176 | public async Task GetObjectByIdAsync( 177 | string sObjectTypeName, 178 | string objectId, 179 | List? fields = null, 180 | CancellationToken cancellationToken = default) 181 | { 182 | var mContext = new Context 183 | { 184 | [_policyContextMethod] = nameof(GetObjectByIdAsync) 185 | }; 186 | 187 | return await _policy.ExecuteAsync( 188 | async (context, token) => 189 | { 190 | var client = _forceClient.Value().Result; 191 | return await client.GetObjectById( 192 | sObjectTypeName, 193 | objectId, 194 | fields); 195 | }, 196 | mContext, 197 | cancellationToken); 198 | } 199 | 200 | /// 201 | public async Task GetObjectDescribeAsync( 202 | string objectTypeName, 203 | CancellationToken cancellationToken = default) 204 | { 205 | var mContext = new Context 206 | { 207 | [_policyContextMethod] = nameof(GetObjectDescribeAsync) 208 | }; 209 | 210 | return await _policy.ExecuteAsync( 211 | async (context, token) => 212 | { 213 | var client = _forceClient.Value().Result; 214 | return await client.GetObjectDescribe(objectTypeName); 215 | }, 216 | mContext, 217 | cancellationToken); 218 | } 219 | 220 | /// 221 | public async Task GetOrganizationLimitsAsync(CancellationToken cancellationToken = default) 222 | { 223 | var mContext = new Context 224 | { 225 | [_policyContextMethod] = nameof(GetOrganizationLimitsAsync) 226 | }; 227 | 228 | return await _policy.ExecuteAsync( 229 | async (context, token) => 230 | { 231 | var client = _forceClient.Value().Result; 232 | return await client.GetOrganizationLimits(); 233 | }, 234 | mContext, 235 | cancellationToken); 236 | } 237 | 238 | /// 239 | public async Task GetUserInfoAsync( 240 | string identityUrl, 241 | CancellationToken cancellationToken = default) 242 | { 243 | var mContext = new Context 244 | { 245 | [_policyContextMethod] = nameof(GetUserInfoAsync) 246 | }; 247 | 248 | return await _policy.ExecuteAsync( 249 | async (context, token) => 250 | { 251 | var client = _forceClient.Value().Result; 252 | return await client.GetUserInfo(identityUrl); 253 | }, 254 | mContext, 255 | cancellationToken); 256 | } 257 | 258 | /// 259 | public async Task InsertOrUpdateRecordAsync( 260 | string sObjectTypeName, 261 | string fieldName, 262 | string fieldValue, 263 | T sObject, 264 | Dictionary? customHeaders = null, 265 | CancellationToken cancellationToken = default) 266 | { 267 | var mContext = new Context 268 | { 269 | [_policyContextMethod] = nameof(InsertOrUpdateRecordAsync) 270 | }; 271 | 272 | return await _policy.ExecuteAsync( 273 | async (context, token) => 274 | { 275 | var client = _forceClient.Value().Result; 276 | return await client.InsertOrUpdateRecord( 277 | sObjectTypeName, 278 | fieldName, 279 | fieldValue, 280 | sObject, 281 | customHeaders); 282 | }, 283 | mContext, 284 | cancellationToken); 285 | } 286 | 287 | /// 288 | public async Task> QueryAsync( 289 | string queryString, 290 | bool queryAll = false, 291 | CancellationToken cancellationToken = default) 292 | { 293 | var mContext = new Context 294 | { 295 | [_policyContextMethod] = nameof(QueryAsync) 296 | }; 297 | 298 | return await _policy.ExecuteAsync( 299 | async (context, token) => 300 | { 301 | var client = _forceClient.Value().Result; 302 | return await client.Query( 303 | queryString, 304 | queryAll); 305 | }, 306 | mContext, 307 | cancellationToken); 308 | } 309 | 310 | /// 311 | public async Task QuerySingleAsync( 312 | string queryString, 313 | bool queryAll = false, 314 | CancellationToken cancellationToken = default) 315 | { 316 | var mContext = new Context 317 | { 318 | [_policyContextMethod] = nameof(QuerySingleAsync) 319 | }; 320 | 321 | return await _policy.ExecuteAsync( 322 | async (context, token) => 323 | { 324 | var client = _forceClient.Value().Result; 325 | return await client.QuerySingle( 326 | queryString, 327 | queryAll); 328 | }, 329 | mContext, 330 | cancellationToken); 331 | } 332 | 333 | /// 334 | public async Task> SearchAsync( 335 | string searchString, 336 | CancellationToken cancellationToken = default) 337 | { 338 | var mContext = new Context 339 | { 340 | [_policyContextMethod] = nameof(SearchAsync) 341 | }; 342 | 343 | return await _policy.ExecuteAsync( 344 | async (context, token) => 345 | { 346 | var client = _forceClient.Value().Result; 347 | return await client.Search(searchString); 348 | }, 349 | mContext, 350 | cancellationToken); 351 | } 352 | 353 | /// 354 | public async Task TestConnectionAsync( 355 | string? currentInstanceUrl = null, 356 | CancellationToken cancellationToken = default) 357 | { 358 | var mContext = new Context 359 | { 360 | [_policyContextMethod] = nameof(TestConnectionAsync) 361 | }; 362 | 363 | return await _policy.ExecuteAsync( 364 | async (context, token) => 365 | { 366 | var client = _forceClient.Value().Result; 367 | return await Task.FromResult(client.TestConnection(currentInstanceUrl)); 368 | }, 369 | mContext, 370 | cancellationToken); 371 | } 372 | 373 | /// 374 | public Task UpdateRecordAsync( 375 | string sObjectTypeName, 376 | string objectId, 377 | T sObject, 378 | Dictionary? customHeaders = null, 379 | CancellationToken cancellationToken = default) 380 | { 381 | var mContext = new Context 382 | { 383 | [_policyContextMethod] = nameof(UpdateRecordAsync) 384 | }; 385 | 386 | return _policy.ExecuteAsync( 387 | (context, token) => 388 | { 389 | var client = _forceClient.Value().Result; 390 | return client.UpdateRecord( 391 | sObjectTypeName, 392 | objectId, 393 | sObject, 394 | customHeaders); 395 | }, 396 | mContext, 397 | cancellationToken); 398 | } 399 | 400 | private IAsyncPolicy GetWaitAndRetryPolicy() 401 | { 402 | var jitterer = new Random(); 403 | 404 | return Policy 405 | .Handle(x => !(x is ForceApiException)) 406 | .WaitAndRetryAsync( 407 | retryCount: _options.Retry, // exponential back-off plus some jitter 408 | sleepDurationProvider: (retryAttempt, context) => 409 | { 410 | return TimeSpan.FromSeconds(Math.Pow(_options.BackoffPower, retryAttempt)) 411 | + TimeSpan.FromMilliseconds(jitterer.Next(0, 100)); 412 | }, 413 | onRetry: (ex, span, context) => 414 | { 415 | var methodName = context[_policyContextMethod] ?? "MethodNotSpecified"; 416 | 417 | _logger.LogWarning( 418 | "{Method} wait {Seconds} to execute with exception: {Message} for named policy: {Policy}", 419 | methodName, 420 | span.TotalSeconds, 421 | ex.Message, 422 | context.PolicyKey); 423 | }) 424 | .WithPolicyKey($"{nameof(ResilientForceClient)}WaitAndRetryAsync"); 425 | } 426 | 427 | private IAsyncPolicy GetAuthenticationRetryPolicy() 428 | { 429 | return Policy 430 | .Handle(x => x.Message.Contains("ErrorCode INVALID_SESSION_ID")) 431 | .RetryAsync( 432 | retryCount: _options.Retry, 433 | onRetryAsync: async (ex, count, context) => 434 | { 435 | var methodName = context[_policyContextMethod] ?? "MethodNotSpecified"; 436 | 437 | _logger.LogWarning( 438 | "{Method} attempting to re-authenticate Retry {Count} of {Total} for named policy {PolicyKey}, due to {Message}.", 439 | methodName, 440 | count, 441 | _options.Retry, 442 | context.PolicyKey, 443 | ex.Message); 444 | 445 | await _forceClient.Invalidate(); 446 | }) 447 | .WithPolicyKey($"{nameof(ResilientForceClient)}RetryAsync"); 448 | } 449 | } 450 | } 451 | --------------------------------------------------------------------------------