Search code examples
c#angularasp.net-web-apickeditor5.net-6.0

CKEditor and C# Web API, upload image with simple upload plugin


In my project I use CKEditor WYSWYG package to make HTML content for my website. There is possible to insert image and send it directly from the package to the server.

Since 2 days I try to figure out how is it possible to catch the sent image from the Angular front-end to the Web API, but still no success.

I use .Net6 and Angular 12 with CKEditor 5.

public async Task<ActionResult<string>> AddPostPhoto(IFormFile photo)
{
    try
    {
        System.Console.WriteLine(Request.ContentType);

        var folderDirectory = $"\\Photos\\PostPhotos";
        var path = Path.Combine("Photos/PostPhotos", "fileName.jpg");

        var memoryStream = new MemoryStream();
        await Request.Body.CopyToAsync(memoryStream);
        
        System.Console.WriteLine(Request.HttpContext.Request.ContentLength);
        System.Console.WriteLine(Request.Form.Keys);

        if (!Directory.Exists(folderDirectory))
        {
            Directory.CreateDirectory(folderDirectory);
        }

        await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
        {
            memoryStream.WriteTo(fs);
        }

        return Ok(new { Url = path });
    } 
    catch(Exception exception)
    {
        return BadRequest(exception.Message);
    }
}

Solution

  • Finally I could find a working solution.

    my-upload-adapter.ts

    //ckeditorExComponent class Ends here and MyUploadAdapter class begins here in the same ckeditorEx.ts
    export class MyUploadAdapter {
        xhr: any;
        loader: any;
        serverUrl: string;
        baseApiUrl: string;
       
        constructor(loader: any, serverUrl: string, baseApiUrl: string) {
          // The file loader instance to use during the upload.
          this.loader = loader;
          this.serverUrl = serverUrl;
          this.baseApiUrl = baseApiUrl;
        }
       
        // Starts the upload process.
        upload() {
          return this.loader.file
            .then((file: any) => new Promise((resolve, reject) => {
              this._initRequest();
              this._initListeners(resolve, reject, file);
              this._sendRequest(file);
            }));
        }
       
        // Aborts the upload process.
        abort() {
          if (this.xhr) {
            this.xhr.abort();
          }
        }
       
        // Initializes the XMLHttpRequest object using the URL passed to the constructor.
        _initRequest() {
          const xhr = this.xhr = new XMLHttpRequest();
      
          // Note that your request may look different. It is up to you and your editor
          // integration to choose the right communication channel. This example uses
          // a POST request with JSON as a data structure but your configuration
          // could be different.
          //Replace below url with your API url
          xhr.open('POST', this.baseApiUrl + 'Tutorial/add-post-photo', true);
          xhr.responseType = 'json';
        }
       
        // Initializes XMLHttpRequest listeners.
        _initListeners(resolve: any, reject: any, file: any) {
          const xhr = this.xhr;
          const loader = this.loader;
          const genericErrorText = `Couldn't upload file: ${file.name}.`;
       
          xhr.addEventListener('error', () => reject(genericErrorText));
          xhr.addEventListener('abort', () => reject());
          xhr.addEventListener('load', () => {
       
            const response = xhr.response;
       
            // This example assumes the XHR server's "response" object will come with
            // an "error" which has its own "message" that can be passed to reject()
            // in the upload promise.
            //
            // Your integration may handle upload errors in a different way so make sure
            // it is done properly. The reject() function must be called when the upload fails.
            if (!response || response.error) {
              return reject(response && response.error ? response.error.message : genericErrorText);
            }
       
            // If the upload is successful, resolve the upload promise with an object containing
            // at least the "default" URL, pointing to the image on the server.
            // This URL will be used to display the image in the content. Learn more in the
            // UploadAdapter#upload documentation.
            resolve({
              default: this.serverUrl + response.url
            });
          });
       
          // Upload progress when it is supported. The file loader has the #uploadTotal and #uploaded
          // properties which are used e.g. to display the upload progress bar in the editor
          // user interface.
       
          if (xhr.upload) {
            xhr.upload.addEventListener('progress', (evt: any) => {
              if (evt.lengthComputable) {
                loader.uploadTotal = evt.total;
                loader.uploaded = evt.loaded;
              }
            });
          }
        }
       
        // Prepares the data and sends the request.
       
        _sendRequest(file: any) { 
          // Prepare the form data. 
          const data = new FormData();
          data.append('upload', file);
      
          // Important note: This is the right place to implement security mechanisms
          // like authentication and CSRF protection. For instance, you can use
          // XMLHttpRequest.setRequestHeader() to set the request headers containing
          // the CSRF token generated earlier by your application.
          // Send the request.
       
          this.xhr.send(data);
       
        }
       
      }
    

    In the Angular component

    onReady($event: any) {
        $event.plugins.get('FileRepository').createUploadAdapter = (loader: any) => {
            return new MyUploadAdapter(loader, this.serverUrl, this.apiUrl);
        };
    }
    

    The C# Web API controller

    [HttpPost("add-post-photo")]
    public async Task<ActionResult<string>> AddPostPhoto(IFormFile upload)
    {
        try
        {
            FileInfo fileInfo = new FileInfo(upload.FileName);
            System.Console.WriteLine(upload.FileName);
            var folderDirectory = $"\\Photos\\PostPhotos";
            var path = Path.Combine("Photos\\PostPhotos", upload.FileName);
    
            var memoryStream = new MemoryStream();
            await upload.OpenReadStream().CopyToAsync(memoryStream);
    
            if (!Directory.Exists(folderDirectory))
            {
                Directory.CreateDirectory(folderDirectory);
            }
    
            await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
            {
                memoryStream.WriteTo(fs);
            }
    
            return Ok(new { Url = path });
        } 
        catch(Exception exception)
        {
            return BadRequest(exception.Message);
        }
    }
    

    It is important to have the parameter upload, otherwise the find the back-end endpoint