Force.com-Toolkit-for-NET icon indicating copy to clipboard operation
Force.com-Toolkit-for-NET copied to clipboard

ExecuteRestApiAsync argument syntax?

Open EricHirst-BZ opened this issue 5 years ago • 4 comments

This is a question, not a bug, However, I'd really like to see something more explicit in the documentation.

I'm trying to run the following code.

        using (var queryClient = new ForceClient(authClient.InstanceUrl, authClient.AccessToken, authClient.ApiVersion))
        {
            // Works fine
            string soql = GetMyQuery();
            QueryResult<dynamic> result = await queryClient.QueryAsync<dynamic>(soql);

            // Throws exception.  Changing version # doesn't seem to have any effect.
            dynamic restResult = await queryClient.ExecuteRestApiAsync<dynamic>("/services/data/v46.0/sobjects/Task/describe");

When I get to ExecuteRestApiAsync, I get the exception:

Salesforce.Common.ForceException
  HResult=0x80131500
  Message=Could not find a match for URL
  Source=Salesforce.Common
  StackTrace:
   at Salesforce.Common.JsonHttpClient.<HttpGetAsync>d__4`1.MoveNext()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
[...]

I've guessed a number of other strings but am running out of ideas. When I use https://workbench.developerforce.com/restExplorer.php GET functionality with this same argument, (/services/data/v46.0/sobjects/Task/describe), it works fine and returns what I need.

Can someone tell me how to use ForceClient to get the same results I get back from the Salesforce Workbench REST Explorer?

Thanks, Eric

EricHirst-BZ avatar Oct 19 '19 23:10 EricHirst-BZ

I partially answered my own question at https://stackoverflow.com/a/58496413/2055511. The correct method to call in this case would have been DescribeAsync. BasicInformationAsync can also be used for more general cases as long as they're based on sobjects, although it's a hack.

I don't see a way to use the library for the more general case of something like Reports, which use "analytics" instead of "sobjects" in the resource URLs. Unless I'm missing something, that seems like a defect in the library. The following addition to ForceClient.cs would resolve this.

 public Task<T> GenericGetRequestAsync<T>(string uri)
  {
    if (string.IsNullOrEmpty(resturi))
      throw new ArgumentNullException(nameof (uri));
    return this._jsonHttpClient.HttpGetAsync<T>(uri));
  }

EricHirst-BZ avatar Oct 22 '19 02:10 EricHirst-BZ

I'm probably way off and not at all familiar with Salesforce but it seems to me that this method wants to generate the URL from the apiName as:

/services/apexrest/[[apiName]] and there is no way of overriding this.

I've been given a custom endpoint that looks like /services/data/v47.0/actions/custom/apex/apiName

Is there a way to hit this with a custom json object and get a response using Force.com? I feel like I'm missing a piece of the puzzle.

cheeseytoastie avatar Mar 19 '20 11:03 cheeseytoastie

@cheeseytoastie what we've been doing is pretty much bypassing all the parts of the library that are doing string.Format to get between us and the API name that we really want to use. The key here is to call the ForceClient ctor which takes the HttpClients as input. For example something like the following:

var httpClientForJson = SalesforceHttpClientFactory.Instance.GetJson(); var httpClientForXml = SalesforceHttpClientFactory.Instance.GetXml(); using (var queryClient = new ForceClient(_authenticationObject.InstanceUrl, _authenticationObject.AccessToken, ApiVersion, //_authenticationObject.ApiVersion httpClientForJson, httpClientForXml, true)) { [...]

Then we use the httpClientForJson essentially the same way you can see the library using it, but with any REST API method that works in https://workbench.developerforce.com.

This approach also has the advantage of better control of the scope/life of the httpclient objects.

EricHirst-BZ avatar Mar 19 '20 18:03 EricHirst-BZ

@EricHirst-BZ Thanks for helpful example. It pointed me in the right direction.

I had the requirement of doing an HTTP DELETE to a URL like /services/apexrest/...

Extending ForceClient and using Salesforce.Common.Common.FormatRestApiUrl got me where I needed to go:

public class CustomForceClient
    : ForceClient
{
    private string InstanceUrl { get; }

    public CustomForceClient(string instanceUrl, string accessToken, string apiVersion)
        : base(instanceUrl, accessToken, apiVersion)
    {
        InstanceUrl = instanceUrl;
    }

    public CustomForceClient(string instanceUrl, string accessToken, string apiVersion, HttpClient httpClientForJson, HttpClient httpClientForXml, bool callerWillDisposeHttpClients = false)
        : base(instanceUrl, accessToken, apiVersion, httpClientForJson, httpClientForXml, callerWillDisposeHttpClients)
    {
        InstanceUrl = instanceUrl;
    }

    public async Task<bool> ExecuteRestApiDeleteAsync(string customApiWithParams)
    {
        if (string.IsNullOrEmpty(customApiWithParams))
            throw new ArgumentNullException(nameof(customApiWithParams));

        var url = Common.FormatRestApiUrl(customApiWithParams, InstanceUrl);

        var response = await this._jsonHttpClient.HttpDeleteAsync(url);
        return response;
    }
}

jkraft-s1a avatar Feb 04 '21 17:02 jkraft-s1a