顯示具有 dotnet 標籤的文章。 顯示所有文章
顯示具有 dotnet 標籤的文章。 顯示所有文章

2013年9月15日 星期日

[named pipe] .Net 的 named pipe,堪用的第一步。Client 端

經過 Server 端程式的字很多轟炸之後,再來看 Client 端程式會覺得很開心。不但 thread 變少,要考慮的狀況也少很多。復習一下,pipe name 與 pipe direction 的關係。

image

看圖可以比較清楚的知道,在 Client 端,<pipe name>_1 是 Out,屆時要連接的是 StreamWriter,而 <pipe name>_2 是 In,屆時要連接 StreamReader。正好跟 Server 端程式反過來。由此可以注意到,所謂的 In, Out 是對本身程式而言,而不是對 Pipe 的方向。同一個 pipe,對於 pipe 兩端的程式來說,in,out 就是剛好相反。所以為了自己也為了別人好,就不要把 pipe 取名 xxx_in 或 xxx_out。要是這樣取名字一定會害死人的。

接下來介紹與 server 端連接的函式。Client 端使用的是 NamedPipeClientStream 這個物件。因為 windows 的 pipe 是可以跨電腦的,所以 client 端的 pipe 在呼叫建構函式的時候,是要清楚告知是哪台電腦的 pipe。在這裡選擇三個參數的建構函式。第一個是 host name,輸入 "." 是告知使用本機。在實例化後,只要呼叫 Connect 即可與 server 端連接。在這裡一樣在處理 in 這個 pipe 需要用一個 thread 來呼叫 SubHandleRead。

Sub ConnectToServer()
    Dim pipename1 As String = TextBox1.Text & "_1"
    Dim pipename2 As String = TextBox1.Text & "_2"
    PipeClientIn = New NamedPipeClientStream(".", pipename2, PipeDirection.In)
    PipeClientOut = New NamedPipeClientStream(".", pipename1, PipeDirection.Out)

    Try
        UpdateConnectionState(ConnectionState.Waiting)
        PipeClientIn.Connect()
        PipeClientOut.Connect()
        ThreadIn = New Thread(AddressOf SubHandleRead)
        ThreadIn.Start()
        UpdateConnectionState(ConnectionState.Connected)
    Catch ex As Exception
        UpdateConnectionState(ConnectionState.Disconnected)
        PipeClientIn = Nothing
        PipeClientOut = Nothing
    End Try
End Sub

因為在 PipeClientIn 斷開之後,不需要自動重連,因此 SubHandleRead 非常簡單,斷開就離開。

Sub SubHandleRead()
    Dim RStream As StreamReader
    RStream = New StreamReader(PipeClientIn)
    While True
        Dim ReadStringLine As String = RStream.ReadLine
        If ReadStringLine Is Nothing Then
            Exit While
        End If
        UpdateTextBox2State(ReadStringLine)
    End While
    UpdateConnectionState(ConnectionState.Disconnected)
    CleanPipeClient()
End Sub

斷開的情境有兩個。一個是 Client 主動斷開,一個是 Server 主動斷開。

在 Server 主動斷開的情況下,SubHandleRead 裡 RStream.ReadLine 方法會傳回 Nothing,接下來離開迴圈。在更新完 GUI 的資訊後,最好把 PipeClientIn 及 PipeClientOut 都 Close 確保有釋放資源。SubHandleRead 離開後 ThreadIn 也就結束了。

在 Client 主動斷開的情況下,是由 PipeClientIn 執行 Close 方法,接下來 SubHandleRead 裡 RStream.ReadLine 方法會傳回 Nothing,然後離開迴圈。按照道理說,PipeClientIn 及 PipeClientOut 都執行過 Close 了,所以可不用執行 CleanPipeClient()。但因為執行也無妨。同時,離開了 SubHandleRead 後,ThreadIn 也停了,所以可放心不會有問題。

傳送資料的方式很簡單,只要在畫面下方的文字框,輸入文字,按下 Send 鈕就可以了。

image

Server 端程式跟 Client 端程式的寫法都一樣:

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
    Dim WStream As StreamWriter
    WStream = New StreamWriter(PipeServerOut)
    Try
        WStream.WriteLine(TextBox3.Text)
        TextBox3.Text = ""
        WStream.Flush()
    Catch ex As Exception
        CleanPipeServer()
        CleanThread()
        UpdateConnectionState(ConnectionState.Disconnected)
    End Try
End Sub

到此,基本的 Server 端程式與 Client 端程式的行為都已跟需求一樣。暫時休息一下。

 

參考:

http://msdn.microsoft.com/en-us/library/system.io.pipes.namedpipeserverstream.aspx

[named pipe] .Net 的 named pipe,堪用的第一步。Server 端

首先要先定義 pipe name 與 pipe in 及 out 的關係。

image

Server 與 Client 之間有兩條 pipe,所以會需要兩個名字,然而,為方便使用者輸入的簡化,當使用者輸入了<pipe name> 之後,實際建立的兩條 pipe 的名字是 <pipe name>_1 與 <pipe name>_2。假設使用者輸入的 pipe name 是 test,Server 端會建立 test_1 與 test_2 兩條 pipe 等待連接。

在這兩條 pipe 裡,Server 端以<pipe name>_1 為輸入端,<pipe name>_2 為輸出端。.Net 為我們準備好的 Server 端物件是 NamedPipeServerStream。設定 PipeServer 的方式僅需給予 pipe 的名字及方向。

當使用者輸入 pipe name 之後,按下 Open 鈕,會執行以下 sub:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    If TextBox1.Text <> "" Then
        IWantStop = False
        ServerSetup()
    End If
End Sub

