CKEditor 和 C# Web API,使用简单的上传插件上传图片

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

在我的项目中,我使用 CKEditor WYSWYG 包为我的网站制作 HTML 内容。 可以插入图片并直接从包中发送到服务器。

2 天以来,我试图弄清楚如何从 Angular 前端捕获发送的图像到 Web API,但仍然没有成功。

我在 CKEditor 5 中使用 .Net6 和 Angular 12。

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);
    }
}

我终于找到了可行的解决方案。

我的上传-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);
   
    }
   
  }

在Angular组件中

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

C# Web API 控制器

[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