I'm trying to create a batch request that will create multiple graph notification subscriptions in a single request.
I've been reading this article:
https://learn.microsoft.com/en-us/graph/sdks/batch-requests?tabs=csharp
Problem
I'm getting the following error message but I don't know how to address it:
(local variable) Task<Subscription> userRequest
Argument 1: cannot convert from 'System.Threading.Tasks.Task<Microsoft.Graph.Subscription>' to 'Microsoft.Graph.BatchRequestStep'
Code
var batchRequestContent = new BatchRequestContent();
GraphServiceClient graphClient = await GenerateGraphAuthToken(this.amParms);
foreach (string userId in this.UserstoSubscribe)
{
var subscription = new Subscription
{
ChangeType = "updated",
NotificationUrl= notificationURL,
Resource = $"users/{userId}/drive/root",
ExpirationDateTime = DateTimeOffset.Parse(subscriptionDate),
ClientState = "secretClientValue",
LatestSupportedTlsVersion = "v1_2"
};
var userRequest = graphClient.Subscriptions
.Request()
.AddAsync(subscription);
var userRequestId = batchRequestContent.AddBatchRequestStep(userRequest);
}
var returnedResponse = await graphClient.Batch.Request().PostAsync(batchRequestContent);
Any tips would be appreciated. Sorry. I'm just new to .NET and to MS Graph.
AddBatchRequestStep method expects as a parameter either BatchRequestStep or IBaseRequest or HttpRequestMessage.
In your case you have a POST request, so you must get the HttpRequestMessage and convert to a POST.
var subscription = new Subscription
{
ChangeType = "updated",
NotificationUrl= notificationURL,
Resource = $"users/{userId}/drive/root",
ExpirationDateTime = DateTimeOffset.Parse(subscriptionDate),
ClientState = "secretClientValue",
LatestSupportedTlsVersion = "v1_2"
};
// create a json content from the subscription
var jsonSubscription = graphClient.HttpProvider.Serializer.SerializeAsJsonContent(subscription);
// create a HttpRequestMessage, specify the method and add the json content
var userRequest = graphClient.Subscriptions.Request().GetHttpRequestMessage();
userRequest.Method = HttpMethod.Post;
userRequest.Content = jsonSubscription;
// add userRequest to a batch request content
var userRequestId = batchRequestContent.AddBatchRequestStep(userRequest);
Related
What is the code to add users to AAD group or remove users from AAD group in batches in C#? (first find batch size and then add or remove users). Any sample code would be great.
UPDATE:
I added the following code:
private HttpRequestMessage MakeRequest(AzureADUser user, Guid targetGroup)
{
return new HttpRequestMessage(HttpMethod.Patch, $"https://graph.microsoft.com/v1.0/groups/{targetGroup}")
{
Content = new StringContent(MakeAddRequestBody(user), System.Text.Encoding.UTF8, "application/json"),
};
}
private static string MakeAddRequestBody(AzureADUser user)
{
JObject body = new JObject
{
["members#odata.bind"] = JArray.FromObject($"https://graph.microsoft.com/v1.0/users/{user.ObjectId}")
};
return body.ToString(Newtonsoft.Json.Formatting.None);
}
public async Task AddUsersToGroup1(IEnumerable<AzureADUser> users, AzureADGroup targetGroup)
{
try
{
var batches = GetBatchRequest(users, targetGroup.ObjectId);
foreach (var batchRequestContent in batches)
{
var response = await _graphServiceClient
.Batch
.Request()
.WithMaxRetry(10)
.PostAsync(batchRequestContent);
var responses = await response.GetResponsesAsync();
}
}
catch (Exception ex)
{
}
}
On running this I get the following exception: Object serialized to String. JArray instance expected. What am I missing? Also, once I get the responses, I need to check if all of the response returned an 'OK' response or not similar to:
return responses.Any(x => x == ResponseCode.Error) ? ResponseCode.Error : ResponseCode.Ok;
How would I do that?
Add users into AAD Group in batch:
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var additionalData = new Dictionary<string, object>()
{
{"members#odata.bind", new List<string>()}
};
(additionalData["members#odata.bind"] as List<string>).Add("https://graph.microsoft.com/v1.0/users/{id}"");
(additionalData["members#odata.bind"] as List<string>).Add("https://graph.microsoft.com/v1.0/users/{id}"");
var group = new Group
{
AdditionalData = additionalData
};
await graphClient.Groups["{group-id}"]
.Request()
.UpdateAsync(group);
There is not an endpoint which we can use to remove users from AAD Group in batch. But there is a batch endpoint which combines multiple requests in one HTTP call. It seems to have a limitation of 20. So we can't delete too many users in one call.
Here is an example, remove users from AAD Group in batch (Reference here):
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var removeUserRequest1 = graphClient.Groups["{group-id}"].Members["{id}"].Reference.Request().GetHttpRequestMessage();
var removeUserRequest2 = graphClient.Groups["{group-id}"].Members["{id}"].Reference.Request().GetHttpRequestMessage();
removeUserRequest1.Method = HttpMethod.Delete;
removeUserRequest2.Method = HttpMethod.Delete;
var batchRequestContent = new BatchRequestContent();
batchRequestContent.AddBatchRequestStep(removeUserRequest1);
batchRequestContent.AddBatchRequestStep(removeUserRequest2);
await graphClient.Batch.Request().PostAsync(batchRequestContent);
Trying to make use of the AndroidPublisherService from Play Developer API Client.
I can list active tracks and the releases in those tracks, but when I try to upload a new build there seems to be no way of attaching the authentication already made previously to read data.
I've authenticated using var googleCredentials = GoogleCredential.FromStream(keyDataStream) .CreateWithUser(serviceUsername); where serviceUsername is the email for my service account.
private static void Execute(string packageName, string aabfile, string credfile, string serviceUsername)
{
var credentialsFilename = credfile;
if (string.IsNullOrWhiteSpace(credentialsFilename))
{
// Check env. var
credentialsFilename =
Environment.GetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS",
EnvironmentVariableTarget.Process);
}
Console.WriteLine($"Using credentials {credfile} with package {packageName} for aab file {aabfile}");
var keyDataStream = File.OpenRead(credentialsFilename);
var googleCredentials = GoogleCredential.FromStream(keyDataStream)
.CreateWithUser(serviceUsername);
var credentials = googleCredentials.UnderlyingCredential as ServiceAccountCredential;
var service = new AndroidPublisherService();
var edit = service.Edits.Insert(new AppEdit { ExpiryTimeSeconds = "3600" }, packageName);
edit.Credential = credentials;
var activeEditSession = edit.Execute();
Console.WriteLine($"Edits started with id {activeEditSession.Id}");
var tracksList = service.Edits.Tracks.List(packageName, activeEditSession.Id);
tracksList.Credential = credentials;
var tracksResponse = tracksList.Execute();
foreach (var track in tracksResponse.Tracks)
{
Console.WriteLine($"Track: {track.TrackValue}");
Console.WriteLine("Releases: ");
foreach (var rel in track.Releases)
Console.WriteLine($"{rel.Name} version: {rel.VersionCodes.FirstOrDefault()} - Status: {rel.Status}");
}
using var fileStream = File.OpenRead(aabfile);
var upload = service.Edits.Bundles.Upload(packageName, activeEditSession.Id, fileStream, "application/octet-stream");
var uploadProgress = upload.Upload();
if (uploadProgress == null || uploadProgress.Exception != null)
{
Console.WriteLine($"Failed to upload. Error: {uploadProgress?.Exception}");
return;
}
Console.WriteLine($"Upload {uploadProgress.Status}");
var tracksUpdate = service.Edits.Tracks.Update(new Track
{
Releases = new List<TrackRelease>(new[]
{
new TrackRelease
{
Name = "Roswell - Grenis Dev Test",
Status = "completed",
VersionCodes = new List<long?>(new[] {(long?) upload?.ResponseBody?.VersionCode})
}
})
}, packageName, activeEditSession.Id, "internal");
tracksUpdate.Credential = credentials;
var trackResult = tracksUpdate.Execute();
Console.WriteLine($"Track {trackResult?.TrackValue}");
var commitResult = service.Edits.Commit(packageName, activeEditSession.Id);
Console.WriteLine($"{commitResult.EditId} has been committed");
}
And as the code points out, all action objects such as tracksList.Credential = credentials; can be given the credentials generated from the service account.
BUT the actual upload action var upload = service.Edits.Bundles.Upload(packageName, activeEditSession.Id, fileStream, "application/octet-stream"); does not expose a .Credential object, and it always fails with:
The service androidpublisher has thrown an exception: Google.GoogleApiException: Google.Apis.Requests.RequestError
Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project. [401]
Errors [
Message[Login Required.] Location[Authorization - header] Reason[required] Domain[global]
]
at Google.Apis.Upload.ResumableUpload`1.InitiateSessionAsync(CancellationToken cancellationToken)
at Google.Apis.Upload.ResumableUpload.UploadAsync(CancellationToken cancellationToken)
So, how would I go about providing the actual Upload action with the given credentials here?
Managed to figure this out during the day, I was missing one call to CreateScoped() when creating the GoogleCredential object as well as a call to InitiateSession() on the upload object.
var googleCredentials = GoogleCredential.FromStream(keyDataStream)
.CreateWithUser(serviceUsername)
.CreateScoped(AndroidPublisherService.Scope.Androidpublisher);
Once that was done I could then get a valid oauth token by calling
var googleCredentials = GoogleCredential.FromStream(keyDataStream)
.CreateWithUser(serviceUsername)
.CreateScoped(AndroidPublisherService.Scope.Androidpublisher);
var credentials = googleCredentials.UnderlyingCredential as ServiceAccountCredential;
var oauthToken = credentials?.GetAccessTokenForRequestAsync(AndroidPublisherService.Scope.Androidpublisher).Result;
And I can now use that oauth token in the upload request:
upload.OauthToken = oauthToken;
_ = await upload.InitiateSessionAsync();
var uploadProgress = await upload.UploadAsync();
if (uploadProgress == null || uploadProgress.Exception != null)
{
Console.WriteLine($"Failed to upload. Error: {uploadProgress?.Exception}");
return;
}
The full code example for successfully uploading a new aab file to google play store internal test track thus looks something like this:
private async Task UploadGooglePlayRelease(string fileToUpload, string changeLogFile, string serviceUsername, string packageName)
{
var serviceAccountFile = ResolveServiceAccountCertificateInfoFile();
if (!serviceAccountFile.Exists)
throw new ApplicationException($"Failed to find the service account certificate file. {serviceAccountFile.FullName}");
var keyDataStream = File.OpenRead(serviceAccountFile.FullName);
var googleCredentials = GoogleCredential.FromStream(keyDataStream)
.CreateWithUser(serviceUsername)
.CreateScoped(AndroidPublisherService.Scope.Androidpublisher);
var credentials = googleCredentials.UnderlyingCredential as ServiceAccountCredential;
var oauthToken = credentials?.GetAccessTokenForRequestAsync(AndroidPublisherService.Scope.Androidpublisher).Result;
var service = new AndroidPublisherService();
var edit = service.Edits.Insert(new AppEdit { ExpiryTimeSeconds = "3600" }, packageName);
edit.Credential = credentials;
var activeEditSession = await edit.ExecuteAsync();
_logger.LogInformation($"Edits started with id {activeEditSession.Id}");
var tracksList = service.Edits.Tracks.List(packageName, activeEditSession.Id);
tracksList.Credential = credentials;
var tracksResponse = await tracksList.ExecuteAsync();
foreach (var track in tracksResponse.Tracks)
{
_logger.LogInformation($"Track: {track.TrackValue}");
_logger.LogInformation("Releases: ");
foreach (var rel in track.Releases)
_logger.LogInformation($"{rel.Name} version: {rel.VersionCodes.FirstOrDefault()} - Status: {rel.Status}");
}
var fileStream = File.OpenRead(fileToUpload);
var upload = service.Edits.Bundles.Upload(packageName, activeEditSession.Id, fileStream, "application/octet-stream");
upload.OauthToken = oauthToken;
_ = await upload.InitiateSessionAsync();
var uploadProgress = await upload.UploadAsync();
if (uploadProgress == null || uploadProgress.Exception != null)
{
Console.WriteLine($"Failed to upload. Error: {uploadProgress?.Exception}");
return;
}
_logger.LogInformation($"Upload {uploadProgress.Status}");
var releaseNotes = await File.ReadAllTextAsync(changeLogFile);
var tracksUpdate = service.Edits.Tracks.Update(new Track
{
Releases = new List<TrackRelease>(new[]
{
new TrackRelease
{
Name = $"{upload?.ResponseBody?.VersionCode}",
Status = "completed",
InAppUpdatePriority = 5,
CountryTargeting = new CountryTargeting { IncludeRestOfWorld = true },
ReleaseNotes = new List<LocalizedText>(new []{ new LocalizedText { Language = "en-US", Text = releaseNotes } }),
VersionCodes = new List<long?>(new[] {(long?) upload?.ResponseBody?.VersionCode})
}
})
}, packageName, activeEditSession.Id, "internal");
tracksUpdate.Credential = credentials;
var trackResult = await tracksUpdate.ExecuteAsync();
_logger.LogInformation($"Track {trackResult?.TrackValue}");
var commitResult = service.Edits.Commit(packageName, activeEditSession.Id);
commitResult.Credential = credentials;
await commitResult.ExecuteAsync();
_logger.LogInformation($"{commitResult.EditId} has been committed");
}
Here I have created my project on the standard .NET library to GET/POST invoices. But as I want to email the invoice to which it's being created on that name. Here is my sample code below to create invoice.
public async Task<ActionResult> Create(string Name, string LineDescription, string LineQuantity, string LineUnitAmount, string LineAccountCode)
{
var xeroToken = TokenUtilities.GetStoredToken();
var utcTimeNow = DateTime.UtcNow;
var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider();
var httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
XeroConfiguration XeroConfig = new XeroConfiguration
{
ClientId = ConfigurationManager.AppSettings["XeroClientId"],
ClientSecret = ConfigurationManager.AppSettings["XeroClientSecret"],
CallbackUri = new Uri(ConfigurationManager.AppSettings["XeroCallbackUri"]),
Scope = ConfigurationManager.AppSettings["XeroScope"],
State = ConfigurationManager.AppSettings["XeroState"]
};
if (utcTimeNow > xeroToken.ExpiresAtUtc)
{
var client = new XeroClient(XeroConfig, httpClientFactory);
xeroToken = (XeroOAuth2Token)await client.RefreshAccessTokenAsync(xeroToken);
TokenUtilities.StoreToken(xeroToken);
}
string accessToken = xeroToken.AccessToken;
string xeroTenantId = xeroToken.Tenants[0].TenantId.ToString();
//string xeroTenantId = xeroToken.Tenants[1].TenantId.ToString();
var contact = new Contact();
contact.Name = Name;
var line = new LineItem()
{
Description = LineDescription,
Quantity = decimal.Parse(LineQuantity),
UnitAmount = decimal.Parse(LineUnitAmount),
AccountCode = LineAccountCode
};
var lines = new List<LineItem>() { line };
//var lines = new List<LineItem>();
//for (int j = 0;j < 5;j++)
//{
// lines.Add(line);
//}
var invoice = new Invoice()
{
Type = Invoice.TypeEnum.ACCREC,
Contact = contact,
Date = DateTime.Today,
DueDate = DateTime.Today.AddDays(30),
LineItems = lines
};
var invoiceList = new List<Invoice>();
invoiceList.Add(invoice);
var invoices = new Invoices();
invoices._Invoices = invoiceList;
var AccountingApi = new AccountingApi();
var response = await AccountingApi.CreateInvoicesAsync(accessToken, xeroTenantId, invoices);
RequestEmpty _request = new RequestEmpty();
//trying this method to send email to specified invoice....
//var test = await AccountingApi.EmailInvoiceAsync(accessToken, xeroTenantId, Guid.NewGuid(), null);
var updatedUTC = response._Invoices[0].UpdatedDateUTC;
return RedirectToAction("Index", "InvoiceSync");
}
Now as I learned that Xero allows sending email to that specified invoice, here is a link which I learned.
https://developer.xero.com/documentation/api/invoices#email
But as try to find method in the .NET standard library for Xero I stumble upon this method.
var test = await AccountingApi.EmailInvoiceAsync(accessToken, xeroTenantId, Guid.NewGuid(), null);
How can I use this method to send email to a specified invoice ..?
It throws me an error regarding Cannot assign void to an implicitly-typed variable.
There is another method also in this library.
var test2 = await AccountingApi.EmailInvoiceAsyncWithHttpInfo(accessToken, xeroTenantId, Guid.NewGuid(), null);
As Guid.NewGuid() i have used is for only testing will add created GUID when I understand how these two methods operate.
Update 1:
Here is the method second method i used.
await AccountingApi.EmailInvoiceAsyncWithHttpInfo(accessToken, xeroTenantId, Guid.NewGuid(), null)
Update 2:
Here is the code i used.
public async Task EmailInvoiceTest(string accessToken,string xeroTenantId,Guid invoiceID, RequestEmpty requestEmpty)
{
var AccountingApi = new AccountingApi();
await AccountingApi.EmailInvoiceAsync(accessToken, xeroTenantId, invoiceID, requestEmpty).ConfigureAwait(false);
}
The return type of method EmailInvoiceAsync seems to be Task with return type void. If you await the task, there is no return type which could be assigned to a variable. Remove the variable assignment and pass a valid argument for parameter of type RequestEmpty to solve the problem.
RequestEmpty requestEmpty = new RequestEmpty();
await AccountingApi.EmailInvoiceAsync(accessToken, xeroTenantId, Guid.NewGuid(), requestEmpty);
For an example test see here
IMPORTANT: According to the documentation (see section Emailing an invoice) the invoice must be of Type ACCREC and must have a valid status for sending (SUMBITTED, AUTHORISED or PAID).
I'm trying to consume a Graphql Api from a C# client. For that I'm using the GraphQl.Net Nuget package. The problem is that, I have no idea how to set the Api Url as I don't have HttpRequest object and this results also with additional problems that I can't set the authentcation header and send the token with the request. My code looks like:
public void Post(TestGraphQl.GraphQLQuery query)
{
var inputs = query.Variables.ToInputs();
var queryToExecute = query.Query;
var result = _executer.ExecuteAsync(_ =>
{
_.Schema = _schema;
_.Query = queryToExecute;
_.OperationName = query.OperationName;
_.Inputs = inputs;
//_.ComplexityConfiguration = new ComplexityConfiguration { MaxDepth = 15 };
_.FieldMiddleware.Use<InstrumentFieldsMiddleware>();
}).Result;
var httpResult = result.Errors?.Count > 0
? HttpStatusCode.BadRequest
: HttpStatusCode.OK;
var json = _writer.Write(result);
}
And the caller looks like this:
var jObject = new Newtonsoft.Json.Linq.JObject();
jObject.Add("id", deviceId);
client.Post(new GraphQLQuery { Query = "query($id: String) { device (id: $id) { displayName, id } }", Variables = jObject });
I'm totally new to this topic and appreciate any help. Many thanks!!
This worked out for me. You will need the GraphQL.Client Package. My_class is the class for the deserialization.
var client = new GraphQLHttpClient(Api_Url, new NewtonsoftJsonSerializer());
var request = new GraphQLRequest
{
Query = {query}
};
var response = await client.SendQueryAsync<my_class>(request);
Not sure if you are still looking for it. One can always use GraphQl.Client nuget to achieve this. Sample code to consume is
var query = #"query($id: String) { device (id: $id) { displayName, id } }";
var request = new GraphQLRequest(){
Query = query,
Variables = new {id =123}
};
var graphQLClient = new GraphQLClient("http://localhost:8080/api/GraphQL");
graphQLClient.DefaultRequestHeaders.Add("Authorization", "yourtoken");
var graphQLResponse = await graphQLClient.PostAsync(request);
Console.WriteLine(graphQLResponse.Data);
I am currently trying to update a submodule's commit id automatically when another project has certain files changed. I have a .net webhook and I'm using the octokit.net library.
I can see in the github documentation (https://developer.github.com/v3/git/trees/#create-a-tree) there is a submodule option when creating a new tree that allows you to add a commit and path, but I can't get it to work. Octokit also has a submodule type for the NewTreeItem/TreeItem objects but no examples or documentation.
My current code is here - currently I'm passing the commit sha in as the sha parameter, but I can see this is wrong, I need to create a commit on that repo and use that sha, I just don't know how to do that before the tree is created and there isn't any documentation that I can find:
public static async Task UpdateSubmoduleInBranch(string repo, string branchName, string submodulePath, string sha, string commitComment, GitHubClient github = null)
{
//hoping this will update the sha of a submodule
// url encode branch name for github operations
branchName = HttpUtility.UrlEncode(branchName);
if (github == null) github = GetClient();
var repoId = (await github.Repository.Get(Settings.GitHub.OrgName, repo)).Id;
RepositoriesClient rClient = new RepositoriesClient(new ApiConnection(github.Connection));
var branch = await rClient.Branch.Get(repoId, branchName);
var tree = await github.Git.Tree.Get(repoId, branchName);
var newTree = new NewTree { BaseTree = tree.Sha };
newTree.Tree.Add(new NewTreeItem
{
Mode = Octokit.FileMode.Submodule,
Path = submodulePath,
Type = TreeType.Commit,
Sha = sha
});
var createdTree = await github.Git.Tree.Create(repoId, newTree);
var newCommit = new NewCommit(commitComment, createdTree.Sha, new[] { branch.Commit.Sha });
newCommit.Committer = Settings.GitHub.Committer;
var createdCommit = await github.Git.Commit.Create(Settings.GitHub.OrgName, Settings.GitHub.AppName, newCommit);
var updateRef = new ReferenceUpdate(createdCommit.Sha, false);
await github.Git.Reference.Update(repoId, "heads/" + branchName, updateRef);
}
Edit
In case anyone else is looking for this, I resolved the issue - the octokit api does not support this action, even if it looks like it does.
In addition to the PatchAsync method found at PATCH Async requests with Windows.Web.Http.HttpClient class, the following code worked for me:
public static async Task UpdateSubmoduleInBranch(string repo, string branchName, string submodulePath, string sha, string commitComment)
{
using (var client = new HttpClient())
{
try
{
//these headers authenticate with github, useragent is required.
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Settings.GitHub.AuthToken);
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("mathspathway-environment-manager", "v1.0"));
var committer = Settings.GitHub.Committer;
//get the branch, and collect the sha of the current commit
var branchResponse = await client.GetAsync($"{Settings.GitHub.ApiUrl}/repos/{Settings.GitHub.OrgName}/{repo}/branches/{branchName}");
JToken branchResult = JToken.Parse(await branchResponse.Content.ReadAsStringAsync());
var currentCommitSha = branchResult["commit"].Value<string>("sha");
//create the new tree, with the mode of 160000 (submodule mode) and type of commit, and the sha of the other
//repository's commit that you want to update the submodule to, and the base tree of the current commit on this repo
var newTreeObj = new
{
base_tree = currentCommitSha,
tree = new List<Object> { new { path = submodulePath, mode= "160000", type = "commit", sha = sha}
}
};
HttpContent treeHttpContent = new StringContent(JsonConvert.SerializeObject(newTreeObj));
var treeResponse = await client.PostAsync($"{Settings.GitHub.ApiUrl}/repos/{Settings.GitHub.OrgName}/{repo}/git/trees", treeHttpContent);
var treeResponseContent = JToken.Parse(await treeResponse.Content.ReadAsStringAsync());
var treeSha = treeResponseContent.Value<string>("sha");
//Create a new commit based on the tree we just created, with the parent of the current commit on the branch
var newCommitObj = new
{
message = commitComment,
author = new { name = committer.Name, email = committer.Email, date = committer.Date },
parents = new[] { currentCommitSha },
tree = treeSha
};
HttpContent newCommitContent = new StringContent(JsonConvert.SerializeObject(newCommitObj));
var commitResponse = await client.PostAsync($"{Settings.GitHub.ApiUrl}/repos/{Settings.GitHub.OrgName}/{repo}/git/commits", newCommitContent);
var commitResponseContent = JToken.Parse(await commitResponse.Content.ReadAsStringAsync());
var commitSha = commitResponseContent.Value<string>("sha");
//create an update reference object, and update the branch's head commit reference to the new commit
var updateRefObject = new { sha = commitSha, force = false };
HttpContent updateRefContent = new StringContent(JsonConvert.SerializeObject(updateRefObject));
var updateRefResponse = await client.PatchAsync($"{Settings.GitHub.ApiUrl}/repos/{Settings.GitHub.OrgName}/{repo}/git/refs/heads/{branchName}", updateRefContent);
} catch (Exception ex)
{
Debug.WriteLine($"Error occurred updating submodule: {ex.Message}{Environment.NewLine}{Environment.NewLine}{ex.StackTrace}");
}
}
}
While trying to accomplish roughly the same goal, but via a separate user's submitting a pull request, I gave your original code a shot with some minor changes. The result was that it worked perfectly with Octokit.net.
Extracted sample code
var submoduleRepoId = (await gitHubClient.Repository.Get(submoduleRepoOwnerName, submoduleRepoName)).Id;
var submoduleRepoBranchLatestSha = (await gitHubClient.Git.Tree.Get(submoduleRepoId, submoduleRepoBranchName)).Sha;
…
var updateParentTree = new NewTree { BaseTree = parentRepoBranchLatestSha };
updateParentTree.Tree.Add(new NewTreeItem
{
Mode = FileMode.Submodule,
Sha = submoduleRepoBranchLatestSha,
Path = pathToSubmoduleInParentRepo,
Type = TreeType.Commit,
});
var newParentTree = await gitHubClient.Git.Tree.Create(pullRequestOwnerForkRepoId, updateParentTree);
var commitMessage = $"Bump to {submoduleOwnerName}/{submoduleRepoName}#{submoduleCommitHash}";
var newCommit = new NewCommit(commitMessage, newParentTree.Sha, parentBranchLatestSha);
var pullRequestBranchRef = $"heads/{pullRequestBranchName}";
var commit = await gitHubClient.Git.Commit.Create(pullRequestOwnerName, parentRepoName, newCommit);
var await gitHubClient.Git.Reference.Update(pullRequestOwnerForkRepoId, pullRequestBranchRef, new ReferenceUpdate(commit.Sha));
Full sample code
At this point, there are only a few potential differences I can see.
I definitely do not HttpUtility.UrlEncode my branch name (Octokit must be doing any needed encoding for me)
I am committing to a separate user's branch on their own fork
It could be those differences were sufficient or maybe a bug has been worked out that was around when you were attempting the same thing.