開啟網站時發生異常
service unavailable http error 503. the service is unavailable
前一次開啟剛網站時還正常
Windows 10 更新以後,開啟架設在IIS上的網站(ASP.NET Core 2.0)時發生錯誤的訊息,搜尋一下得知是這個網站的 ApplicationPool 是[停用]狀態,啟用以後執行網站,還是出現同樣異常,ApplicationPool 又變成[停用]狀態,用事件檢視器查看來源為[IIS AspNetCore Module]的資訊,發現下面這則訊息
無法載入 C:\WINDOWS\System32\inetsrv\iiswsock.dll
前幾天開發時有用到 WebSocket,也能正常使用。開啟[開啟或關閉 Windows 功能],點開[Internet Infomation Services]>[World Wide Web 服務]>[應用程式開發功能]發現之前已經勾選的[WebSocket通訊協定]是未選取狀態,勾選以後問題就解決了。
2018年5月20日 星期日
2018年5月18日 星期五
ASP.NET Core 2.0 使用 WebSocket 讓設備和網頁可以即時通訊
目前有一個專案,其中一項功能為
"接收設備端傳送過來的數據並顯示在頁面上"
例如空調系統設備傳送當前溫度
後端使用 ASP.NET Core 2.0 webapi 開發
以前有在 .Net Framework 上使用過 ASP.NET SignalR
查資料得知目前(寫這篇筆記的時候) .Net Core 版本的 SingalR 還在開發階段
2.1 版本才會發行正式版本,有些文章建議不適合在正式產品中使用開發版本
於是就查了如何在 .Net Core 使用 WebSocket
#1 [ASP.NET Core: Building chat room using WebSocket]
http://gunnarpeipman.com/aspnet/aspnet-core-websocket-chat/
上面這篇文章使用網頁發送和接收訊息
但是目前專案需求是要由設備發送訊息
#2 [Real-time chart using ASP.NET Core and WebSocket]
http://gunnarpeipman.com/aspnet/aspnet-core-websocket-chart/
上面這篇文章使用api當端點給設備端呼叫,符合專案的需求
以上兩種發送訊息的方式都想要使用,於是整合兩篇文章
使用 #1 的 SocketMiddleware 和 # 2 的 SocketManager
#2 的 SocketManager 不需修改, #1 產生 socketId 和保存當前 socket 的程式改使用 # 2 的 SocketManaer
後續 :
整合過的程式執行起來正常,但是接收到的中文字串變問號,檢查後發現問題出在 #2 的 SocketManager,發送訊息(SendMessageAsync)使用的 Encoding 是ASCII,改為UTF8或是Default以後可以正常顯示中文,但是序列化成JSON格式的字串卻少了最後一個大括號,調整成下面這段程式碼以後就正常了
"接收設備端傳送過來的數據並顯示在頁面上"
例如空調系統設備傳送當前溫度
後端使用 ASP.NET Core 2.0 webapi 開發
以前有在 .Net Framework 上使用過 ASP.NET SignalR
查資料得知目前(寫這篇筆記的時候) .Net Core 版本的 SingalR 還在開發階段
2.1 版本才會發行正式版本,有些文章建議不適合在正式產品中使用開發版本
於是就查了如何在 .Net Core 使用 WebSocket
#1 [ASP.NET Core: Building chat room using WebSocket]
http://gunnarpeipman.com/aspnet/aspnet-core-websocket-chat/
上面這篇文章使用網頁發送和接收訊息
但是目前專案需求是要由設備發送訊息
#2 [Real-time chart using ASP.NET Core and WebSocket]
http://gunnarpeipman.com/aspnet/aspnet-core-websocket-chart/
上面這篇文章使用api當端點給設備端呼叫,符合專案的需求
以上兩種發送訊息的方式都想要使用,於是整合兩篇文章
使用 #1 的 SocketMiddleware 和 # 2 的 SocketManager
#2 的 SocketManager 不需修改, #1 產生 socketId 和保存當前 socket 的程式改使用 # 2 的 SocketManaer
public class ACSystemSocketMiddleware { private readonly RequestDelegate _next; private readonly ACSystemSocketManager _socketManager; public ChatWebSocketMiddleware( RequestDelegate next, ACSystemSocketManager socketManager) { _next = next; _socketManager = socketManager; } public async Task Invoke(HttpContext context) { if (!context.WebSockets.IsWebSocketRequest) { await _next.Invoke(context); return; } CancellationToken ct = context.RequestAborted; WebSocket currentSocket = await context.WebSockets.AcceptWebSocketAsync(); var socketId = _socketManager.AddSocket(currentSocket); while (true) { if (ct.IsCancellationRequested) { break; } var response = await ReceiveStringAsync(currentSocket, ct); if (string.IsNullOrEmpty(response)) { if (currentSocket.State != WebSocketState.Open) { break; } continue; } foreach (var socket in _socketManager.GetAll()) { if (socket.Value.State != WebSocketState.Open) { continue; } await SendStringAsync(socket.Value, response, ct); } } await _socketManager.RemoveSocket(socketId); await currentSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", ct); currentSocket.Dispose(); } private static Task SendStringAsync( WebSocket socket, string data, CancellationToken ct = default(CancellationToken)) { var buffer = Encoding.UTF8.GetBytes(data); var segment = new ArraySegment<byte>(buffer); return socket.SendAsync(segment, WebSocketMessageType.Text, true, ct); } private static async Task<string> ReceiveStringAsync( WebSocket socket, CancellationToken ct = default(CancellationToken)) { var buffer = new ArraySegment<byte>(new byte[8192]); using (var ms = new MemoryStream()) { WebSocketReceiveResult result; do { ct.ThrowIfCancellationRequested(); result = await socket.ReceiveAsync(buffer, ct); ms.Write(buffer.Array, buffer.Offset, result.Count); } while (!result.EndOfMessage); ms.Seek(0, SeekOrigin.Begin); if (result.MessageType != WebSocketMessageType.Text) { return null; } using (var reader = new StreamReader(ms, Encoding.UTF8)) { return await reader.ReadToEndAsync(); } } } }
後續 :
整合過的程式執行起來正常,但是接收到的中文字串變問號,檢查後發現問題出在 #2 的 SocketManager,發送訊息(SendMessageAsync)使用的 Encoding 是ASCII,改為UTF8或是Default以後可以正常顯示中文,但是序列化成JSON格式的字串卻少了最後一個大括號,調整成下面這段程式碼以後就正常了
private async Task SendMessageAsync(WebSocket socket, string message) { if (socket.State != WebSocketState.Open) return; var buffer = Encoding.UTF8.GetBytes(message); var segment = new ArraySegment<byte>(buffer); await socket.SendAsync( segment, WebSocketMessageType.Text, true, CancellationToken.None); }
2018年5月14日 星期一
React & ASP.NET Core 2.0 Upload File
一個新增檔案的Form含有上傳圖片的功能並可預覽圖片,圖片與表單內容一起傳送到後端,資料新增後再存檔圖片
建立一個選取圖片的子Component
建立新增資料的Form的父Component,兩個輸入欄位的type分別為text和file
後端接收資料資料的參數類型
處理上傳的後端程式
建立一個選取圖片的子Component
import React from 'react'; class ImageUpload extends React.Component { constructor(props) { super(props); this.state = { file: '', imagePreviewUrl: '' }; } handleImageChange(e) { e.preventDefault(); let reader = new FileReader(); let file = e.target.files[0]; reader.onloadend = () => { this.setState({ file: file, imagePreviewUrl: reader.result }); }; reader.readAsDataURL(file); this.props.onImageChange(file); } render() { let { imagePreviewUrl } = this.state; let $imagePreview = null; if (imagePreviewUrl) { $imagePreview = <img src={imagePreviewUrl} />; } else { $imagePreview = ( <div className="previewText"> Please select an Image for Preview </div> ); } return ( <div className="previewComponent form-group"> <label htmlFor="file">{this.props.label}</label> <input id="file" type="file" className="fileInput form-control" accept="image/*" onChange={e => this.handleImageChange(e)} /> <div className="imgPreview"> {$imagePreview} </div> </div> ); } } ImageUpload.defaultProps = { label: '上傳圖片', onImageChange: (file) => { console.log('default onImageChange() has been called', file); } }; export default ImageUpload;
建立新增資料的Form的父Component,兩個輸入欄位的type分別為text和file
import React from 'react'; import axios from 'axios'; import ImageUpload from './ImageUpload'; class CreateProduct extends React.Component { constructor(props) { super(props); this.state = { product: '', file: '', } this.handleSubmit = this.handleSubmit.bind(this); this.handleChange = this.handleChange.bind(this); this.handleImageChange = this.handleImageChange.bind(this); } handleSubmit(e) { e.preventDefault(); const formData = new FormData(); const keys = Object.keys(this.state); keys.forEach(key => { formData.append(key, this.state[key]); }); const headers = { 'Content-Type': 'multipart/form-data' }; axios.post('api/product', formData, { headers: headers }).then(response => { console.log('create success', response); }); } handleChange(e) { this.setState({ [e.target.id]: e.target.value }); } handleImageChange(file) { this.setState({ file }); } render() { const { product } = this.state; return ( <div> <form onSubmit={this.handleSubmit}> <div> <label htmlFor="url">Product</label> <input type="text" id="product" value={product} onChange={this.handleChange} /> </div> <ImageUpload onImageChange={this.handleImageChange} /> <button type="submit">Submit</button> </form> </div> ); } } export default CreateProduct;
後端接收資料資料的參數類型
public class ProductCreationModel { public string Product{ get; set; } public IFormFile File { get; set; } }
處理上傳的後端程式
public class AppService { private readonly IHostingEnvironment _environment; public AppService(IHostingEnvironment environment) { _environment = environment ?? throw new ArgumentNullException(nameof(environment)); } public async Task<string> Upload(IFormFile file, string name = null) { if (string.IsNullOrWhiteSpace(_environment.WebRootPath)) { _environment.WebRootPath = Path.Combine( Directory.GetCurrentDirectory(), "wwwroot" ); } var dir = Path.Combine(_environment.WebRootPath, "uploads"); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } var fileName = file.FileName; var path = Path.Combine(dir, fileName); if (!string.IsNullOrEmpty(name)) // 使用自訂的檔案名稱 { var extension = Path.GetExtension(path); fileName = $"{name}{extension}"; path = path.Replace(file.FileName, fileName); } if (file.Length > 0) { using (var fileStream = new FileStream(path, FileMode.Create)) { try { await file.CopyToAsync(fileStream); return fileName; } catch (Exception ex) { throw ex; } } } else { throw new Exception("file is empty"); } } }
訂閱:
文章 (Atom)