Blazor整併CKEditor5(二)_圖檔上傳處理
通常富文本編輯器中還涉及到圖檔上傳的處裡
我們可在Blazor Server專案目錄創建webapi 目錄並新增一個API 控制器
ImageHandlerController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace XXXProj.WebApi { [Route("api/Images")] [ApiController] public class ImageHandlerController : ControllerBase { [HttpPost, DisableRequestSizeLimit] public IActionResult Upload() { try { IFormFile file = Request.Form.Files[0]; if (file.Length > 0) { string strFileName = $"{Guid.NewGuid()}_{file.FileName}"; using var stream = new FileStream(Path.Combine($"{System.IO.Directory.GetCurrentDirectory()}{@"\wwwroot"}", "images", strFileName), FileMode.Create); file.CopyTo(stream); string cur_url = GetFullUrl(); return Ok(new { url = $"{cur_url}/{strFileName}" }); } else { return BadRequest("Image File not found"); } } catch (Exception ex) { return BadRequest(ex.Message); } } private string GetFullUrl() { StringBuilder sb = new StringBuilder(); sb.Append(HttpContext.Request.Scheme) .Append("://") .Append(HttpContext.Request.Host) .Append(HttpContext.Request.PathBase) .Append(HttpContext.Request.Path) .Append(HttpContext.Request.QueryString) .ToString(); return sb.ToString(); } [HttpGet("{fileName}")] public async Task<IActionResult> LoadFile(string fileName) { try { string path = Path.Combine($"{System.IO.Directory.GetCurrentDirectory()}{@"\wwwroot"}", "images", fileName); MemoryStream memory = new MemoryStream(); using (FileStream stream = new FileStream(path, FileMode.Open)) { await stream.CopyToAsync(memory); } memory.Position = 0; return File(memory, @"application/octet-stream", Path.GetFileName(path)); } catch (Exception ex) { return BadRequest(ex.Message); } } } } |
新增client端接收圖檔上傳用的js
editor.js外掛UploadAdapter.js
UploadAdapter.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | class UploadAdapter { constructor(loader) { // The file loader instance to use during the upload. this.loader = loader; } // Starts the upload process. upload() { return this.loader.file .then(file => 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. const full_cur_url = location.protocol + '//' + location.host; console.log("_initRequest_current url:" + full_cur_url); const api_endpoint = full_cur_url + '/api/Images' xhr.open('POST', api_endpoint, true); //xhr.open('POST', 'http://localhost:55322/api/Images', true); xhr.responseType = 'json'; } // Initializes XMLHttpRequest listeners. _initListeners(resolve, reject, file) { 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: 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 => { if (evt.lengthComputable) { loader.uploadTotal = evt.total; loader.uploaded = evt.loaded; } }); } } // Prepares the data and sends the request. _sendRequest(file) { // 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); } } |
這邊要注意
前後端call api的end point都要記得改為動態獲取方式
避免直接寫死url endpoint 避免程式要頻繁更動來更動去的問題
再到editor.js進行擴充編輯
editor.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | editors = {}; function CustomUploadAdapterPlugin(editor) { editor.plugins.get('FileRepository').createUploadAdapter = (loader) => { return new UploadAdapter(loader); }; } function CreateEditor(editorId, defaultValue, height, dotNetReference) { ClassicEditor .create(document.getElementById(editorId), { extraPlugins: [CustomUploadAdapterPlugin], toolbar: { items: [ 'heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', '|', 'outdent', 'indent', '|', 'imageUpload', 'blockQuote', 'insertTable', 'mediaEmbed', 'undo', 'redo' ] }, language: 'en', licenseKey: '', }) .then(editor => { editors[editorId] = editor; editor.setData(defaultValue); editor.editing.view.change(writer => { writer.setStyle('height', height, editor.editing.view.document.getRoot()); }); editor.model.document.on('change:data', () => { let data = editor.getData(); dotNetReference.invokeMethodAsync('OnEditorChanged', data); }); }) .catch(error => { console.error(error); }); } function DestroyEditor(editorId) { editors[editorId].destroy().then(() => delete editors[editorId]) .catch(error => console.log(error)); } |
記得_Host.cshtml要回去添加額外新建的外掛js(要在editor.js之前)
那還要記得
到Startup.cs處理同源政策的配置問題
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public void ConfigureServices(IServiceCollection services) { //....省 services.AddCors(options => { options.AddPolicy("any", builder => { //允許任何不同來源的站台應用存取 builder.SetIsOriginAllowed(_ => true) .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials(); }); }); //....省 } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { //....省 app.UseCors("any");//必須要在app.UseRouting();之後,app.UseAuthorization();跟app.UseEndpoints之前 //....省 } |
藉此就能完善FCKEditor 富文本編輯器的圖檔上傳了
留言
張貼留言