如果你看到這篇文章時, 或許你會對於這個標題感覺到十分的詭異。是的, 如果我不是遇到這個詭異的問題, 也不會下這種詭異的標題...
如果你看到這篇文章時, 或許你會對於這個標題感覺到十分的詭異。是的, 如果我不是遇到這個詭異的問題, 也不會下這種詭異的標題。
對於一個寫了很久的 ASP.NET 網頁的人來說, 我實在沒有想到在 WP7 專案要把自己專案裡的文件以程式讀出來是這麼一件拐來繞去才辦得到的麻煩事。例如, 我在專案的 /Templates 資料夾中放了一個叫做 Exam.XML 的文件, 那麼我要怎麼在程式中把這份文件的內容取出來呢?
在 Web Form 程式裡, 這根本不是問題, 因為專案的資料夾會一併拷貝到發行的目的地, 所以我很容易把它取出來 - 至少不會連在設計時期都取不到。而在 Windows Form 程式裡, 我不需要把文件放在什麼資料夾裡, 因為我大可以把文字內容存放在組件的 Resource 裡面。它也可以非常容易的取出來。
但是很不幸的, 上述兩種方式到了 WP7 (或者說是 Silverlight, 這我還不是很確定) 都通通無效了。雖然我後來已經找到如何讀取資源檔的內容 (請參考「[WP7] 在 Windows Phone 讀取多語系資源的方法及輔助工具」), 但是我已經先朝著建立專案內文件的做法去思考了。
在經過許多徒勞無功的嘗試之後, 我終於找到一個可以取出專案中文件的方法了, 甚至用不到 Reflection :
-
首先, 就像我在上面已經提到過的, 在專案建立一個資料夾 (例如 /Templates), 然後裡面建立一個 XML 文件 (例如 Exam.XML)。
-
接著, 很重要的是, 請在方案總管中選取上述文件 (即 Exam.XML), 然後在屬性視窗中找到「建置動作」這個項目, 並且選取「Resource」(不是「內嵌資源」, 別選錯了), 如下圖:
-
現在已經可以從程式中取得它的值了:
using System.IO;
using System.Resources;
...
string input;
using (Stream stream = App.GetResourceStream(new Uri("/MyProject;component/Templates/Exam.XML", UriKind.Relative)).Stream)
using (StreamReader sr = new StreamReader(stream))
input = sr.ReadToEnd();
程式中 MyProject 是我的專案名稱, 請改成你自己的專案名稱。而那個 URI 字串的確很奇怪, 但並不是我打錯字; 它就是一定得這麼寫才行。
並不是只有文字檔才能這麼做。依照同樣的方式, 其它類型的檔案 (例如圖片) 也都是這麼做的; 當然, 或許程式必須稍為改一下 (如以下範例)。
我把我自己寫的 Helper 程式列在下面, 拷貝回去就可以使用了:
using System;
using System.Net;
using System.IO;
using System.Resources;
namespace Johnny
{
public class REmbeddedFileHelper
{
private const string appName = "MyProject";
public string Folder;
public string Filepath;
public string LastError;
private string path
{
get
{
if (string.IsNullOrEmpty(Folder) || string.IsNullOrEmpty(Filepath))
throw new Exception("ResourceHelper: Must specify Folder and Filepath!");
return string.Format("/{0};component/{1}/{2}", appName, regulatePath(Folder), regulatePath(Filepath));
}
}
/// <summary>
/// Internal utility that removes the beginning "/"
/// </summary>
private string regulatePath(string input)
{
return (input.Trim().StartsWith("/")) ? regulatePath(input.Substring(1)) : input.Trim();
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="folder">The folder where the resource file stays</param>
/// <param name="filepath">The path of the resource file</param>
public EmbeddedFileHelper(string folder, string filepath)
{
Folder = folder;
Filepath = filepath;
}
/// <summary>
/// Constructor
/// </summary>
public EmbeddedFileHelper() { }
/// <summary>
/// Read from the resource specified
/// </summary>
/// <param name="output">The content read</param>
/// <returns>True if successful, or else false</returns>
public bool Read(out string output)
{
try
{
using (Stream stream = App.GetResourceStream(new Uri(path, UriKind.Relative)).Stream)
using (StreamReader sr = new StreamReader(stream))
output = sr.ReadToEnd();
return true;
}
catch (Exception ex)
{
LastError = string.Format("EmbeddedFileHelper.Read() failed. Error: {0}", ex.Message);
output = null;
return false;
}
}
/// <summary>
/// Read from the resource specified
/// </summary>
/// <param name="output">The content read</param>
/// <returns>True if successful, or else false</returns>
public bool Read(out byte[] output)
{
try
{
using (Stream stream = App.GetResourceStream(new Uri(path, UriKind.Relative)).Stream)
{
output = new byte[stream.Length];
stream.Read(output, 0, output.Length);
}
return true;
}
catch (Exception ex)
{
LastError = string.Format("EmbeddedFileHelper.Read() failed. Error: {0}", ex.Message);
output = null;
return false;
}
}
}
}
照理說它既可以讀, 應該也可以寫, 不過在 WP7 環境下想寫入資源檔再包入 XAP 的可能性似乎不是很高的樣子, 所以我就連試驗看看的興趣也沒了。還有, 我額外了 Read() 方法的多型, 所以這個方法已經可以用來讀取文字檔案和二進位檔案 (以 byte[] 型別傳出)。