(本篇程式是用 vb.net 寫成)
前一篇留下一個問題,若是要提供服務 .html, .js, .css, .jpg, .png, .gif ... 等等,我想到的辦法是自行撰寫程式來處理。在參考了[1] 的 route 用法,我在 config.Routes.MapHttpRoute 多加 4 個 route。img、css、js 分別提供 圖檔、css、js 三大類檔案的服務。而 default 則是提供第一個頁面的服務。
config.Routes.MapHttpRoute("img", "img/{name}", New With {.controller = "Img", .action = "g"})
config.Routes.MapHttpRoute("css", "css/{name}", New With {.controller = "Css", .action = "g"})
config.Routes.MapHttpRoute("js", "js/{name}", New With {.controller = "Js", .action = "g"})
config.Routes.MapHttpRoute("API", "{controller}/{action}/{id}", New With {.id = RouteParameter.Optional}) ' vs2010 just can not IntelliSense....
config.Routes.MapHttpRoute("default", "", New With {.controller = "Home", .action = "g"})
接下來則是加入 ImgController, CssController, JsController, HomeController 這四個 class
Public Class ImgController
Inherits ApiController
Dim accept_type As Dictionary(Of String, String) = New Dictionary(Of String, String) From {{".jpg", "jpg"}, {".png", "png"}, {".gif", "gif"}}
<HttpGet()> Public Function g(ByVal name As String) As HttpResponseMessage
Dim content As New StreamContent(New System.IO.FileStream(Request.RequestUri.LocalPath.Substring(1), IO.FileMode.Open))
Dim ext As String = System.IO.Path.GetExtension(Request.RequestUri.LocalPath).ToLower()
If accept_type.ContainsKey(ext) Then
content.Headers.ContentType = New MediaTypeHeaderValue("image/" & ext)
End If
Dim response = New HttpResponseMessage() With {.Content = content}
'response.Headers.CacheControl = New CacheControlHeaderValue() With {.MaxAge = New TimeSpan(1, 0, 0)}
Return response
End Function
End Class
Public Class CssController
Inherits ApiController
<HttpGet()> Public Function g(ByVal name As String) As HttpResponseMessage
Dim content As New StreamContent(New System.IO.FileStream(Request.RequestUri.LocalPath.Substring(1), IO.FileMode.Open))
content.Headers.ContentType = New MediaTypeHeaderValue("text/css")
Dim response = New HttpResponseMessage() With {.Content = content}
'response.Headers.CacheControl = New CacheControlHeaderValue() With {.MaxAge = New TimeSpan(1, 0, 0)}
Return response
End Function
End Class
Public Class JsController
Inherits ApiController
<HttpGet()> Public Function g(ByVal name As String) As HttpResponseMessage
Dim content As New StreamContent(New System.IO.FileStream(Request.RequestUri.LocalPath.Substring(1), IO.FileMode.Open))
content.Headers.ContentType = New MediaTypeHeaderValue("text/javascript")
Dim response = New HttpResponseMessage() With {.Content = content}
'response.Headers.CacheControl = New CacheControlHeaderValue() With {.MaxAge = New TimeSpan(1, 0, 0)}
Return response
End Function
End Class
Public Class HomeController
Inherits ApiController
<HttpGet()> Public Function g() As HttpResponseMessage
Dim content As New StreamContent(New System.IO.FileStream("html/first.html", IO.FileMode.Open))
content.Headers.ContentType = New MediaTypeHeaderValue("text/html")
Dim response As New HttpResponseMessage() With {.Content = content}
Return response
End Function
End Class
接下來,就是把測試用的 first.html, .css, .js, .png 放到 html, css, js, img 目錄去。
以下是 first.html
<!doctype html>
<html>
<head></head>
<title>web browser</title>
<body>
<link href="css/site.css" rel="stylesheet" />
<script src="js/jquery.js" type="text/javascript" ></script>
<script type="text/javascript">
$(document).ready(function(){
//alert("jquery alert works")
});
</script>
<p>
this is html test
</p>
<p>
<input type="text" id="txtboxTest" />
</p>
<p>
<button id="btnTest">click me</button>
</p>
<script type="text/javascript"> $(document).ready(function(){
$('#btnTest').click(function(){
$('#txtboxTest').val('jquery click works!');
})
}); </script>
<p>
<img class="imgstyle" src="img/glyphicons-halflings.png" />
</p>
</body>
</html>
以下是 site.css
body { background-color: #0000ff }
.imgstyle {border:2px solid #00ffff;background-color: #ffffff}
而 jquery 及 png 圖檔還請自行準備。執行後,以 IE 來看。
下圖是用 chrome 來開。
這樣的程度,應該可以處理小小應用程式的需求,使用 SPA (Single Page Application) 的觀念來做,也就不需要多個 html 檔。若有其他需要,只需新增 route 及 controller 即可。
但是,這樣的程式,拿給我爸這種老人家,要先點一個執行檔,再開瀏覽器,然後輸入網址,他一定會嫌。在黑大的文章[2]底下有個留言說:
# GATTACA said on 04 June, 2013 10:44 PM
SignalR也有SelfHost可用啊,
ip過濾也可以只限自己127.0.0.1連線,
我現在所有單機版的操作介面都只剩下一個填滿的WebBrowser,啟動後再去讀Html檔案進來;
美工依照原本的WinForm排版Html順便美化,
程式設計師再去填SignalR JavaScript Client與Knockout ViewModel,
如果有用到COM元件,其callback事件處理後透過SignalR更新到Html上,也沒有背景執行緒更新主執行緒WinForm畫面的問題;
雖然是較極端的做法,但是可以擺脫微軟悲情的UI更迭(MFC->WinForm->WPF...),
避免程式設計師將業務邏輯綁死到UI元件上,
Html美工又很好找,
可說是利大於弊啊
對啊,這樣就會跟 app.js 一樣,看起來像是原生桌面程式有什麼不好的呢?
原來的程式執行起來長這樣,看來就想把它關掉。
現在,請在 Form1 加上一個 WebBrowser。然後在 httpserver.OpenAsync.Wait() 這一句的後面,加上
WebBrowser1.Navigate(http://localhost:32767)
在程式裡調好 Form1 寬度及寬度。接著在 WebBrowser1_DocumentTitleChanged 加上
Private Sub WebBrowser1_DocumentTitleChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles WebBrowser1.DocumentTitleChanged
Me.Text = WebBrowser1.DocumentTitle
End Sub
執行程式就得到:
嗯,我現在感覺滿意耶。
我猜想這概念已經存在這麼久,.NET 的社群裡也許已經有類似的程式。
我這隻程式應該只是個幼稚園。
如果想要程式的話,我最近會整理放上 github 讓大家下載來用。
更新,程式在此:https://github.com/rickyteng/washapp
現在要說有什麼缺點的話,應該就是想把 webbrowser 換成 chrome 或 firefox 的引擎,html5 支援比較高。還有用 vb.net 寫 web api 的 route 及 對應的 controller,好像不如 c# 的簡易,c# 可以用 Get 當 function 名字,看起來若不特別指定,action 就是呼叫 Get(),vb.net 的我找不到對應的寫法,所以寫起來有點醜醜的。
終於用三天把兩個月來研究的「用 web 技術寫桌面程式」,的主題弄出一個段落。雖然,其實是 windows 桌面,因為使用者都在 windows 上,我技術上最後也跨不出 windows 平台。接下來就該寫出些有用的東西吧?
參考:
1. https://github.com/darrelmiller/HypermediaApiSite
2. http://blog2.darkthread.net/post-2013-06-04-self-host-web-api.aspx