2

Is there a way to create a byte array from a file in JavaScript (or Typescript) that will be sent to a C# WebApi service, and consumable by the C# byte array method parameter? If so, can you provide an example of the JavaScript?

I should mention, this is an Angular 4+ application that will be posting to the service.

I need to upload Word documents, PDFs and images to a C# service, and this is the way the C# service is currently built. It expects a byte array of the file being uploaded.

This is the request object of the C# service:

[DataContract]
public class SaveAttachmentRequest : BaseRequest
{
    [RestUrlParameter(Mode = UrlParameterMode.Inline)]
    [DataMember(IsRequired = true)] public AttachmentLookupInformation LookupInformation { get; set; } = new AttachmentLookupInformation();
    [DataMember(IsRequired = true)] public byte[] Attachment { get; set; } = null;
}

And this is the AttachmentLookupInformation class:

[DataContract]
public class AttachmentLookupInformation
{
    [DataMember(IsRequired = true)] public Guid Id { get; set; } = Guid.Empty;
    [DataMember(IsRequired = true)] public Guid LinkedToId { get; set; } = Guid.Empty;
    ///<summary>Not including the path- just the file name and extension.</summary>
    [DataMember(IsRequired = true)] public string FileName { get; set; } = string.Empty;
    [DataMember(IsRequired = true)] public int FileSizeInBytes { get; set; } = 0;
    ///<summary>MIME type.</summary>
    [DataMember(IsRequired = true)] public string ContentType { get; set; } = string.Empty;
    [DataMember(IsRequired = true)] public AttachmentCategories AttachmentCategory { get; set; } = AttachmentCategories.Unknown;
    [DataMember(IsRequired = true)] public string DownloadUrl { get; set; } = string.Empty;
    [DataMember(IsRequired = true)] public string CreatedBy { get; set; } = string.Empty;
    [DataMember(IsRequired = true)] public DateTimeOffset CreatedTimestamp { get; set; } = DateTimeOffset.MinValue;
}

This is so I can upload the file and additional data to the service.

EDIT: including failed Angular code that I used:

const lookupInfo = new AttachmentLookupInformation();
lookupInfo.Id = Helpers.emptyGuid;
lookupInfo.LinkedToId = this.contractId;
lookupInfo.AttachmentCategory = 10;

const request = new SaveAttachmentRequest();

const reader = new FileReader();
reader.onload = function() {
  const buffer = reader.result;
  let binary = '';
  const bytes = new Uint8Array(buffer);
  const length = bytes.byteLength;
  for (let i = 0; i < length; i++) {
    binary += String.fromCharCode(bytes[i]);
  }

  request.Attachment = binary;
  lookupInfo.ContentType = files[0].type;
  lookupInfo.FileName = files[0].name;
  lookupInfo.FileSizeInBytes = length;
};
reader.readAsArrayBuffer(files[0]);
reader.onloadend = function() {
  request.LookupInformation = lookupInfo;

  console.log('request: ', request);

  that.contractsService
    .save(request, 'attachment/saveattachment')
    .subscribe(res => {
      if (res.Success) {
        console.log('upload response: ', res);
      }
    });

};
King Wilder
  • 629
  • 1
  • 7
  • 19
  • https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data – nvioli Jan 30 '18 at 18:20
  • @nvioli - I did try this days ago, but I couldn't get it to work, meaning that the service rejected my post with a 406, invalid input parameter. I probably did something wrong, but not sure what. – King Wilder Jan 30 '18 at 18:29
  • That's the information you should put in your post, then. It's much easier for the community to help when given a Minimal, Complete, and Verifiable example (https://stackoverflow.com/help/mcve) rather thank asking for an end-to-end solution. If you think the problem is with your javascript code, then provide the javascript code you've written and detail the error you're getting. – nvioli Jan 30 '18 at 19:22
  • @nvioli - You're right. I just added the code that I tried that failed. It's returning the 406 - Invalid input parameter error. – King Wilder Jan 30 '18 at 19:34
  • Note: this question's title really shouldn't be "create a byte array in JavaScript" - it looks like this is already being done. Where the issue is, is getting the server-side to recognize that it's a `byte[]` and sending it. – vapcguy Apr 05 '18 at 20:20

2 Answers2

1

Well, you have:

that.contractsService
    .save(request, 'attachment/saveattachment')

But your class you're showing is SaveAttachmentRequest, with public byte[] Attachment inside that. You don't have a function showing in your question called saveattachment inside a class called attachment, which is what should be processing the transferring of the data to the byte[], I would think, unless you're somehow just setting it directly into the parameter. Assuming you have such an attachment class, it should be where you'd have an entry point called saveattachment that does all the work. But say that it is failing because you are passing in your JS model and mapping it directly to the .NET one. So then create a string parameter that can accept your binary string, then save it over to your byte[] one.

[DataMember(IsRequired = true)] public string strAttachment { get; set; } = null;

and in your JS,

request.Attachment = binary;

becomes

request.strAttachment = binary;

Then you'd need:

public class attachment : ApiController
{
    [System.Web.Http.HttpPost]
    public JsonResult saveattachment(SaveAttachmentRequest sar, AttachmentLookupInformation ali)
    {          
        //string bits = "000011110000001000";
        string bits = sar.strAttachment;
        int numOfBytes = (int)Math.Ceiling(bits.Length / 8m);
        byte[] bytes = new byte[numOfBytes];
        int chunkSize = 8;

        for (int i = 1; i <= numOfBytes; i++)
        {
            int startIndex = bits.Length - 8 * i;
            if (startIndex < 0)
            {
                chunkSize = 8 + startIndex;
                startIndex = 0;
            }
            bytes[numOfBytes - i] = Convert.ToByte(bits.Substring(startIndex, chunkSize), 2);
        }

        sar.attachment = bytes;

        // ... do your other stuff for saving your model objects to the DB
        // using Entity Framework's data context, etc.
    }
}

That's just one way of converting a binary string to byte[]. Plenty of other ways here.

Or, have you tried just sending it as a string, making this:

[DataMember(IsRequired = true)] public byte[] Attachment { get; set; } = null;

turn into this:

[DataMember(IsRequired = true)] public string Attachment { get; set; } = null;

in the first place?

vapcguy
  • 7,097
  • 1
  • 56
  • 52
-1

In file type input change event you can get byte array. This should help you.

Sumeet Kale
  • 365
  • 1
  • 11