For uploading images from a client to the server I use chunking.
Here is the client code:
private async Task UploadPersonImage(int personId, string fileName, CancellationToken cancellationToken)
{
var stream = Client.UploadPersonImage();
PersonImageMessage personImageMessage = new PersonImageMessage();
personImageMessage.PersonId = personId;
personImageMessage.ImageType = ImageType.Jpg;
byte[] image = File.ReadAllBytes(fileName);
int imageOffset = 0;
byte[] imageChunk = new byte[imageChunkSize];
while (imageOffset < image.Length && !cancellationToken.IsCancellationRequested)
{
int length = Math.Min(imageChunkSize, image.Length - imageOffset);
Buffer.BlockCopy(image, imageOffset, imageChunk, 0, length);
imageOffset += length;
ByteString byteString = ByteString.CopyFrom(imageChunk);
personImageMessage.ImageChunk = byteString;
await stream.RequestStream.WriteAsync(personImageMessage).ConfigureAwait(false);
}
await stream.RequestStream.CompleteAsync().ConfigureAwait(false);
if (!cancellationToken.IsCancellationRequested)
{
var uploadPersonImageResult = await stream.ResponseAsync.ConfigureAwait(false);
// Process answer...
}
}
And this is the server code:
public override async Task<TransferStatusMessage> UploadPersonImage(
IAsyncStreamReader<PersonImageMessage> requestStream, ServerCallContext context)
{
TransferStatusMessage transferStatusMessage = new TransferStatusMessage();
transferStatusMessage.Status = TransferStatus.Success;
try
{
await Task.Run(
async () =>
{
CancellationToken cancellationToken = context.CancellationToken;
await using (Stream fs = File.OpenWrite(ImageFileName))
{
await foreach (PersonImageMessage personImageMessage in
requestStream.ReadAllAsync(cancellationToken).ConfigureAwait(false))
{
fs.Write(personImageMessage.ImageChunk.ToByteArray());
}
}
}).ConfigureAwait(false);
}
// Is thrown on cancellation -> ignore...
catch (OperationCanceledException)
{
transferStatusMessage.Status = TransferStatus.Cancelled;
}
catch (RpcException rpcEx)
{
if (rpcEx.StatusCode == StatusCode.Cancelled)
{
transferStatusMessage.Status = TransferStatus.Cancelled;
}
else
{
_logger.LogError($"Exception while processing image file '{ImageFileName}'. Exception: '{requestStream}'");
transferStatusMessage.Status = TransferStatus.Failure;
}
}
// Delete incomplete file
if (transferStatusMessage.Status != TransferStatus.Success)
{
File.Delete(ImageFileName);
}
return transferStatusMessage;
}
Everything works fine. I want to cancel the upload in between sending the chunks. Now, CompleteAsync() is called and the server thinks the data transfer ended successfully. I'm looking for a way to trigger the cancellation in the server (i.e. the CancellationToken in the ServerCallContext) via the client. As a workaround I could add a flag to PersonImageMessage, something like 'upload_cancelled', to tell the server that the transfer is aborted. But there must be a built-in mechanism.
Does somebody know the trick?