Sub ServerSetup()
    Dim pipename1 As String = TextBox1.Text & "_1"
    Dim pipename2 As String = TextBox1.Text & "_2"
    PipeServerIn = New NamedPipeServerStream(pipename1, PipeDirection.In)
    PipeServerOut = New NamedPipeServerStream(pipename2, PipeDirection.Out)
    ThreadWaitConnectionIn = New Thread(AddressOf SubWaitConnectionIn)
    ThreadWaitConnectionOut = New Thread(AddressOf SubWaitConnectionOut)
    ThreadWaitConnectionIn.Start()
    ThreadWaitConnectionOut.Start()
    UpdateConnectionState(ConnectionState.Waiting)
End Sub

ServerSetup 前面 4 行就是在處理組合 pipe name 及設定 PipeServer。原本接下來應該是呼叫 WaitForConnection 等待連接,因為此呼叫會 block,所以另外用 thread 來做呼叫的處理。對於 PipeServerOut 的 WaitForConnection 比較單純,我僅使用一個 sub 來處理,在連接來時,若 PipeServerIn 也連接上時,才更新 GUI 上的狀況說明。

Sub SubWaitConnectionOut()
    PipeServerOut.WaitForConnection()
    If PipeServerIn.IsConnected Then
        UpdateConnectionState(ConnectionState.Connected)
    End If
End Sub

在 PipeServerOut 實例化後,執行以下動作,使用另一個 thread 來等待:

ThreadWaitConnectionOut = New Thread(AddressOf SubWaitConnectionOut)
ThreadWaitConnectionOut.Start()

在接收端 PipeServerIn 這個物件,在被連接後,要開始處理接收輸入的動作。然後,處理接收這件事,也要用一個 thread 來處理。於是 PipeServerIn 的 WaitForConnection 處理 sub 如下:

Sub SubWaitConnectionIn()
    PipeServerIn.WaitForConnection()
    ThreadIn = New Thread(AddressOf SubHandleRead)
    ThreadIn.Start()
    If PipeServerOut.IsConnected Then
        UpdateConnectionState(ConnectionState.Connected)
    End If
End Sub

在處理輸入這個 SubHandleRead,有一半是真的處理輸入,另一半是處理 PipeServer 重設的工作。不小心,又用了專用的 thread 來做事。因為 SubHandleRead 被 ThreadIn 所使用,在做重設定時 ThreadIn 必須先結束才能重設。我是不得已才這麼做才又多一個 thread 來呼叫 ReSetUp 的。

Sub SubHandleRead()
    Dim RStream As StreamReader
    RStream = New StreamReader(PipeServerIn)
    While True
        Dim ReadStringLine As String = RStream.ReadLine
        If ReadStringLine Is Nothing Then
            Exit While
        End If
        UpdateTextBox2State(ReadStringLine)
    End While
    UpdateConnectionState(ConnectionState.Disconnected)
    CleanPipeServer()
    If IWantStop = False Then
        If Not ThreadReSetup Is Nothing Then
            If ThreadReSetup.IsAlive Then
                ThreadReSetup.Abort()
            End If
        End If
        ThreadReSetup = New Thread(AddressOf ReSetup)
        ThreadReSetup.Start()
    End If
    CleanThread()
End Sub

Sub ReSetup()
    Thread.Sleep(500)
    ServerSetup()
End Sub

在處理輸入這一半,我使用了非常非常偷懶的 ReadLine 方法,在 StreamReader 的幫助下,只要讀到換行符號,就會從 ReadLine 方法得到資料,該資料不含換行符號。如果,Client 端的連接斷掉了,ReadLine 得到的是 Nothing。所以,得到 Nothing 的時候,就要離開處理迴圈。假如,Client 斷開的原因是故意的,那就不直接重建 Server。但,Client 斷開的原因是不小心的, IWantStop 會是 False,此時要開始執行重建 Server 的動作,回到等待連接的狀態。

而什麼時候是故意斷開的呢?就是按下 Close 鈕的時候:

Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
    IWantStop = True
    CleanPipeServer()
    CleanThread()
    UpdateConnectionState(ConnectionState.Disconnected)
End Sub

在清理 Thread 這件事,我用了很簡單的方法,就是用力地 Abort!為了保證每個 thread 一定都會執行 Abort,我用 Try Catch 把每行執行都包起來,就減少了一些 thread 物件是不是有實例化的檢查。

Sub CleanThread()
    Try
        ThreadWaitConnectionIn.Abort()
    Catch ex As Exception

    End Try
    Try
        ThreadWaitConnectionOut.Abort()
    Catch ex As Exception

    End Try
    Try
        ThreadIn.Abort()
    Catch ex As Exception

    End Try
    ThreadWaitConnectionIn = Nothing
    ThreadWaitConnectionOut = Nothing
    ThreadIn = Nothing
End Sub

然而在清理 PipeServerIn、PipeServerOut 這兩個物件時,比較複雜一點點。當有 Client 端連接上時,讓 PipeServerIn 直接呼叫 Close 方法,就可以斷開。雖然我很直覺地試過要先呼叫 Disconnect,結果就是 block 住,反而無法動彈。PipeServerOut 則需要呼叫 Disconnect,它可以保證所有寫出的字都被讀取之後才斷開。

另一個流程是,PipeServerIn、PipeServerOut 沒有連接上,也就是正在等待連接,而 Server 程式想要關掉,此時 WaitForConnection 卻無法取消!因為 WaitForConnection 的無法取消,導致 thread 的資源沒有釋放乾淨。為了要離開 WaitForConnection,網路上有人提出一個解法,就是自己連上馬上斷掉。而我也採用了這個解法。準備了 PipeClientIn、PipeClientOut 連接,然後 Close。

