在 Windows 7 上打開 IIS Manager,無法連線到其他伺服器做管理工作耶!原因無他,就是為了簡化。
在 Windows 7 上的 IIS Manager,當初設計時只想要管理本機而已,故沒有可以連線到其他伺服器的工具。(但工具列卻又有…)
解法:
在 Windows 7 上打開 IIS Manager,無法連線到其他伺服器做管理工作耶!原因無他,就是為了簡化。
在 Windows 7 上的 IIS Manager,當初設計時只想要管理本機而已,故沒有可以連線到其他伺服器的工具。(但工具列卻又有…)
解法:
在 Visual Studio 2010 中,可以針對 Web.config 進行轉換。
那 App.config 呢?好心人很多,現在也可以使用相同語法進行 app.conifg 的轉換。
在 Extension Manager 中找 SlowCheetah - XML Transforms
from http://tw.knowledge.yahoo.com/question/question?qid=1606121207007
特殊符號的英文
~ Tilde (取代符號)
@ At sign, at (At 符號,At)
# Pound sign (井字號)
$ Dollar sign (錢符號)
^ Caret (插入號)
& Ampersand (And 符號)
* Asterisk (星號)
Backslash (反斜線)
[ Open bracket (左開式方括弧)
] Close bracket (右關式方括弧)
( Open parenthesis (左開式圓括號)
) Close parenthesis (右關式圓括號)
: Colon (冒號)
, Comma (逗號)
-- Double dash (雙破折號)
... Ellipsis (省略符號)
' Single quote (單引號)
" Quote (引號)
= Equals (等號)
+ Plus, plus sign (加,加號)
! Exclamation point (驚歎號)
> Greater than (大於)
< Less than (小於)
? Question mark (問號)
. Period, dot (句號,點)
; Semicolon (分號)
- Hyphen (連字號)
— Dash (破折號)
_ Underscore (底線)
| Vertical bar (垂直線)
{ Open brace (左開式大括號)
} Close brace (右關式大括號)
% Percent, percent sign (百分比,百分比符號)
/ Slash (斜線)
// Double slash (雙斜線)
WPF 中,最好用的莫過於強大的繫結(Binding)能力了。也因此,以往常用的 WinForm 寫作方式也必須作個改變。這裡以 UserControl 的 Binding 為例。
第一個例子是舊習慣的 UserControl 寫作方式。
首先有個 MyUserControl,功能為:使用 Count propery,顯示出多個人名的下拉選單,讓使用者選擇。並以 SelectedName 作為選擇項目的輸出。
public partial class MyUserControl : UserControl { public MyUserControl() { InitializeComponent(); } public int Count { get; set; } public string SelectedName { get { return cbNames.SelectedValue as string; } } private void UserControl_Loaded(object sender, RoutedEventArgs e) { var q = from i in Enumerable.Range(1, Count) select string.Format("Name{0}", i); cbNames.ItemsSource = q; } }
使用此 UserControl 時,只需要在 xaml 中加上 Count 的屬性,就可以顯示多個人名。
MainWindow.xaml
<Window x:Class="NormalUserControl.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:NormalUserControl"> <Grid> <my:MyUserControl HorizontalAlignment="Left" Margin="13,11,0,0" x:Name="myUserControl1" VerticalAlignment="Top" Count="3" /> <Button Content="Ok" Height="23" HorizontalAlignment="Left" Margin="25,87,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" /> </Grid> </Window> 最後,在 Button Click event handler 中,如下的使用方式,可以知道使用者選擇了何人。
private void button1_Click(object sender, RoutedEventArgs e) { MessageBox.Show(myUserControl1.SelectedName); }
一切似乎運作的很好。這也是 Windows Form 的寫作方式。
有個問題來了,如果這個 UserControl 的 Count 值,是繫結自另一個控制項呢?
如下:
<Window x:Class="NormalUserControl.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:NormalUserControl"> <Grid> <my:MyUserControl HorizontalAlignment="Left" Margin="13,11,0,0" x:Name="myUserControl1" VerticalAlignment="Top" Count="{Binding}" /> <Button Content="Ok" Height="23" HorizontalAlignment="Left" Margin="23,108,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" /> <TextBox x:Name="txtCount" Margin="160,12,247,265" Text="3" /> </Grid> </Window>我們想到將 Count 的值繫結自一個 txtCount 的 TextBox,然而 Visual Studio IDE 卻告訴我,A 'Binding' cannot be set on the 'Count' property of type 'MyUserControl'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
在 WPF 中,DependencyObject 的屬性如果要被繫結的話,該屬性必須是 DependencyProperty.
修改的方式也不難。首先在MyUserControl.xaml.cs 中使用 dependencyProperty 的 snippet,作一個 Count 的 dependencyProperty, 取代原先的 Count property
public static readonly DependencyProperty CountProperty = DependencyProperty.Register("Count", typeof(int), typeof(MyUserControl), new PropertyMetadata(default(int)) ); public int Count { get { return (int)GetValue(CountProperty); } set { SetValue(CountProperty, value); } }
再執行一次,程式可以正常編譯了。
執行程式,修改 TextBox為另一個數字後發現,Binding 只有第一次執行時才會繫結,其他時間都不會。為什麼?
原因是目前的實作,並沒有告訴它「當資料變化時該怎麼執行」。
以下是修改後的結果。
public static readonly DependencyProperty CountProperty = DependencyProperty.Register("Count", typeof(int), typeof(MyUserControl), new UIPropertyMetadata(default(int), OnCountChanged)); private static void OnCountChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { var count = (int)e.NewValue; var q = from i in Enumerable.Range(1, count) select string.Format("Name{0}", i); var userControl = o as MyUserControl; var comboBox = userControl.FindName("cbNames") as ComboBox; comboBox.ItemsSource = q; }
以上的重點在於,註冊 DependencyProperty 時,告訴 WPF,當 Count 的值有變化時,請執行 OnCountChanged.
上面談完了將值 Binding 到 UserControl,UserControl 可以讀值,並顯示 UI。那輸出的值是否也可以 Binding 呢?如下面的程式,我們發現 UserControl 的SelectedName 雖然可以讀出,但也不能做 DataBinding。
<Window x:Class="NormalUserControl.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:NormalUserControl"> <Grid> <my:MyUserControl HorizontalAlignment="Left" Margin="13,11,0,0" x:Name="myUserControl1" VerticalAlignment="Top" Count="{Binding ElementName=txtCount, Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> <Button Content="Ok" Height="23" HorizontalAlignment="Left" Margin="23,108,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" /> <TextBox x:Name="txtCount" Margin="160,12,247,265" Text="4" /> <Label Content="{Binding SelectedName, ElementName=myUserControl1}" Height="28" HorizontalAlignment="Right" Margin="0,101,247,0" Name="label1" VerticalAlignment="Top" /> <Label Content="Your Select" Height="28" HorizontalAlignment="Left" Margin="150,101,0,0" Name="label2" VerticalAlignment="Top" /> </Grid> </Window>
此時,要解這個問題,SelectedName仍然需要 dependencyproperty。
public string SelectedName { get { return (string)GetValue(SelectedNameProperty); } set { SetValue(SelectedNameProperty, value); } } public static readonly DependencyProperty SelectedNameProperty = DependencyProperty.Register("SelectedName", typeof(string), typeof(MyUserControl), new UIPropertyMetadata(null));
光這樣是不夠的。ComboBox 的 SelectedItem 並不會自動更新這我們剛剛加上的 SelectedName。
此時在建構子上加如下的繫結(只不過是用程式加的)
var binding = new Binding("SelectedName") { Source = this }; cbNames.SetBinding(ComboBox.SelectedItemProperty, binding);
當 UserControl 遇到 Binding 時,會發生許多原先在 Windows Form 上遇不到的事。正因為 WPF 的繫結非常強大,要駕馭它也要相當的功夫。
最近因專案的緣故,需要開發 Windows Mobile 6.1 的應用程式。
這是我第一個 Mobile 的應用程式,沒想到必須使用舊的系統來開發。(心聲:真的有點老舊的系統,好想直接開發 WM 7,就可以使用 Silverlight 了)
無論如何,Visual Studio 2010 是無法開發這麼老舊的系統了。因此必須回頭灌 VS2008,沒想到,過程有點辛苦。
首先,我用了 Hyper-V 上的虛擬機 (Windows 2003)來開發,沒想到 Windows Mobile Emulator 無法模擬 PDA 上的網路。想了一下,在虛擬機上再虛擬一個網路,果然有些不對勁。
以下,是我在實體機(Windows 7)上的安裝步驟。
當我們開發應用程式時,難免(一定)會有bug。無論是使用者輸入不正確的資料,環境/平台因素等,只要有 bug 就是開發人員的錯。
開發人員難道只有被罵的份嗎?當然不能坐以待斃。我們開發人員必須留下錯誤的痕跡,以快速地找到錯誤的原因。
因此,我們都必須寫像下面的 Code
try { //執行商業邏輯 } catch (Exception) { //記錄例外資訊到檔案或資料庫 Console.WriteLine("對不起!發生錯誤,請洽..."); }
要如何進行例外的記錄(log)呢?
解決的方法,我都是使用 Enterprise Library 中的Exception Handling Application Block。
程式碼的部份很簡單。只需要呼叫ExceptionPolicy.HandleException 如下:
public virtual void Go2() { try { throw new NotImplementedException(); } catch (Exception ex) { ExceptionPolicy.HandleException(ex, "Policy"); } }
但config 設定的話,新手初看就很複雜了。這裡我並不想多做介紹,有興趣的話可以看這一篇。
這裡有個問題,如果每一段程式都需要 try…cache 再來處理例外的話,程式碼就變的很難看。
要在同一個地方處理例外其實很常見。例如在 ASP.NET 上可以在 Global.aspx.cs 中的 Application_Error 中處理
protected void Application_Error(object sender, EventArgs e) { //處理 Application 所有拋出的 exception Exception exception = Server.GetLastError(); ExceptionPolicy.HandleException(exception, "LogAllInFile"); }
雖然集中在一個地方處理很方便,但能做的事情也很有限,只能使用相同的 Policy 來處理例外。並且一旦到如 Global.asax 這裡來處理時,就失去了各自處理的彈性。
有沒有一個方便的解決方法,既可以各自處理,又不用每個地方寫 try..cache 這樣的 Code 呢?
解決3:使用 ExceptionCallHandler
接下來就是今天的主題了。PIAB 可以幫我們解決這樣的問題。第一步,是要讓物件可以被注入。因此物件的類別宣告不可以為sealed,Method 也必須是 virtual。
public class Go { public virtual void Go1() { throw new NotImplementedException(); }
第二步是在 Method 上進行 Attribute 的註記。如下:
[ExceptionCallHandler("Policy")] public virtual void Go1() { throw new NotImplementedException(); }
其中,”Policy”是Exception Policy 的名稱。
當然,不是只有這樣就可以搞定的。我們必須在實例化物件前開始進行複雜的 injection(注射)以達到我們的目的。第三步:
static void Main(string[] args) { var container = new UnityContainer(); container.AddNewExtension<EnterpriseLibraryCoreExtension>(); container.AddNewExtension<Interception>(); container.RegisterType<ICallHandler, ExceptionCallHandler>("ExceptionCallHandler", new InjectionConstructor( new ResolvedParameter(typeof(ExceptionPolicyImpl), "Policy"))); container.RegisterType<Go>( new InterceptionBehavior<PolicyInjectionBehavior>(), new Interceptor<VirtualMethodInterceptor>()); var g = container.Resolve<Go>(); g.Go1(); }
使用 ExceptionCallHandler 來解決似乎方便多了,因為可以在不同的 Method 上註記當錯誤發生時,使用不同的 exception policy 處理之。
但壞處是物件的 Method 必須都是 virtual,讓 IoC 有機會注入不同的行為。
當我使用 Visual Studio 2010 建立一個 Unit Test 時,發生了下面的錯誤
While trying to generate your tests, the following errors occurred:
Value cannot be null.
Parameter name: key
找了一下,原因很簡單。我的 TestProject 已經有相同名稱的檔名了,但在不同的目錄。VS 2010 發現有相同的 class (Full name),就會發生這樣的錯誤。
解決方法呢?就是改檔名或改 namespace 即可。
這個標題下的有些古怪。
我的專案,原本都以 ASP.NET為主。排程的程式大都是 Console 的程式。然而最近接了一個 Client Application 的專案。
經過比較後,我選擇了較新的 WPF,而放棄了 WinForm。問題來了,專案中的成員會問到WPF中如何在DataGrid 取得使用者所選擇ComboBox 的SelectedIndex。
為了演示 WebForm 與 WPF 兩者的差異,我寫了這個 demo.
這個 Demo其實很簡單:設定10個 Emp 的性別。然而分別以 WebForm 與 WPF 做一次,再討論兩者的設計方式。
雖然是不同的應用程式,但有相同的資料,故,將兩者相同的程式放在 CommonLib 中.
using System.Collections.Generic; using System.Linq; namespace CommonLib { public class Emp { public int Id { get; set; } public string Name { get; set; } public int Gender { get; set; } } public class GenderStruct { public int Id { get; set; } public string Name { get; set; } } public class MyDataSource { public List<Emp> GetAllEmps() { var q = from i in Enumerable.Range(1, 10) select new Emp() { Id = i, Name = string.Format("N{0}", i), Gender = 1 }; return q.ToList(); } public List<GenderStruct> GetAllGenders() { return new List<GenderStruct> { new GenderStruct { Id = 1, Name = "Boy"}, new GenderStruct { Id = 2, Name = "Girl"}, new GenderStruct { Id = 3, Name = "Gay"}, }; } } }
ASP.NET WebForm的設計其實真的很麻煩,但用習慣了,也就習以為常。下是在 aspx 中放入一個 GridView 以顯示員工的資料。使用者輸入性別再按下 button,網頁顯示所有員工的最後值。
Default.aspx
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="Id" onrowdatabound="GridView1_RowDataBound"> <Columns> <asp:BoundField DataField="Id" HeaderText="Id" /> <asp:BoundField DataField="Name" HeaderText="Name" /> <asp:TemplateField HeaderText="Gender"> <ItemTemplate> <asp:DropDownList runat="server" ID="ddl"> </asp:DropDownList> </ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView> <asp:Button ID="Button1" runat="server" onclick="Button1_Click" Text="Button" />
Default.aspx.cs
using System; using System.Web.UI.WebControls; using CommonLib; namespace WebApplication1 { public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) { var dataSource = new MyDataSource(); var emps = dataSource.GetAllEmps(); GridView1.DataSource = emps; GridView1.DataBind(); } } protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e) { var dataSource = new MyDataSource(); var allGender = dataSource.GetAllGenders(); if (e.Row.RowType == DataControlRowType.DataRow) { var ddl = e.Row.FindControl("ddl") as DropDownList; ddl.DataSource = allGender; ddl.DataValueField = "Id"; ddl.DataTextField = "Name"; ddl.DataBind(); } } protected void Button1_Click(object sender, EventArgs e) { //由 GridView1 讀取資料 foreach (GridViewRow row in GridView1.Rows) { var ddl = row.FindControl("ddl") as DropDownList; var gender = ddl.SelectedIndex; var id = int.Parse(row.Cells[0].Text); string message = string.Format("Id:{0}, Gender:{1}", id, gender); Response.Write(message + "<br />"); } } } }
這一段程式寫的還真不怎麼樣,竟然還用 Response.Write 這種古老 ASP 的寫作方式?重點不是在演示如何取得資料,而是 ASP.NET 先天上的限制: Stateless。
由於 Web 的 Stateless 的特性,使得Server 端不會記得 Client 曾經輸入或給過的值。因此,當網頁第一次進入時取得所有 Emp 的資料,並在 GridView 的 RowDataBound 事件發生時,為每個性別欄位設定 DropDownList。
之後,使用者輸入完畢後,Submit 回到 Server後,emp 及 gender 的資料都已經不見了,只剩下 GridView 這個 Control 中的值能幫我們留下使用者最終的選擇。
在不得已的情況下,我們必須研究 GridView 的組成,如 GridView 的 Row,Row 下有 Cells,再使用 FindControl 取得 DropDownList…等等。
在 WPF 中,由於是 Stateful,相同的功能,寫作方式相對 ASP.NET 大為進步。
MainForm.xaml
<DataGrid AutoGenerateColumns="False" Height="206" HorizontalAlignment="Left" Margin="12,12,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="288" > <DataGrid.Columns> <DataGridTextColumn Header="Id" Binding="{Binding Path=Id}" /> <DataGridTextColumn Header="Name" Binding="{Binding Path=Name}" /> <DataGridTextColumn Header="Gender" Binding="{Binding Path=Gender}" /> <DataGridComboBoxColumn Header="Gender2" x:Name="cbGender" SelectedValuePath="Id" DisplayMemberPath="Name" SelectedValueBinding="{Binding Gender}" /> </DataGrid.Columns> </DataGrid> <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="359,115,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
MainForm.xaml.cs
public partial class MainWindow : Window { private List<Emp> _emps; private List<GenderStruct> _allGenders; public MainWindow() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { //取得資料並放到 Window 的變數中。因為Stateful,所以可以這樣做 var dataSource = new MyDataSource(); _emps = dataSource.GetAllEmps(); _allGenders = dataSource.GetAllGenders(); dataGrid1.ItemsSource = _emps; cbGender.ItemsSource = _allGenders; } private void button1_Click(object sender, RoutedEventArgs e) { var emp = dataGrid1.SelectedItem as Emp; var message = string.Format("Name: {0}, Gender: {1}", emp.Name, emp.Gender); MessageBox.Show(message); } }
值得注意的是,在 Window Loaded 後,取得所有 Emp 及 Gender 資料後,直接放到 Window 的 private member 中,讓後續的資料繫結(databind) 作業直接存取window private member即可。
使用者輸入完畢後,更新後的資料「直接」反應到資料繫結的對象(即 _emps, _allGenders),不必像 WebForm一樣透過 Control 間接取得資料。
這個範例中,主要演示了 ASP.NET WebForm 與 WPF 的先天上的差異(Stateless vs Stateful),並且由於 WPF 雙向繫結的功能,使用者的輸入可以直接更新到資料來源,遠較WebForm 來的直覺與方便。
經由上次介紹的 設定 IIS 為自動啟動 (Auto-Start)後,原先在 TFS 的 Web 自動部署 反而失敗了。訊息如下:
C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.targets (3588): Web deployment task failed.((2011/7/5 上午 08:16:45) An error occurred when the request was processed on the remote computer.) (2011/7/5 上午 08:16:45) An error occurred when the request was processed on the remote computer. Filename: \\?\C:\Windows\system32\inetsrv\config\applicationHost.config Line number: 176 Error: Unrecognized attribute 'serviceAutoStartMode'
看來,目前 TFS 的 Publishing 尚不知道 AppFabric 的serviceAutoStartMode 屬性。只好在測試機上不使用 IIS Auto Start 的功能。
以前在dotnet 1.0 時介紹 ASP.NET 的工作原理時,總是滿懷興奮向客戶提到compilation 的好處,就是程式執行後,會留下編譯(compilation) 的結果,下次相同程式執行時,不必再編譯了. 但此處有個缺點,第一個 Request,總是最倒楣的,不但要等 compilation,而且要等 IIS 建立執行序。
在 dotnet 2.0 後,有了 precompilation的機制。作法是部署程式,以手動執行 aspnet_compiler 來扮演那第一個倒楣的Reqeust。
至於等 IIS 建立執行序這回事呢,還是無解呢。
在 IIS 7.5 後(Windows Server 2008 R2 or Windows 7),此問題終於有解了。作法是修改 C:\Windows\System32\inetsrv\config\applicationHost.config,並且設定兩個地方。
1 修改應用程式集區 (Application Pool) 為自動啟動:搜尋 <applicationPools,並將對應的 application pool 加上 startMode=”AlwaysRunning”
<applicationPools> <add name="ASP.NET v4.0" autoStart="true" managedRuntimeVersion="v4.0" startMode="AlwaysRunning"> <processModel identityType="ApplicationPoolIdentity" loadUserProfile="true" /> </add>
<sites> <site name="Default Web Site" id="1" serverAutoStart="true"> <application path="/"> <virtualDirectory path="/" physicalPath="c:\inetpub\wwwroot" /> </application> <application path="/MyApp" applicationPool="ASP.NET v4.0" serviceAutoStartEnabled="true" serviceAutoStartProvider="Service" serviceAutoStartMode="All"> <virtualDirectory path="/" physicalPath="E:\Projects\Smart\Dev\Rsi.Smart2G\Rsi.Smart2G.WebServices" /> </application>
使用NotePad 修改 config總是怕怕的,尤其是 IIS 此等重要的設定,怎麼可以用如此簡易的工具來設定呢?客戶的資訊人員萬一修改錯誤,又得呼叫我們苦命的Vender 工程師,誰會給我們加班費呢?難道微軟沒有聽到我們的心聲嗎?
有的,這個 UI 設定工具,就是之前介紹的 Windows Server AppFabric。安裝後,只要簡單地在應用程式上按右鍵/Manage WCF and WF Services/Configure…,
接下來在左方功能表中選到 Auto Start,再選擇 Enable(all services will auto start)的選項。
按 Apply 鍵後,就大功告成了.
最近一直與 WCF Data Service 打交道。也漸漸了解這個技術的優勢與缺點了。整理如下
如果使用WCF來開發一個 CustomerService,就免不了如下的Method
AddCustomer(Customer customer);
DeleteCustomer(int id);
UpdateCustomer(Customer customer);
FindCustomers(string condition);
新刪修查,至少要發佈4個 Method。如果使用 WCF Data Service,只需要一個 Url 來定位即可。如果再使用 Entity Framework,更是方便。
使用 WCF Data Service Client Library 來查詢 OData的資料,可再進一步使用 LINQ 的語法來查詢資料。新增/刪除/修改資料時,更像是在操作 Entity Framework 一般的方便。見更新資料服務 (WCF Data Services)
這個年代,服務不能跨平台,就沒有人要使用。因此,WCF Data Service 是非常簡單的 Url 定址,與 xml 純文字的回傳資料,再加上 HTTP 的 Verbs,即可完成簡單的操作。跨平台,是最大的賣點。看一下 oData SDK,各式各樣的平台與技術都被支援哦!
WCF Data Service要查詢資料目前大都只支援 GET。當有大量資料要上傳到伺服器時,由於 HTTP GET 有 Url的長度限制( 通常為 8096 bytes),所以只好轉用 POST。但 WCF Data Service Client Library 支援 POST 的能力很差哦!連呼叫都必須使用 HttpWebRequest 或 WebClient 來呼叫,相對來說實在很難用。
查詢資料服務 (WCF Data Services),初看之下看來很強悍,但 check LINQ 考量 (WCF Data Services) 中的「不支援的 LINQ 方法」一節,不支援的 LINQ 語法還真多。最常用的 Join,Count竟然也不支援,聲勢立即弱了下來。
由於缺點1的關係,想要傳較多資料到伺服器時,就必須使用 WebInvoke 的 service operation。在Service Operation上,並不能使用類似 WCF Service 的 Stream 來讀取 POST 的資料。原因很簡單,因為 Stream 並無法自動發佈成 metadata。因此,如果要讀POST 的內容,必須使用 ASP.NET 的 HttpContext.Current.Request.Form 來讀取。因此程式碼就完全相依服 ASP.NET了。之後有空,就寫另一篇來介紹這個作法。
RESTful 使用了 Url 來定址,新刪修查分別對應 POST, DELETE, PUT,GET等 HTTP Method(Verbs),所在只能跑 http 的 protocal,無法跑 binary 來傳資料。傳輸資料量變大後,效能就會比較差。幸好, IIS 有動態壓縮的功能,可以有效減少傳輸的資料量。
看起來好像缺點比優點多一些,但是,跨平台卻是相當大的優勢。未來是 HTML5的世界,WCF Data Service 一定大有可為。
微軟的 Visual Studio 2010 終於支援 HTML5 的編輯了。看了Announcing the Web Standards Update - HTML5 Support for the Visual Studio 2010 Editor,就下載了 Visual Studio Web Standards Update。
怪事發生了,一旦執行 Web Standards Update.msi,就會出現 Service pack 1 for visual studio 2010 must be installed 的警告訊息。我的確有安裝 SP1 for VS2010 啊!
如果有安裝 Windows SDK 的話,可從修改 msi 檔著手。以我的機器為例,在 C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\ 下有個 Orca.msi,安裝後,執行 Orca。
打開 Web Standard Update.msi 後, 在左方 Tables 內選 CustomAction, 在 VSDCA_VsdLaunchConditions 上按右鍵 ,執行 Drop Row功能。
記得按儲存鍵。
由於我們修改了 msi,數位簽章就失效了,執行時會出現如下圖的警告訊息,這是正常的。按「Run」以繼續執行。
然後,就可以安裝了。
執行 Visual Studio 2010.建立一個新的 Web Application後,可以選擇 HTML5 來檢查格式。
當我們打入 <vi時,會出現 video 的 intellisense (智能提示)。果然好用。
我將修改好的 msi 放在這裡,有需要的人可以自行下載。
在以往的 VB6,或者是 Windows Form 應用程式中,更新 UI的方式極為簡單,往往只是 Application.DoEvents 就可以更新。Windows Form 中更有 Invoke 與 BeginInvoke 可以彈性地使用。
那在 WPF 中,要如何更新 UI 的內容呢?
當然,要從一個不正確的範例開始。
Ex1Bad.xaml
<Window x:Class="WpfApplication10.Ex1Bad" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Ex1Bad" Height="300" Width="300"> <StackPanel> <Label Name="lblMsg" Content="Nothing" /> <Button Content="Start" Name="btnStart" Click="btnStart_Click"/> </StackPanel> </Window>
Ex1Bad.xaml.cs
using System.Threading; using System.Windows; namespace WpfApplication10 { public partial class Ex1Bad : Window { public Ex1Bad() { InitializeComponent(); } private void btnStart_Click(object sender, RoutedEventArgs e) { lblMsg.Content = "Starting..."; Thread.Sleep(3000); //執行長時間工作 lblMsg.Content = "Doing..."; Thread.Sleep(3000); //執行長時間工作 lblMsg.Content = "Finished..."; } } }
這裡以 Thread.Sleep(3000)讓 UI Thread 睡個3秒鐘,來模擬長時間的工作。
這是個常見的程式碼,但卻是沒有用。在 Windows Form 的 API 中有 Application.DoEvents() 可以呼叫。WPF 中沒有類似的嗎?
原來,WPF 中雖然沒有類似的 api 可以呼叫,但仍可以直接呼叫 Windows Form 的 Application.DoEvents.當然,需要引用 System.Windows.Forms.dll。
Ex2WinformDoEvents.xaml
using System.Threading; using System.Windows; using swf = System.Windows.Forms; namespace WpfApplication10 { public partial class Ex2WinformDoEvents : Window { public Ex2WinformDoEvents() { InitializeComponent(); } private void btnStart_Click(object sender, RoutedEventArgs e) { lblMsg.Content = "Starting..."; swf.Application.DoEvents(); Thread.Sleep(3000); //執行長時間工作 lblMsg.Content = "Doing..."; swf.Application.DoEvents(); Thread.Sleep(3000); //執行長時間工作 lblMsg.Content = "Finished..."; } } }
在更新UI後,呼叫 swf.Application.DoEvents(),就可以更新 UI 了。這樣的方式與之前的 VB6是一模一樣的手法。
哦?WPF 沒有 DoEvents 可以使用,只能呼叫老前輩Windows Form 的 API 嗎?也不是。在 DispacherFrame 文章中就有sample.
Ex3WPFDoEvents.xaml.cs
using System; using System.Security.Permissions; using System.Threading; using System.Windows; using System.Windows.Threading; namespace WpfApplication10 { public partial class Ex3WPFDoEvents : Window { public Ex3WPFDoEvents() { InitializeComponent(); } private void btnStart_Click(object sender, RoutedEventArgs e) { lblMsg.Content = "Starting..."; DoEvents(); Thread.Sleep(3000); //執行長時間工作 lblMsg.Content = "Doing..."; DoEvents(); Thread.Sleep(3000); //執行長時間工作 lblMsg.Content = "Finished..."; } [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] public void DoEvents() { var frame = new DispatcherFrame(); Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame); Dispatcher.PushFrame(frame); } public object ExitFrame(object f) { ((DispatcherFrame)f).Continue = false; return null; } public static void DoEvents2() { Action action = delegate { }; Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Input, action); } } }
DoEvents() 與 DoEvents2() 的效果相同。
DoEvents 這麼好用,為什麼WPF還要發明 Dispatcher,或 Windows Form 的 BeginInvoke 這些 API 呢?
跑以下的程式碼看看吧。
private void btnEvil_Click(object sender, RoutedEventArgs e) { for (int i = 0; i < 10000000; i++) { System.Windows.Forms.Application.DoEvents(); } MessageBox.Show("Ok"); }
執行時,記得打開工作管理員看看CPU 的負載,會持續飆高一斷時間。雖然 UI 沒有任何的更新,為何 CPU 會飆高呢?
DoEvent 的原理是execution loop,也就是以迴圈的方式來查詢是否有要更新的訊息(message)。一看到迴圈,各位看倌就知道是怎麼回事了吧。
範例3中的WPF 的 DoEvents 也是相同的道理。
有沒有較正常的方式來更新 UI 呢?看一下Ex1Bad.xaml.cs的設計方式吧。更新lblMessage後執行一段工作,這基本上是同步的寫作方式。在 UI Thread 上執行工作,本來就會使得 UI 停頓,使用者感到不方變。
正確的方式,是使用BackgroundWorker來執行長時間的工作,並以非同步的方式更新在 UI Tread 上的UI內容。
Ex4BackgroundWorker.xaml.cs
using System; using System.ComponentModel; using System.Threading; using System.Windows; using System.Windows.Controls; namespace WpfApplication10 { public partial class Ex4BackgroundWorker : Window { public Ex4BackgroundWorker() { InitializeComponent(); } private void btnStart_Click(object sender, RoutedEventArgs e) { ExecuteLongTimeWork(lblMsg, "Starting"); ExecuteLongTimeWork(lblMsg, "Doing"); ExecuteLongTimeWork(lblMsg, "Finished"); } private void ExecuteLongTimeWork(Label label, string message) { var backgroundWorker = new BackgroundWorker(); backgroundWorker.DoWork += (s, o) => { Thread.Sleep(3000); //執行長時間工作 }; backgroundWorker.RunWorkerCompleted += (s, args) => { Dispatcher.BeginInvoke(new Action(() => { label.Content = message; })); }; backgroundWorker.RunWorkerAsync(); } } }
BackgroundWorker 工作方式,是建立一個新的 Thread 來執行 DoWork 的event handler,執行完畢後,再執行 RunWorkerCompleted 的event handler。因此,我們需要的是在RunWorkerCompleted 的event handler 中更新的 UI。
更新 UI 時,又使用 Dispatcher 來更新 UI。基本上,Dispatcher 是一個 Queue 的概念,凡是使用 Dispatcher 來更新 UI,WPF 會照 DispatcherPriority 的優先順序來更新 UI。
雖然只是小小的UI 更新方式,也是有不少的學問呢!
見 http://msdn.microsoft.com/zh-tw/library/system.windows.threading.dispatcherframe.aspx
execution (執行) 怎麼會翻譯成例外(exception) 呢?機器翻譯時不會翻錯字,這明顯地不是機器翻譯的,而是人去翻譯的。
沒有人會故意犯錯,但只要是人,就很可能犯錯。(當然包括我)
可以的話,還是看原文比較好。雖然較吃力,但至少不會被誤導而不自知。
最近在使用 WCF Data Services 時,WCF Data Services Client library 使用 DELETE HttpMethod 時,發生了 HTTP Error 405.0 - Method Not Allowed 的錯誤。
The page you are looking for cannot be displayed because an invalid method (HTTP verb) is being used.
經過不少的測試,也試過移除IIS 7 的 WebDAV module,都不成功。最簡單的方法,是在 Web.Config 中增加下面的設定。感謝 IIS 7。
<system.webServer> <validation validateIntegratedModeConfiguration="false" /> <modules> <remove name="WebDAVModule" /> </modules> <handlers> <remove name="WebDAV" /> </handlers> </system.webServer>
近來幾乎所有的 Middle Tier 的功能,微軟都是使用 IIS 來作 Host,最有名的就是WCF。如果要建置跨平台的解決方案,使用 xml + Http 幾乎是不二人選,binary 的 protocal 就把它忘了吧。
然而, xml + http 為人最為垢病的地方,就是它實在痴肥。為了讓它減肥,我不得已來 study 如何在 IIS 7 上壓縮網頁的輸出。
如下圖,我呼叫叫了一個 WebService,回傳的是 xml,但大小竟然是 1.5 MB!!這叫我如何能接受?
說實話,我已在 IIS 6 上實作過了。IIS 6 的過程實在不人道,我也不想 POST 到部落格上。而 IIS 7 就簡單許多。
首先,要了解的是 IIS 7 預設就打開了靜態內容的壓縮。不像 IIS 6預設都不壓縮。這大概與 IIS 壓縮的成熟度有關吧。
打開 IIS 7, 點到 Compression 的功能,右方出現一排字「The dynamic content compression module is not installed」,意思就是沒有安裝 dynamic content compression 這一個 module。
使用 Programs and Features,執行左方 Menu 的 Turn Windows features on or off,並勾選「Dynamic Content Compression」,按OK建進行安裝。
安裝完成後,再到 IIS 上執行Compress 的設定。check 一下,果然設定成功了。
只有安裝 dynamic content compression 模組是不夠的。IIS 此時尚不知道要對什麼動態內容作壓縮,接下來設定的就是:「告訴 IIS 什麼樣的內容要作動態壓縮」
在 IIS 上,點選 machine 名稱,執行「Configuration Editor」。
選 Section : system.webServer/httpCompression,並調整「dynamicTypes」
按右方 Actions Pane 的 Add 連結,並輸入 application/atom+xml
記得完成後,要按右上的 Apply 鍵完成設定。
最後,還要重新起動 IIS
再使用Fiddler 測試一下相同的網頁。Oh, My God, 1.5MB的內容壓縮過後只剩 149 KB,壓縮成原來的 10% ,省下的頻寬實在可觀。
繼上次的 TFS 建置:Coded UI 自動測試 問題後,好久好久我都沒再碰 TFS 的 Build 設定了。
這次,目的在建立 Web 的自動部署。
這次的環境較為複雜。有開發機,TFS Server,Build Server,Test WebServer.
希望可以達成的需求:在開發機上要求一個 Build後,TFS Server 向 Build Server 要求啟動一個 Build。Build Server 完成 Compile 後,將 Web Application 自動部署到 Test WebServer。
過程還有些漫長,最大的原因還是「太久遠的記憶」是不可靠的。下面是我試出來的結果。
在 MSBuild Arguments 我設定如下
/p:DeployOnBuild=True /p:DeployTarget=MsDeployPublish /p:MSDeployPublishMethod=RemoteAgent /p:CreatePackageOnPublish=True /p:DeployIisAppPath="Default Web Site/YourApp" /p:MsDeployServiceUrl=http://172.199.199.199/MsDeployAgentService /p:username=administrator /p:password=adminpassword
Build Server 上,必須安裝了 Visual Studio 2010。我試的結果,不裝,就不會自動部署。我不知道到底我錯過了什麼環結,雖然設定了上述的 MSBuild Arguments,但就是不跑 Deploy。
安裝時,我選了 c# 與Visual Web Developer。理論上只需安裝 Visual Web Developer即可,但我不想再浪費時間試了。
另外,在 Visual Studio 2010 Web Application 的專案屬性上,也有一些玄機。如果有勾選「Include IIS settings as configured in IIS」或「Include application pool settings used by this Web project」的話,在 Build Server 就要安裝 IIS 並且有相同的設定。因為當 Build Server 要打包 package 時,需要讀IIS的設定。讀不到設定就會出現下面的錯誤訊息。
C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.targets (1657): "MapUriToIisWebServer" 工作發生未預期的失敗。 System.Runtime.InteropServices.COMException (0x80005000): 未知的錯誤 (0x80005000)
此時的我,顧不得完美了,先不要勾選這兩個選項了。
1 在待部署的 Web Server 上,必須安裝 Web Deployment Tool。安裝時,必須注意要選「完整」安裝。
2 安裝完畢後,需要在服務中啟動 Web Development Agent Service
3 Build 執行後出現下面的錯誤
Web deployment task failed.((2011/6/2 下午 05:14:42) An error occurred when the request was processed on the remote computer.) (2011/6/2 下午 05:14:42) An error occurred when the request was processed on the remote computer. The application pool that you are trying to use has the 'managedRuntimeVersion' property set to 'v2.0'. This application requires 'v4.0'.
由於我沒有選擇要打包 IIS 的設定,部署後的應用程式是下 Default Web Site 下。Default Web Site 預設是.net framework v2.0, MSDeploy 在建立 application 時會預設同上一層的 application pool。
我的解法是直接將上一層的 Default Web Site 改用 v4.0 的 application pool。
上述設定,一定還有調整優化的空間。之後有空再寫 blog好了。
雖然很麻煩,但想到之後不必再手動部署測試環境時,心裡也就舒坦了許多。
今天早上客戶寄來一封資安檢測結果,說我們維護的Web系統有漏洞。
弱點大意是:Web Server 提供了 PUT, TRACE, DELETE, CONNECT 的 Http Method. 應該關閉。
並且把這項缺失列為中等風險,寫的很厲害的樣子。「PUT method 允許用戶端上傳檔案至伺服器, DELETE method 允許用戶端將伺服器的檔案刪除. 將允許遠端攻擊者可任意上傳檔案至伺服器而置換網頁, 若攻擊者上傳惡意程式到具有執行權限的目錄, 將可達到更大的破壞.」
記得我們沒有開 WebDAV 啊!為什麼寫的這麼離譜?使用 Fiddler 查一下吧!
使用 Fiddler 的 Request Builder 查開發機 IIS
再選到 Inspectors,看查詢的 Request 及 Response (結果)
OPTIONS 會列出該 Web Server 可使用的 Http Method.
結果列出來,只有一項 TRACE 是有爭議的。根據 RFC 2616 指出,這是用來 Debug 用的 Method,可能揭露出不必要的資訊。因此會被列為風險。在 Apache 上更可能被偷取 cookie。
為什麼在IIS 上 TRACE 預設是可以使用的呢?再使用 Fiddler,這次使用 TRACE 查一下吧。
這次的結果是 HTTP/1.1 501 Not Implemented (未實作)。HttpStatus 500 以上代表伺服器錯誤。
雖然有開放 TRACE 這個 Method,但一律回未實作。這妙吧。
因此,沒有漏洞。
再使用 DELETE 查一下。結果是 405 Not allowed(不允許)。這證明 IIS 上的WebDAV 果然沒有開啟。
一看到資安檢測結果時,雖然只有這一項目,還是心裡不舒服。
看來該資安廠商是使用工具掃出來的結果。雖然只有一個 TRACE 看來符合,但把整個 PUT, DELETE, TRACE, CONNECT 全部列上去,看來比較有「價值」。
另外, IIS 為什麼要列為可用的Method,然後再回應 501 未實作呢?這裡就不深究原因了。
NuGet 也可以直接安裝 Enterprise Library 了。
舉例來說,之前在使用 Enterprise Library Exception Handling Application Block 時,需要在專案上加入下列參考
Microsoft.Practices.EnterpriseLibrary.Common
Microsoft.Practices.EnterpriseLibrary.ExceptionHandling
沒有經驗的人,很難一次引用正確。
Add Library Package Reference
安裝指定的 package
完成!還比想像中多了一些 dll,這些 unity 在用的 dll,什麼時候會用到我也不知道。
可惜的是,引用 package 並不會直接產生 .config 的設定。原因很簡單,它不知道我們要設定些什麼。
Enterprise Library 的設定太靈活了,要設定的話,還是乖乖地使用 Enterprise Library Configuration Editor 來設定吧。
在WPF 中如何一次對所有的 UserControl 作樣式設定呢?
在 WPF 中,要對同一個 Control 作預設的樣式設定很簡單。只需要在 App.xaml 中設定即可。
App.xaml
<Application x:Class="WpfApplication1.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> <Application.Resources> <Style TargetType="UserControl"> <Setter Property="Background" Value="Gray" /> </Style> </Application.Resources> </Application>
在 Visual Studio 中,可以在 designer 看到 usercontrol 的樣式套用了指定的樣式,看起來是可以的樣子哦!(當然沒這麼簡單,不然就不用寫這一篇了)
執行起來畫面就是沒有套用到。
程式的部份,請看附件的 WpfApplicatioin1 這個專案。
不信邪的我,稍稍改動了一下,讓樣式直接指定 UserControl1。這樣就可以套用樣式了。
<Application x:Class="WpfApplication2.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication2" StartupUri="MainWindow.xaml"> <Application.Resources> <Style TargetType="{x:Type local:UserControl1}"> <Setter Property="Background" Value="Gray" /> </Style> </Application.Resources> </Application>
這樣子只能套用到指定的 UserControl1,而非所有的樣式。原因是:樣式雖然可以像「繼承」樣子套用到指定的 Type,但不是繼承出來的 Type 都可以套用父類別的樣式。
程式的部份,請看附件的 WpfApplicatioin2 這個專案
這一版,自已加上了一個自訂的 MyUserControl 並繼承自 UserControl。之後,所有的 UserControl 都改繼承自訂的 MyUserControl。
在 App.xaml 中,樣式使用 BaseOn 這個關鍵。
<Application x:Class="WpfApplication3.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication3" StartupUri="MainWindow.xaml"> <Application.Resources> <Style TargetType="UserControl" x:Key="baseStyle"> <Setter Property="Background" Value="Gray" /> </Style> <Style TargetType="{x:Type local:MyUserControl}" BasedOn="{StaticResource baseStyle}" /> </Application.Resources> </Application>
滿心歡喜地以為這樣萬事 OK 了,結果還是失敗。使用 Snoop 來查一下,Style 並沒有值。
程式的部份,請看附件的 WpfApplicatioin3 這個專案
不知道這是不是 bug。結果還是動用了 code behind 來解決事情。
public class MyUserControl : UserControl { public MyUserControl() { this.Style = this.FindResource("baseStyle") as Style; } }
使用 Snoop 來看,終於有套用到 style 了
程式的部份,請看附件的 WpfApplicatioin4 這個專案
這是 WPF 4 的 bug 嗎?不知道。只知道浪費了2小時的時間。
LINQ 的一大強項,就是 deferred execution。所謂的 deferred execution,是指當下宣告時只是宣告未來要執行的命令,而實際的執行要等到列舉(enumerate) 時才真的開始。
class Program { static void Main(string[] args) { var stringArray = new string[] { "A", "B", "D", "D", "E"}; //宣告執行的動作,未實際執行 var q = from i in stringArray where i.StartsWith("D") select i; //列舉時才真的執行 var count = q.Count(); Console.WriteLine(count); } }
這裡看來沒什麼問題。
class Program { static void Main(string[] args) { var stringArray = new string[] { "A", "B", "D", "D", "E"}; var enumCount = 0; //宣告執行的動作,未實際執行 var q = from i in stringArray select new {EnumCount = enumCount++, i}; //列舉時才真的執行 foreach (var item in q) { enumCount++; Console.WriteLine("{0}/{1}", enumCount, item.EnumCount ); } } }
輸出
看到問題了嗎?原以為在 q 裡面的值應該是 0, 1, 2, 3, 4,結果輸出的卻是 0, 2, 4, 6, 8。
原因是在實際執行時(foreach loop 內)受到了 enumCount++這一行的干擾,讓原本的執行原意被扭曲了。
LINQ 雖然好用,但在執行時千萬要注意 deferred execution 的現象。不該 deferred execution 時,就直接列舉吧。即 .ToList(), ToArray(), Sum() 等
使用ODAC 11.2.0.2.30 Beta for Entity Framework and LINQ to Entities (以下簡稱 Oracle EF)來查詢資料,發生了以下的錯誤。
發票的主檔與明細檔。
這只是相當簡單的查詢CREATE TABLE MYSCHEMA.INV
(
INVOICENO NUMBER(38),
NAME CHAR(10 BYTE)
)
CREATE TABLE MYSCHEMA.INVDETAIL
(
INVDETAILID NUMBER(38),
INVNO NUMBER(38),
QTY NUMBER(38),
AMT NUMBER(38)
)
並且,以 InvNO 做為關聯
執行下面的 LINQ To Entity
var context = new Entities(); var q = from m in context.INVs select new { m.INVOICENO, Amt = m.INVDETAILs.Sum(x => x.QTY * x.AMT) }; Console.WriteLine(((ObjectQuery)q).ToTraceString()); var resutl = q.ToList();
Oracle.DataAccess.Client.OracleException: ORA-00904: "Extent1"."INVOICENO": invalid identifier
產生的sql 還有些長,就不貼出來了。
已在 Oracle ODP.NET Forum 上發問了。
說實話,這樣的查詢其實相當普遍,然而 Oracle 在 Alfa 版時竟沒查出,而在Beta 版時給普羅大眾給測到這個 bug,實在有些……
一個無意間,觸碰到了地雷。而這個地雷,是有原因的。
WPF 的 xaml 中,如果在 StackPanel 中含有 DataGrid,是常見的事。但是,如果 DataGrid bindind 到大量的資料時,就有些問題了。
下面是我的 xaml
xaml
<Window x:Class="LargeDataInDataGrid.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded"> <StackPanel> <Label Content="資料筆數" /> <TextBox Name="txtRecNo" TextChanged="txtRecNo_TextChanged" Text="100" /> <DataGrid Name="dgResult" ItemsSource="{Binding}"/> </StackPanel> </Window>
程式運作如下圖
當資料只有100筆時,看起來很正常。但看一下工作管理員,天啊,要秏掉記憶體 72 MB。WPF 這麼秏 memory?
如果將筆數調整到 1000筆呢?剛改完數字,WPF 程式彷彿當掉一樣。hang 住個幾秒鐘後,看一下記憶體,增加到 187 MB了。
原來, StackPanel 的用途,是儘量地長出 Child control 所要使用的空間 (space)。DataGrid 要長出 100 筆資料的空間,就計算並配置所要的空間及記憶體。所以當 DataGrid 要長出1000筆時,記憶體的使用量就不得了。
但是,我們看不到這麼多筆啊?雖然看不到, StackPanel 仍然堅持配置記憶體呢!
我們將 StackPanel 換成 Grid來試試看。
<Grid> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition Height="30"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Label Content="資料筆數" /> <TextBox Name="txtRecNo" Grid.Row="1" TextChanged="txtRecNo_TextChanged" Text="100" /> <DataGrid Name="dgResult" ItemsSource="{Binding}" Grid.Row="2"/> </Grid>
換成 Grid 後,即使是1000筆,記憶體只用了 71 MB,明顯地少掉許多,程式運作時也不會有問題。
為什麼 Grid 可以運作正常呢?原來 Grid 運作時是有計算Row 的高度的。DataGrid 在顯示時 Row 高度明顯有限制,因此要求 DataGrid 有高度的限制。DataGrid 的 Height 預設是 Auto,不足時 ScrollBar 就會出現。此時 UI Virtulization 的機制開始運作,只需要顯示看的到的區域即可 ,因此記憶體使用量變少。
如果我還是需要在 StackPanel 使用 DataGrid 呢?只需要設定 DataGrid 的 Height 或 MaxHeight 就可以了。
<StackPanel> <Label Content="資料筆數" /> <TextBox Name="txtRecNo" TextChanged="txtRecNo_TextChanged" Text="100" /> <DataGrid Name="dgResult" ItemsSource="{Binding}" MaxHeight="500"/> </StackPanel>