Sub CleanPipeServer()
    Try
        If PipeServerIn.IsConnected Then
            'PipeServerIn.Disconnect() ' no need
            PipeServerIn.Close()
        Else
            PipeServerIn.Close() 'It Needs to close first. Client_Close_First will need this line.
            Dim pipename1 As String = TextBox1.Text & "_1"
            Dim PipeClientOut = New NamedPipeClientStream(".", pipename1, PipeDirection.Out)
            PipeClientOut.Connect(500)
            PipeClientOut.Close()
            PipeClientOut = Nothing
        End If
    Catch ex As Exception

    End Try
    Try
        If PipeServerOut.IsConnected Then
            PipeServerOut.Disconnect()
            PipeServerOut.Close()
        Else
            Dim pipename2 As String = TextBox1.Text & "_2"
            Dim PipeClientIn = New NamedPipeClientStream(".", pipename2, PipeDirection.In)
            PipeClientIn.Connect(500)
            PipeClientIn.Close()
            PipeClientIn = Nothing
        End If
    Catch ex As Exception

    End Try
    PipeServerIn = Nothing
    PipeServerOut = Nothing
End Sub

到這裡,Server 端程式就說明完畢了。

[named pipe] .Net 的 named pipe,堪用的第一步。

NamedPipeServerStream 這個類別從 .Net 3.5 開始提供。使用者可以 io stream 的概念來操作資料的傳送。與它相對應的是 NamedPipeClientStream。由這兩個類別可以完成 named pipe 的範例。

在 msdn 的範例中,NamedPipeServerStream 是首先被啟用,以等待另一端來連接。程式宣告了一個 NamedPipeServerStream,名字叫 testpipe,pipe 的方向是 out。接下來便是等待對方來連接。這時,才是 NamedPipeClientStream 要來連接的時機。在 Client 連接之後,Server 只是寫一行字就準備關掉了。而 Client 也是讀一行字就準備關掉了。這樣的情境(Sceanrio),可以示範,但在實務上是不能用的。目前教育大概也是這樣吧,學校以為把東西教了,學生就可以用來工作了,學生也以為是這樣,公司也以為是這樣。結果,就整件事來說,這只是半套,另外半套得要有人教。但是全部的人都以為學生學了全套,到了公司發現不對,就怪學生或是學校,這情況就是亂象。雖然另外半套簡單到死,公司再不願意教的,就剩補習班要教了。雖然大家嫌補習班,證照班教的太基本、太無聊、太簡單。啊~套一句我最新的名言:「你不會的那一點,就是重點」。我離題了就此打住,希望有教學生的人可以幫幫學生們,不要被大題目嚇傻了,不要被大老闆嚇傻了。台灣的年輕人能力不輸人的。

接下來我這裡嘗試比較一般會使用的情境,會有幾個要求:

  1. Server 端要能等待連線,在 client 斷掉後回到等待下一次連線。
  2. Server 端要能傳送資料,也要能接收資料。Client 也是。
  3. 使用 GUI (也就是 windows form 程式)顯示接收到的訊息,以及輸入要傳送的訊息。

因為以上的要求,程式就變得複雜一點:

  1. 因為 .WaitForConnection 呼叫後會 block 住,等到 client 連接才回來,為避免 GUI 卡主,因此選擇用 thread 來處理。
  2. 同 1 的理由,不論 Server 或 Client 的 ReadLine 也會 block 住,也選擇用 thread 來處理。
  3. 為了第二點,Server 端及 Client 端中間,開兩個 pipe,一個 in,一個 out。

你會發現,不過多了三個要求,程式碼多了一大堆!例如,什麼叫做 client斷掉,怎麼判斷?MSDN 的範例中沒看到。收兩行以上的程式怎麼寫?MSDN 的範例中沒看到。多看別人寫的程式碼,尤其是 open source 的專案,是學寫程式的好範本!這讓我覺得以前的電腦書比較好,會一步步教,很仔細。但現在基礎的書大多跳掉細節只講觀念,反而又無法做出一個完整的東西。奇怪這一篇很常在抱怨哩…。

好,那先介紹 GUI 畫面,下次再看程式碼吧…。

image image

正常的操作情境(Scenario)是:

  1. Server 程式啟動,Label1 位置會是紅字底的文字 Disconnected image 說明程式是 Disconnected 狀態。
  2. 在 Server 畫面的 Open 右方的文字框,輸入 Pipe Name。
  3. 按下 Open 鈕後,Label1 位置會是黃字底的文字 Waiting image 。
  4. Client 程式啟動,Label1 位置會是紅字底的文字 Disconnected image
  5. 在 Client 畫面的 Open 右方的文字框,輸入 Pipe Name。
  6. 按下 Open 鈕後,兩隻程式的 Label1 位置會是綠字底的文字 Connected image
  7. 接下來就可以在畫面最下方的方塊輸入文字,按下 Send 鈕,另一方就會收到文字了。
  8. 按下 Client 畫面的 Close 鈕,Client 程式會變成 Disconnected 狀態。而 Server 程式會變成 Waiting 狀態。Client 程式關掉。
  9. 按下 Server 畫面的 Close 鈕,Server 程式會變成 Disconnected 狀態。Server 程式關掉。

2013年7月10日 星期三

[webapi]微軟 ASP.NET Web API Self Host 的應用(2)

(本篇程式是用 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 來看。

image

下圖是用 chrome 來開。

image

這樣的程度,應該可以處理小小應用程式的需求,使用 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 一樣,看起來像是原生桌面程式有什麼不好的呢?

原來的程式執行起來長這樣,看來就想把它關掉。

image

現在,請在 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

執行程式就得到:

image

嗯,我現在感覺滿意耶。

我猜想這概念已經存在這麼久,.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

[webapi]微軟 ASP.NET Web API Self Host 的應用(1)

(本篇程式是用 vb.net 寫成)

因為瀏覽器的能力大增,現在許多 GUI 的彩色糖果(新功能)都在瀏覽器上簡單易用。憑藉著 javascript 及瀏覽器就能做到原生桌面程式的事,而且不用在 c++ 裡打滾,也不用在意平台,真是一個全新的世界的來臨。

最早,mozilla 就試著使用 XUL 想要進入桌面程式,Google 也趁著 chrome 已融入大家的生活中的時機,推出 Chrome App。XUL 算是只使用前端瀏覽器引擎配合自有原生執行程式,Chrome App 則是採取與chrome 共生的執行環境來使用作業系統資源。接下來,有其他的人,想出自帶 server 端的方式來實現使用瀏覽器當 GUI 的解決方法,例如 app.js,利用 node.js 來當做程式的後端。

在黑暗執行緒看到的兩篇文章[1], [2]。才知道微軟出了個套件 ASP.NET Web API Self Host,可以當做 server 端的程式,可寫成 Console 程式,也可以寫成 WinForm。而這套件正好可以與 Chrome App、app.js 所提供的後端支援的功能相同。於是,我也來試試看是否能做出些什麼 app 來…。因為黑大已經示範了寫成 console 的程式,我就來試試 WinForm 的程式。黑大用的是 c#,我就來試試 vb.net 吧。

我還是一步步把畫面剪下來,免得以後我自己忘記,所以落落長。初學者才需要這樣。

首先,開啟一個 WinForm 的專案。

image

先存檔,然後開啟 NuGet。安裝 ASP.NET Web API Self Host。

image

以下是看到相依套件的授權。

image

實際安裝的套件如下:

image

接下來,安排程式碼。首先,在 Form1.vb 的最前面,加入 imports: (這樣可以少打幾個字。)

Imports System.Web.Http
Imports System.Web.Http.SelfHost

然後,加入兩個 form1 的變數,如下:

Dim config As HttpSelfHostConfiguration
Dim httpserver As HttpSelfHostServer

然後,加入一個 sub,是啟動 host 的部份,如下:

Sub hostinit()
    config = New HttpSelfHostConfiguration("http://localhost:32767")

    config.Routes.MapHttpRoute("API", "{controller}/{action}/{id}", New With {.id = RouteParameter.Optional}) ' vs2010 just can not IntelliSense....

    httpserver = New HttpSelfHostServer(config)

    'http://blog2.darkthread.net/post-2013-06-04-self-host-web-api.aspx
    httpserver.OpenAsync.Wait()
End Sub

選擇 Form1 的 New 事件,加入 hostinit(),使得 Form1 一出現就會接受來自 localhost:32767 的 request

Public Sub New()

     ' 此為設計工具所需的呼叫。
     InitializeComponent()

     ' 在 InitializeComponent() 呼叫之後加入任何初始設定。
     hostinit()
End Sub

在 Form1 的結束時,也要關掉 32767 的監聽。

Protected Overrides Sub Finalize()
    httpserver.CloseAsync.Wait()
    MyBase.Finalize()
End Sub

最後,也要加上 Controller 的物件,BlahController 來接受 request。(不要放在 Form1 裡面。我是放在另一個檔案 BlahController.vb)

Imports System.Web.Http
Public Class BlahController
    Inherits ApiController
    <HttpGet()> Public Function getDate() As String
        Return System.DateTime.Today.ToString("yyyy/MM/dd")
    End Function
End Class

完成了程式碼的安排後,還要記得黑大提醒的一件事,在 Windows 7、Windows 2008 這些作業系統開發時,因為權限控管較嚴,開 port 監聽需要較高權限來開放。我選擇了用 netsh 這個方法來開放權限。先開啟命令列(記得要用 administrator 權限開啟命令列)

netsh http add urlacl url=http://+:32767/ user=machine\username

記得上面的 machine 及 username 要換成你的電腦名稱及當下登入的使用者名稱。

接下來就可以試著啟動程式,開啟瀏覽器,輸入:

http://localhost:32767/Blah/getDate

如果瀏覽器的是 IE,會是這樣的結果:

image

按下開啟,內容是:

image

如果是用 chrome 的話,則會是:

image

 

出現了兩個不同的回傳內容。

 

接下來開始跟黑大不一樣了。

 

我們要開始問,如果我們要能夠服務 .html, .js, .css, .jpg, .png, .gif ... 等等一般的 web server 可以服務的檔案,該怎麼辦?

 

參考

1. 黑暗執行緒,不用IIS也能執行ASP.NET Web API,http://blog2.darkthread.net/post-2013-06-04-self-host-web-api.aspx
2. 黑暗執行緒,【範例】呼叫Self-Hosted ASP.NET Web API,http://blog2.darkthread.net/post-2013-06-05-post-to-web-api.aspx

3. http://www.dotnetcurry.com/ShowArticle.aspx?ID=896

4. http://stackoverflow.com/questions/17450846/is-it-possible-to-serve-a-web-page-from-a-self-hosted-web-api-in-a-windows-servi

5. https://github.com/darrelmiller/HypermediaApiSite

6. GetManifestResourceStream http://msdn.microsoft.com/en-us/library/System.Reflection.Assembly.aspx

7. return 405 while get .js http://stackoverflow.com/questions/17170277/jquery-get-returns-404-undefined-from-self-hosting-web-api

8. http://www.codeproject.com/Articles/455565/How-to-access-Manifest-Embedded-resources-from-an


類似 app.js 的平台,我試過的有:

google chrome packaged apps

appjs

還沒試過的平台:

node-webkit https://github.com/rogerwang/node-webkit

xul https://developer.mozilla.org/en/docs/XUL

Appcelerator Titanium http://en.wikipedia.org/wiki/Appcelerator_Titanium

Ember.js http://emberjs.com/

NACL https://developers.google.com/native-client/

 

VB.NET 的 IntelliSense 看不到 .Routes.MapHttpRoute 但 CS 可以。又多了一個換 CS 的理由。(逃)
http://forums.asp.net/t/1819836.aspx/1

2013年7月7日 星期日

[nuget]我與 NuGet 初見面

大名鼎鼎的 NuGet,是 Microsoft 開發平台(包含 .NET)的 package manager。因為 Microsoft 開發有太多方便的套件可用,以往都是開發者自行在網路上下載、安裝,難免會有安裝維護上的困擾。而現在透 NuGet,不但方便一般開發者,有統一的搜尋、下載、安裝介面,也方便套件開發者的散佈、維護。

我直到現在才裝來用,因為…我一直使用 VS2008。在沒有 VS2010 (含)以上的狀況,是有提供指令方式。

最近換了 VS2010,按照 http://docs.nuget.org/docs/start-here/installing-nuget 的說明把 NuGet 裝上。

從 工具 > 擴充管理員 把擴充管理員打開,在右上角有搜尋框,輸入 NuGet,就可以選擇 NuGet 套件管理員來安裝。

安裝完會要你重開一次 VS2010。

接下來呢?

假設你的 Window Form 程式 需要一個 json 套件叫做 Json.NET。先建立一個新的專案,在方案總管那裡點右鍵,就會看到「管理 NuGet 套件」。執行前要先確定專案檔已經儲存。

image

等待一些時間,會出現主畫面,搜尋、選擇 Json.NET

image

按安裝就解決了 Json.NET 的下載及安裝了。酷!

以上,是使用 VS2010 Premium 版。

我另外使用 VS2010 C# Express Edition,但是在 Extension Manager 卻找不到 NuGet Package Manager。會不會是有什麼設定要改的呢?

如果有人也是 Express Edition,可以使用 NuGet 的請教教我,感恩。

image

2012年11月22日 星期四

[aspx]HttpUtility.UrlDecode 與 Server.UrlDecode 真的不一樣

我以為我經過上次的錯誤,中文亂碼可以不會再出現在我的生活中。

直到我的膝蓋中了一箭。 (這裡)

找了一陣子,原來有人,以前的程式碼用了Server.UrlDecode

HttpUtility.UrlDecode 與 Server.UrlDecode 真的不一樣。

我寫了以下的程式來證明:

Function WriteOutput(ByVal str As String) As String
    Response.AppendHeader("Content-Type", "text/html;charset=UTF-8")
    Dim bs As Byte() = System.Text.Encoding.UTF8.GetBytes(str)
    Response.ContentEncoding = System.Text.Encoding.UTF8
    Response.OutputStream.Write(bs, 0, bs.Length)
    Return "OK"
End Function
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
    WriteOutput("System.Text.Encoding.Default.EncodingName:" + System.Text.Encoding.Default.EncodingName + "<br/>")
    WriteOutput("System.Text.Encoding.Default.WebName:" + System.Text.Encoding.Default.WebName + "<br/>")
    WriteOutput("HttpUtility.UrlDecode:" + HttpUtility.UrlDecode("%e4%b8%ad") + "<br/>")
    WriteOutput("Server.UrlDecode:" + Server.UrlDecode("%e4%b8%ad") + "<br/>")
End Sub

結果這就是不一樣:

image

附帶說明一下,這個現象不一定會在你的伺服器出現。在我的開發環境,如下。

image

我現在還找不到確切指出設定不同的地方。

我只看到兩個 EncodingName 所出現的字串是不同的。

據網路消息,說 Server.UrlDecode 是用系統編碼。可是我還沒辦法找到如何顯示現在系統編碼。

2012年11月20日 星期二

[aspx]不會用設定,不要怪微軟!(原題:好心的微軟幫了倒忙。中文亂碼解決實錄)

更新:要看最後解法,請直接跳最下面

有史以來解決中文亂碼最長的一次。從我2000年開始,踩進可怕的中文混沌中,
從沒有這麼令人生氣的一次了。

原因就是微軟幫倒忙!微軟好心幫忙但是我還是得告訴它我的 charset。

「絕聖棄智,民利百倍。絕仁棄義,民復孝慈。絕巧棄利,盜賊無有。」--老子

故事是這樣開始的。要求是:
「我需要用 post 方法,傳送一筆記錄的資料,而網頁伺服器接到之後,幫忙下 SQL 插入資料。」

你看多簡單的一句話!結果發生亂碼,沒關係,那我就寫個測試的程式,看是傳過去的字就亂了,還是 SQL 執行完才錯。

好,簡化過的發送端程式如下:

Function SendPutRequest(ByVal keyname As String, ByVal keyvalue As String)
    Dim uristr As String = "http://localhost:1490/WebSite1/poid_fail.aspx"
    Dim req As System.Net.HttpWebRequest = System.Net.HttpWebRequest.Create(uristr)
    req.Method = "POST"

    Dim param As String = "a=put"
    param += "&" & "key" & "=" & keyname
    param += "&" & "value" & "=" & keyvalue
    Dim bs As Byte() = Encoding.UTF8.GetBytes(param)
    req.ContentType = "application/x-www-form-urlencoded"
    req.ContentLength = bs.Length

    Using reqStream As System.IO.Stream = req.GetRequestStream()
        reqStream.Write(bs, 0, bs.Length)
    End Using
    Using wr As System.Net.WebResponse = req.GetResponse()
        Dim rs(wr.ContentLength) As Byte
        Dim expectcount As Integer = wr.ContentLength
        Dim readcount As Integer = 0
        Response.ContentEncoding = System.Text.Encoding.UTF8
        readcount += wr.GetResponseStream().Read(rs, 0, wr.ContentLength)
        Response.Write(Encoding.UTF8.GetChars(rs))
    End Using
    Return "OK"
End Function

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
    SendPutRequest("DataName", "中文測試")
End Sub

發送端,就只是一個假裝用 form 打出資料的 request。其中也很簡單的一個 keyname 跟 keyvalue,傳送至伺服端

而,接收端程式(簡化版)很快就寫好了:

<%@ Page Language="VB" %>

<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.OleDb" %>

<script runat="server">
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
        Dim act As String
        Dim insertSql As String = ""
        Try
            act = Request.Form("a")
            If act = "put" Then
                Dim keyname As String
                keyname = Request.Form("key")
                Dim keyvalue As String
                keyvalue = Request.Form("value")
                insertSql = "insert into [poidata] ($columns$) values ($values$)"
                insertSql = insertSql.Replace("$columns$", keyname).Replace("$values$", keyvalue)
                ExecuteSql(insertSql)
            End If
        Catch ex As Exception
            insertSql = ex.Message
        End Try
        Dim contentbody As String = insertSql
        Response.AppendHeader("Content-Type", "text/json")
        Response.ContentEncoding = System.Text.Encoding.UTF8
        Response.OutputStream.Write(Encoding.UTF8.GetBytes(contentbody), 0, System.Text.Encoding.UTF8.GetByteCount(contentbody))
    End Sub
</script>

在開發環境很正常。發送端與接收端都是在同一個電腦上,沒有問題。

image 

把接收端程式丟到真的伺服器去,就變亂碼!:

image

好。很正常的,因為值有中文,沒有 encode 所以變亂碼,我可以接受。
而我還特別把接收端的程式的 response,以 Encoding.UTF8.GetBytes 轉成 byte,然後用 write byte 方式,這是最保險,照字串byte回傳回來的保險方式。

那我 encode 吧。

把傳送端 encode。

param += "&" & "key" & "=" & HttpUtility.UrlEncode(keyname)

param += "&" & "value" & "=" & HttpUtility.UrlEncode(keyvalue)

結果長一模一樣。奇怪了。

image

那我接收端 decode 看看,結果一樣。

那來檢查兩邊的 default encoding 好了。
把接收端的 default encoding 回傳回來看看。

Dim contentbody As String = insertSql + "<br />" + System.Text.Encoding.Default.EncodingName

在開發環境,看起來是這樣

image

把程式丟到真的伺服器上,嗯,都是 big5 啊!

image

啊,等等,不對勁耶,為什麼真的伺服器不是寫「繁體中文 (Big5)」傳回來,而是用「Chinese Traditional (Big5)」這是搞什麼鬼?

後來,同事看我搞很久,決定傳授我妙方。
原本,在 IIS 的設定,我是使用虛擬目錄的方式,指到網站程式所放的目錄。
而他的經驗是,在 IIS 上,指定用「網路應用程式」
他也幫我弄好了。只是,網路位址會移動,而我,只是改別人的程式,不太想去改位址這種事。
如果,只想研究到這裡的人,就去改成「網路應用程式」試試吧。

因為沒辦法、因為不死心,我決定來試試強迫接到 encode 過的字串為目標,
想確定傳過去的 byte 沒有變動。
所以,接收端先不 decode,就接到什麼回寫什麼看看。
再來,因為傳送端我 encode 一次不夠,我再多 encode 一次看看。

param += "&" & "value" & "=" & HttpUtility.UrlEncode(HttpUtility.UrlEncode(keyvalue))

很賭氣的作法。UrlEncode 這種 encode 法,作兩次 encode,要得回原來的字,就要 decode 兩次。只要我拿到不是亂碼的字串,算清楚encode次數,就知道是不是 byte 被動到了。

image

很好,這是好的開始,這看來像是 encode 一次的字串。
那我接收端,就 decode 一次試試。

keyvalue = HttpUtility.UrlDecode(Request.Form("value"))

image

耶,看來好了。

而實際把組好的 SQL 字串執行下去,資料庫裡,也就正常的中文了。

回頭討論,我的傳送端 encode 兩次,我的接收端 decode 一次。
這代表,有某個地方幫我 decode 一次。而且在我的程式執行之前。
按照我的推斷,那一次的 decode 的時候,參考不知道哪裡的編碼,在那裡字串亂掉了。
明明就看不懂中文,還硬要轉,當然就搞亂了。
這從 default encoding name 可以看得出來,雖然都是 Big5,但是一個用中文表示,一個用英文表示,明白的指出,兩個的執行環境是不同的,而這個執行環境的不同,就會導致亂碼。
而我,用兩次 encode,讓我的字串被自動 decode (也就是第一次,不是我的程式做的) 之後,成為 encode 一次的狀態,這時是 ascii 的字串表示式,用這個字串,讓我的程式來 decode 回 .net 裡面的字串。由於資訊保持完整,所以可以完美地解回 .net 字串。問題才得以解決。
確切幫忙 decode 一次的程式我沒有找到,不過應該不脫離 iis,asp .net 這個範圍。
所以這次幫倒忙就算在微軟的頭上!

我想也許會有高手更了解 IIS 或 asp .net 的可以用更漂亮的方式解決這問題吧?
我的確是不了解所以硬幹啊。

更新:

因為無可避免地會被 decode 一次,所以指定正確 charset 一定是正解。指定的方式即在 request 的 Content-Type 裡。如下:

req.ContentType = "application/x-www-form-urlencoded;charset=UTF-8"

接收端程式,不用需 decode,微軟會按指定編碼幫你搞定。

它就是要幫你解,你只好一定要告訴他怎麼解。

2012年2月13日 星期一

[vbnet]right

有時候,在專案裡,想用 right 卻發生

取得控制項右邊緣和其容器左邊緣之間的距離

那就改用

Microsoft.VisualBasic.Right

也許調整 imports 有用?

2011年11月23日 星期三

[dotnet]webbrowser 做為 UI log 的眉眉角角

一開始的動作

'WebBrowser1 一開始,可以設定 DocumentText 來顯示第一個畫面。
若是想用,WebBrowser1.Document.Write,
因為一開始,WebBrowser1.Document 是 nothing,所以此路不通。
網路上也滿多人提出,先 WebBrowser1.Navigate("about:blank"),
然後用迴圈加上 Application.DoEvents() 等待 'WebBrowser1.Document 長好,
再用 WebBrowser1.Document.Write。

最後我的決定是,準備好一個檔案直讀進來就好。
因為程式是 multithread,保險起見,還是用 while 等 Document 長好。
但我不確定是否真有用,因為一定要用 Application.DoEvents() 讓 WebBrowser 去做事,
當然這時有其他事情發生也沒辦法阻止。

    Sub initUILogComponet()
        WebBrowser1.Navigate("UI.html")
        WebBrowser1.IsWebBrowserContextMenuEnabled = False
        While (True)
            Application.DoEvents()
            If Not WebBrowser1.Document.Body Is Nothing Then
                Exit While
            End If
        End While
    End Sub

更新畫面

要更新 UI 畫面,基本要做的就是檢查 thread 是否回到 form。
這裡用 Document 物件新增新的 tr。
等待之後,取出 Document 的 html 元素的 OuterHtml 再寫進檔案。
接著Navigate。
清除舊資料的方式就是把最舊的那一個元素的 OuterHtml 取出,
把它從 html 元素的 OuterHtml 裡消掉,
再寫進檔案後Navigate。

    Sub UpdateUILog(ByVal inType As EQS_ScreenType, ByVal intxt As String)
        If Me.InvokeRequired Then
            Me.Invoke(New LogDele(AddressOf UpdateUILog), New Object() {inType, intxt})
        Else
            Try
                Dim tr As HtmlElement
                Dim td As HtmlElement
                tr = WebBrowser1.Document.CreateElement("tr")
                td = WebBrowser1.Document.CreateElement("td")
                td.InnerText = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")
                tr.AppendChild(td)
                td = WebBrowser1.Document.CreateElement("td")
                td.InnerText = intxt
                tr.AppendChild(td)
                Select Case inType
                    Case ERROR
                        tr.SetAttribute("class", "error")
                    Case M_INFO
                        tr.SetAttribute("class", "info")
                    Case M_MES
                End Select
                WebBrowser1.Document.Body.FirstChild.FirstChild.FirstChild.InsertAdjacentElement(HtmlElementInsertionOrientation.AfterEnd, tr)
                While (True)
                    Application.DoEvents()
                    If Not WebBrowser1.Document.Body Is Nothing Then
                        Exit While
                    End If
                End While
                Dim n_html = "<!doctype html>" & WebBrowser1.Document.GetElementsByTagName("html").Item(0).OuterHtml
                Dim n_sw As System.IO.StreamWriter = System.IO.File.CreateText("UI.html")
                n_sw.Write(n_html)
                n_sw.Close()
                WebBrowser1.Navigate("UI.html")
                Dim v As Integer
                v = 5
                If WebBrowser1.Document.Body.FirstChild.FirstChild.Children.Count > v Then
                    Dim t As String = WebBrowser1.Document.Body.FirstChild.FirstChild.Children(v).OuterHtml
                    Dim new_html As String = WebBrowser1.Document.GetElementsByTagName("html").Item(0).OuterHtml
                    t = t.Replace(vbNewLine, "")
                    new_html = new_html.Replace(vbNewLine, "")
                    new_html = new_html.Replace(t, "")
                    new_html = "<!doctype html>" & new_html

                    Dim sw As System.IO.StreamWriter = System.IO.File.CreateText("UI.html")
                    sw.Write(new_html)
                    sw.Close()
                    WebBrowser1.Navigate("UI.html")
                End If
            Catch ex As Exception

            End Try
        End If
    End Sub

 

保證一定得到更新後內容的方式

在 DocumentCompleted 事件去拿一定可以。
要注意的是,當初設定 DocumentText 的話,更新的內容會在 DocumentText。
而使用 Document 物件操作的,更新的內容要從 Document 物件拿。

    Private Sub WebBrowser1_DocumentCompleted(ByVal sender As Object, ByVal e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs) Handles WebBrowser1.DocumentCompleted
        If firstContextSetComplete = False Then
            firstContextSetComplete = True
            'UpdateUILog(FACETEA.EQS_ScreenType.M_INFO, "StartUp")
        End If
        Dim control As Control = Me.ActiveControl
        WebBrowser1.Focus()
        control.Focus()
    End Sub

用jQuery清除舊資料

因為 WebBrowser 控制項沒辦法支援 addEventHandler,所以我的腦筋動到 setInterval。
這個方法可行,但是無法存檔。
而且考量到使用 class 控制外觀非得存檔再Navigate,在兩個因素考量下,
所以清舊資料的事只能交給主程式本身來做。

[dotnet]WebBrowser控制項對於 tr 的 border 顯示

在WebBrowser控制項裡,

tr { border-bottom: 1px solid #000000;}

不會顯示線。

在 ie9 的話,上面那行再加

table { border-collapse:collapse;}

就會顯示線。

有人提到

tr td{ border-bottom: 1px solid #000000; }

可以解決問題。但是兩個 td 之間可能會有斷線。

[dotnet]WebBrowser控制項不可用script偵測新增節點

經過研究及試驗,在 ie9 以下這行可以成功

document.addEventListener("DOMNodeInserted",function(){alert('change')});

但是在WebBrowser控制項就失敗。

[dotnet]WebBrowser控制項可吃 css 及 script

經過研究及實驗,可正常使用一般在 html 中加入 css 及 script 的標記,
也可以引用外部 css 檔案及 script 檔案。

用 WebBrowser.Document 新增新的 element,有幾個現象:

  1. 新的 element 並不會套上 class 的設定。
  2. 新的 element 會直接套用 style 的設定。

要解決第1點,可以先寫檔,再 navigate,就會套用設定。
所以一氣呵成的流程就是:

  1. WebBrowser.Document 新增新的 element
  2. 確定 WebBrowser1.Document.GetElementsByTagName("html").Item(0).OuterHtml 已經是新的內容
  3. 開檔寫檔
  4. navigate

[dotnet].NET Framework 還是有分 32 和 64

這個算是記憶刷新,並留下資料。

http://msdn.microsoft.com/zh-tw/windows/gg537087

[dotnet]讓WebBrowser預設右鍵menu不出現

因為打算使用 WebBrowser 這個控制項當做 UI 上顯示 log 的控制項,
這是一個快速筆記。

為了讓右鍵不顯示,只要一行code的設定:

WebBrowser1.IsWebBrowserContextMenuEnabled = False

http://evabow.blogspot.com/2011/07/webbrowsermenu.html

[dotnet]WebBrowser1.DocumentText

因為打算使用 WebBrowser 這個控制項當做 UI 上顯示 log 的控制項,
這是一個快速筆記。

http://msdn.microsoft.com/zh-tw/library/system.windows.forms.webbrowser.documenttext(v=vs.85).aspx 

即使已要求另一份文件,這個屬性仍會包含目前文件的文字。如果設定這個屬性的值,然後立即重新擷取這個值,則在 WebBrowser 控制項尚未有時間載入新內容時,所擷取到的值可能會與設定的值不同。您可以在 DocumentCompleted 事件處理常式中擷取新的值。

有人說,用doevents等一下可以

        While (True)
            Application.DoEvents()
            If Not WebBrowser1.Document.Body Is Nothing Then
                Exit While
            End If
        End While

2011年8月15日 星期一

[VB6toVB.NET2010]

公司要把 VB6 的東西轉成 .NET 2010
上網找了一下,答案是不行直接轉。
http://www.google.com.tw/url?sa=t&source=web&cd=1&ved=0CBkQFjAA&url=http%3A%2F%2Fsocial.msdn.microsoft.com%2FForums%2Fzh-CN%2F2212%2Fthread%2Fd444bbea-d23d-48d2-b005-49d057405523%2F&ei=QeRJTtmaE4jniALamcGiBw&usg=AFQjCNFp3e09h6ajRzYMXJ7GCMGyEHRjwQ&sig2=nnlC759IJECozFq013x0aA

這一篇講得深得我心,尤其是最後一PO,真是爽啊。

所以該怎辦呢?
VS2003可以把VB6轉成.NET 的 project。
然後再用 VS2010 開啟轉換完的 project,再轉一次。
然後,form.vb 的部份只能看見UI程式碼,看不見UI外觀,哈哈!
執行一下失敗,需要許多舊時代的dll。
結論是還是要重寫一次。

恭喜!

2010年11月4日 星期四

[aspnet]按下按鈕後,出現下載視窗

這是常常看到的功能,但是我就是花了一、兩個小時在試它。

protected void Button1_Click(object sender, EventArgs e)
{
    Response.ContentType = "text/xml";
    Response.AppendHeader("Content-Disposition", "attachment; filename=newfile.xml");
    Response.TransmitFile(Server.MapPath(@"App_Data\" + "newfile.xml"));
    Response.End();
}

以上的程式是把網站中,存在的檔案送出去。

protected void Button1_Click(object sender, EventArgs e)
{
    Response.ContentType = "text/xml";
    Response.AppendHeader("Content-Disposition", "attachment; filename=newfile.xml");
    Response.BinaryWrite(System.Text.Encoding.UTF8.GetBytes("text test"));
    Response.End();
}

而以上的程式是把網站中,已知的字串用UTF-8編碼當做XML檔案送出去。

兩個程式裡,只要走到Response.End();就離開了。在後面寫什麼程式都執行不到。

2010年9月30日 星期四

[aspnet]自行用程式讀取 excel 2007 的內容,需要的元件

又發現,在開發環境沒問題的東西,到了測試環境出問題的問題。
讀取 excel 2007 的確還是使用 oledb 來抓資料,用到的 driver,是

Microsoft.ACE.OLEDB.12.0

可是裝上 Visual Studio 2008 的開發環境可以,沒裝的測試環境就不行,那不是要我們把 VS2008 裝上去嗎?真是裝笑維嗎?

還好只要去找微軟把 AccessDatabaseEngine.exe 裝好就可以了。
謝天謝地

2010年9月15日 星期三

[aspnet]使用access 2007 x86, x64有不同

發生在跟同事整合web service遇到的問題。

嚴格說起來,就是在 OLEDB 底下,使用 Microsoft.ACE.OLEDB.12.0 這個 driver 存取 access file。

在他的電腦上都好好的,到了整合的時候卻出來

"The 'Microsoft.ACE.OLEDB.12.0' provider is not registered on the local machine"

當下決定,把 mdb 改成舊格式,driver 改成 Jet。

從此不想碰這種問題了。

可是,後來到了客戶那裡安裝程式,他的環境是 windows 2008 R2。居然會出現:

"The 'Microsoft.Jet.OLEDB.4.0' provider is not registered on the local machine"

這簡直是要我的命嘛!

就算裝了AccessDatabaseEngine.exe 也沒用,事情沒有這麼單純。

要感謝 google 幫忙,一下子就找到原因。那就是AccessDatabaseEngine.exe 現在只有 32bit 的版本。我們的OS都是32bit的,所以到了整合的時候才發生這個問題。

http://ybbest.wordpress.com/2009/07/22/the-microsoft-ace-oledb-12-0-provider-is-not-registered-on-the-local-machine/

改編譯方法之外變成x86,還是沒有用。還要更改 iis 的進階設定,讓 iis 能夠執行 32bit 程式才行,否則還是白談。

謝謝大家

2010/11/3 更新