Quantcast
Channel: 黑暗執行緒
Viewing all 2433 articles
Browse latest View live

WCF探勘8-使用Protobuf-Net序列化

$
0
0

Protocol Buffers是Google內部使用的跨語言資料格式標準,在資料體積及序列化/反序列化速度上表現亮眼,相信以下的Benchmark圖表已具備足夠的說服力,說明本文的研究動機:(Protocol Buffers拿下速度及資料量雙料冠軍)

圖表來源:http://theburningmonk.com/2014/08/json-serializers-benchmarks-updated-2/

protobuf-net是Protobol Buffers的.NET版實作,只需簡單的程序,我們就能在WCF以Protocol Buffers取代SOAP及WCF原有的二進位序列化。好啦,我騙大家的,換用protobuf-net有不少眉角,測試過程我還踩到雷。但在這次經驗也讓我體認到,WCF架構的彈性與周全,允許開發者抽換組裝處理程序的各個環節,設定複雜背後是有收獲的。

我們繼續沿用先前觀察SOAP XML資料傳輸量的IService1專案,動手改裝成protobuf-net版。第一步要將CompositeType抽取成獨立DLL給WCF Service及WCF Client參照,因為參照WCF服務自動產生的CompositeType型別無法保留protobuf-net設定。

將IService1.cs的CompositeType搬到獨立的WcfDto專案,先透過NuGet加入protobuf-net:

在原本的[DataContract] [DataMember]之外,再加入[ProtoContract]及[ProtoMember(n)]:

using ProtoBuf;
using System.Runtime.Serialization;
 
namespace WcfDto
{
    [DataContract]
    [ProtoContract]
publicclass CompositeType
    {
bool boolValue = true;
string stringValue = "Hello ";
int intValue = 1;
 
        [DataMember]
        [ProtoMember(1)]
publicbool BoolValue
        {
            get { return boolValue; }
            set { boolValue = value; }
        }
 
        [DataMember]
        [ProtoMember(2)]
publicstring StringValue
        {
            get { return stringValue; }
            set { stringValue = value; }
        }
 
        [DataMember]
        [ProtoMember(3)]
publicint IntValue
        {
            get { return intValue; }
            set { intValue = value; }
        }
    }
}

WCF Service參照WcfDto專案並用NuGet加入protobuf-net,接著要調整WCF Service端web.config設定,最終目的要調成如下設定。新増BehaviorExtension、EndpointBehavior,Service的Endpoint設定再指向新増的EndpointBehavior。

感覺有點複雜,用來示範WCF設定GUI編輯器操作好了。

開啟編輯器後先找到Advanced/Extensions/behavior element extensions,按New新増一筆。

名稱輸入protobuf,型別則可靠瀏覽protobuf.dll找出完整型別名稱。

 

 

下一步要新増Endpoint Behavior:

名稱輸入protoEndpointBehavior(可自由命名),按下Add鈕會帶出可用的擴充項目清單。

清單裡可找到剛才新増的protobuf:

接著再回到服務的兩個Service Endpoint設定,BehaviorConfiguration下拉選單就有protoEndpointBehavior可選,到這裡web.config設定就完成了,可以得到如先前擷圖的設定結果。

實做的心得是WCF設定編輯器提供了不少指引、提示,但效率遠不如直接編輯config檔,度過新手探索期, 大家應該還是會選擇直接編輯config吧。

這裡分享我踩到的一個雷:protobuf-net不支援陣列格式的序列化,先前程式範例的

[OperationContract]
CompositeType[] GetDataUsingDataContract(CompositeType composite)

要改成

[OperationContract]
List<CompositeType> GetDataUsingDataContract(CompositeType composite)

(或IEnumerable<CompositeType>也可以,不要用陣列就對了,否則傳回結果會維持SOAP XML,參考

另外,WCF Client加入服務參照時,預設會將List<T>視為T[],繼續踩到protobuf-net的痛處,要記得調設定:(以上兩點花了我兩個小時才學會)

WCF Client也要比照WCF Service端,先NuGet安裝protobuf-net,並app.config加上behaviorExtension,behaviorConfiguration,Service的endpoint設定指向behaviorConfiguration。

一切設定妥當,用MNM觀察,可看到原本的SOAP XML:

變成<protobuf>CgKIARIF… 的特殊資料格式。

看起來節省不少體積,至於資料量有多少改善?賣個關子,下回再談。

歸納WCF改用protobuf-net序列化的重點:

  1. 將WCF參數或結果用到的自訂型別放到獨立DLL專案,WCF Service及WCF Client改參照獨立DLL
  2. 獨立DLL、WCF Client、WCF Service都要參照protobuf-net(可用NuGet安裝)
  3. 在自訂型別加上[ProtoContract]、[ProtoMember]
  4. 修改WCF Service設定,最終要為Service Endpoint加上behaviorConfiguration
  5. protonet-buf不支援陣列型別的序列化,若WCF參數或傳回值用到陣列, 需改用List<T>或IEnumerable<T> ,並記得在參照WCF服務時修改Collection Type
  6. 確認WCF Client的Endpoint設定也加上behaviorConfiguration

WCF探勘9-Protobuf-Net序列化資料量觀察

$
0
0

前篇文章介紹完如何用protobuf-net取代WCF原本的XML及NMF序列化,來看看它在資料減量上的表現。

NetTcpBinding

首先登場的是NetTcpBinding改用protobuf-net後的封包分析,黃底部分為WCF Server回傳結果的封包:

資紏統計如下(第二欄來自先前測試NetTcpBinding的數據,第三欄為NetTcpBinding改用protobuf-net後的結果)

64-256筆結果時,資料量減少約28%。

BasicHttpBinding(啟用IIS動態壓縮)

資紏統計如下(第二欄來自先前測試BasicHttpBinding的數據,第三欄為BasicHttpBinding加protobuf-net,IIS啟用「動態內容壓縮」)

資料筆數增加時,protobuf-net的表現愈好,256筆時可減少約65%的資料量。但我注意到一點,128跟256筆時,BasicHttpBinding+protobuf-net的資料量比NetTcpBinding+protobuf-net還低(1469<1959、2423<4047)。探究原因,我們案例的測試資料且規則性,下圖為NetTcpBinding+protobuf-net的封包內容,其中可見大量Item0、Item1… 字串內容,類似重複資料也會出現在BasicHttpBinding+protobuf-net,由於IIS啟用動態壓縮,當內容重複性愈高,壓縮比愈高,是BasicHttpBinding+protobuf-net勝出的原因,若資料重複性低,資料量將會上升。

BasicHttpBinding(不壓縮)

資紏統計如下(第二欄來自先前測試BasicHttpBinding的數據,第三欄為BasicHttpBinding加protobuf-net,IIS關閉「動態內容壓縮」)

128-256筆時,改用protobuf-net資料量可減少約74%。

結論

針對BasicHttpBinding或NetTcpBinding,改用protobuf-net序列化可減少65%到28%的資料量。

WCF探勘10-InstanceContextMode與ConcurrencyMode

$
0
0

跟ASP.NET WebForm或MVC Controller一樣,WCF在接收Client的呼叫時,Server端必須建立一個Service型別的Instacne(執行個體)執行作業。在WebForm或MVC Controller裡,多採行「為每次Request建立Instance,處理完畢就抛棄」的策略,這與HTTP協定的無狀態(Stateless)特性有關。WCF Service因支援雙向呼叫等應用模型,故WCF Service Instance的生命週期管理比一般ASP.NET網頁複雜。

透過[ServiceBehavior(InstanceContextMode=…)],我們可以指定Service Instance採行以下三種策略:

  • PerCall:每次接受呼叫就新建一個Instance,跟WebForm或MVC Controller概念一致。
  • PerSession:為每個Session建立一個Instance,用該Instance處理該Session的所有呼叫。
    註:WCF Session的定義是從Clinet端new TheWcfProxyClient()開始,一直到它.Close()結束或Dispose()被回收為止。另外,要啟用Session需選用Ws*Binding、NetTcpBinding、NetNamedPipeBinding等管道,像BasicHttpBinding就不支援。(參考:WCF預設Binding種類
  • Single:從頭到尾只會建一個Instance,服務所有呼叫要求。

PerCall走的是無狀態(Stateless)哲學,所有呼叫動作彼此獨立無關聯,故可輕易透過擴充伺服器數量提高系統負荷量。當Instance建立成本較高,或在多次呼叫動作間需保留狀態,就必須考慮改用PerSession或Single方式。而雙向式WCF作業,Server端需要一個持續存在的Instance主動向Client發動呼叫,故需使用PerSession或Single。(延伸閱讀:WCF Sessions - Brief Introduction - CodeProject

除了InstanceContextMode,ServiceBehavior還有另一個參數ConcurrencyMode,ConcurrencyMode有三個選項:

  • Single:WCF在執行作業時會鎖定Service Context(包含Service Instance),限定單一時間只能由一條執行緒存取,如此可避免非同步作業衍生的資源存取衝突。但需留意如此限定對該Instance的所有作業只能循序執行,上演以前提過的ASP.NET大排長龍效應
  • Multiple:允許多執行緒同時存取,可達最高效能,但如共用Instance或其他資源,開發者必須使用lock等機制自行確保Thread-Safe。
  • Reentrant:類似Single,不允許多執行緒同時存取Instance,但該Instance向外呼叫其他WCF服務又回頭對該Instance發動的呼叫則不在此限,以避免Deadlock。
    延伸閱讀:Chapter 8. Concurrency Management

了解原理後,免不了要實驗驗證。我設計如下的Service1.svc.cs,Instance建立時以GUID隨機命名,用來區別不同的Instance。ShowInstanceAndThread()作業會延遲一秒再傳回Instance名稱及當下執行的Thread ID,延遲一秒目的在突顯呼叫為循序執行還是同時並行。在Service1類別我們加上ServiceBehavior Attribute,切換不同InstanceContextMode及ConcurrencyMode,以觀察不同模式下的執行結果。

using System;
using System.ServiceModel;
using System.Threading;
 
namespace WcfTest
{
    [ServiceContract]
publicinterface IService1
    {
        [OperationContract]
string ShowInstanceAndThread();
    }
 
    [ServiceBehavior(
        InstanceContextMode = InstanceContextMode.PerCall,
        ConcurrencyMode = ConcurrencyMode.Single
    )]
publicclass Service1 : IService1
    {
//Instance建立時隨機決定instanceName
string instanceName = Guid.NewGuid().ToString().Substring(0, 4);
publicstring ShowInstanceAndThread()
        {
            Thread.Sleep(1000);
returnstring.Format("{0}/Thread:{1}/Time:{2:HH:mm:ss.fff}",
                instanceName, Thread.CurrentThread.ManagedThreadId, DateTime.Now);
        }
    }
}

由於預設的basicHttpBinding不支援Session,web.config要修改一下:(註:我在本機測試,故忽略驗證問題,否則還需加上<security mode="None" />設定,相關細節請參閱前文)

<protocolMapping>
<addbinding="wsHttpBinding"scheme="http"/>
</protocolMapping>

WCF Client加入參照後,程式中同時建立兩個Service1Client()(在PerSession模式會在Server端對應兩個專屬Service Instance),先用client.Open()跟Server建立連線,之後用Parallel.For同時送出三次ShowInstanceAndThread()呼叫,由於非同步執行無法預期執行順序,這裡用了點小技巧,讓第一次呼叫Delay 50ms,第二次Delay 100ms,第三次Delay 150ms,確保三次呼叫先後送出:

static Task RunTest(string name)
        {
return Task.Factory.StartNew(() =>
            {
                var client = new WcfTest.Service1Client();
//呼叫client.Open(),確保WCF服務就緒
                client.Open();
                Parallel.For(1, 4, (j) =>
                {
//加上小小的延遲,確保先發j=1,再依序發2,3
                    Thread.Sleep(j * 50);
                    Console.WriteLine("N={0},J={1}->{2}",
                        name, j, client.ShowInstanceAndThread());
                });
            });
        }
 
staticvoid Main(string[] args)
        {
            var jobs = new List<Task>();
for (var i = 1; i <= 2; i++)
            {
                jobs.Add(RunTest("Client" + i));
            }
            Task.WaitAll(jobs.ToArray());
            Console.Read();
        }

準備就緒,來看看不同InstanceContextMode及ConcurrencyMode的測試結果。

1. InstanceContextMode.PerCall + ConcurrencyMode.Multiple

N=Client2,J=2->8f3e/Thread:17/Time:15:43:41.062
N=Client1,J=3->abf9/Thread:22/Time:15:43:41.063
N=Client2,J=1->d99a/Thread:11/Time:15:43:41.059
N=Client1,J=1->a66b/Thread:12/Time:15:43:41.059
N=Client2,J=3->21c7/Thread:14/Time:15:43:41.063
N=Client1,J=2->8131/Thread:15/Time:15:43:41.060

PerCall時每次呼叫用的Instance都不同,同時執行。

2. InstanceContextMode.PerCall + ConcurrencyMode.Single

N=Client2,J=1->99c9/Thread:11/Time:15:45:43.497
N=Client1,J=1->525a/Thread:9/Time:15:45:43.497
N=Client2,J=2->ac78/Thread:11/Time:15:45:44.506
N=Client1,J=2->0a2b/Thread:9/Time:15:45:44.505
N=Client1,J=3->2c51/Thread:9/Time:15:45:45.513
N=Client2,J=3->96be/Thread:11/Time:15:45:45.514

指定ConcurrencyMode.Single,出現Client2固定用Thread 11,Client1固定用Thread 9的結果,雖然Instance每次不同,但WCF鎖定的對象為整個Service Contexty,Client1/Client2的三次呼叫循序跑完,耗時三秒。

3. InstanceContextMode.PerSession + ConcurrencyMode.Multiple

N=Client2,J=1->9dae/Thread:11/Time:15:47:20.290
N=Client1,J=2->3a09/Thread:14/Time:15:47:20.293
N=Client1,J=3->3a09/Thread:13/Time:15:47:20.293
N=Client2,J=3->9dae/Thread:17/Time:15:47:20.293
N=Client1,J=1->3a09/Thread:9/Time:15:47:20.290
N=Client2,J=2->9dae/Thread:15/Time:15:47:20.291

看出PerSession的效果了,Client1固定用Instance 3a08,Client2固定用9dae,但六次呼叫同時跑完。

4. InstanceContextMode.PerSession + ConcurrencyMode.Single

N=Client2,J=1->df72/Thread:10/Time:15:48:22.361
N=Client1,J=1->26ab/Thread:9/Time:15:48:22.361
N=Client2,J=2->df72/Thread:10/Time:15:48:23.372
N=Client1,J=2->26ab/Thread:9/Time:15:48:23.370
N=Client2,J=3->df72/Thread:10/Time:15:48:24.380
N=Client1,J=3->26ab/Thread:9/Time:15:48:24.381

Client1固定用Instance 26ab,Client2固定用df72,各自的三次呼叫循序跑完。

5. InstanceContextMode.Single + ConcurrencyMode.Multiple

N=Client1,J=2->3df5/Thread:14/Time:15:49:22.760
N=Client2,J=1->3df5/Thread:9/Time:15:49:22.759
N=Client2,J=3->3df5/Thread:21/Time:15:49:22.764
N=Client1,J=1->3df5/Thread:8/Time:15:49:22.759
N=Client2,J=2->3df5/Thread:10/Time:15:49:22.762
N=Client1,J=3->3df5/Thread:13/Time:15:49:22.762

Client1與Client2共用Instance 3df5,六次呼叫同時跑完。

6. InstanceContextMode.Single + ConcurrencyMode.Single

N=Client1,J=1->8044/Thread:14/Time:15:50:16.621
N=Client2,J=1->8044/Thread:13/Time:15:50:17.626
N=Client1,J=2->8044/Thread:14/Time:15:50:18.628
N=Client2,J=2->8044/Thread:13/Time:15:50:19.631
N=Client1,J=3->8044/Thread:14/Time:15:50:20.637
N=Client2,J=3->8044/Thread:14/Time:15:50:21.642

Client1與Client2共用Instance 8044,六次呼叫排成一列,花六秒慢慢消化。

由以上結果,我們觀察到InstanceContextMode及ConcurrencyMode對Instance管理及執行順序的影響,可做為日後設計WCF服務的參考。

部落格版面微調公告

$
0
0

某部落格的萬年CSS版型採960px寬度基準,落後螢幕主流解析度多時,雖陸續多次接獲網友反應「部落格字太小,閱讀起來很吃力」,無奈版主懶惰成性,厚顏裝死至今。蒼天有眼,終於撐到版主視力退化到連自己都嫌字小,加上日前有網友留言反應,機緣成熟,千呼萬喚的改版總算來了。什麼,只有「版面微調」?唉… 算了,有總比沒有好。

既然目的在求解決字型過小不易閱讀問題,自當心無旁騖,不妄想兼做版面美化或CSS重構,追求花最小功夫最少時間的懶人極致。所以我只加了一組新規則:當視窗尺寸大於1024px,將字型與主欄寬放大一級;若大於1280px,則再放大一級。經粗略檢視,應該多少能改善字型過小問題。另外,還調了CSS讓文章標題明顯一點,這是另一個老覺得不順眼的地方。(說不順眼也忍了快十年,適應能力與惰性是成正比滴 XD)

草草修改,不確定有沒有造成部分版面不協調或閱讀困難,索性採「全民公測」模式,煩請大家幫忙找碴,遇到有問題的地方在本站或專頁留言通報,感謝~

WCF探勘11-雙工服務(Duplex Services)

$
0
0

除了從Client呼叫WCF服務取得結果,WCF也支援Server端反過來呼叫寫在Client端的方法(類似事件觸發概念),這種雙工(Duplex)模式算是WCF的一大賣點。Web API要實現類似概念得靠SignalR架構支援,直接內建雙工模式的WCF略勝一籌。

這篇文章,我們就來建立一個簡單的WCF雙工服務,實際體驗它的威力。

假設我們有一個ITimer報時服務,構想是在WCF Server跑一個PerSession Instance(關於InstanceContextMode.PerSession的意義請參考前文),當Client呼叫ITimer.Start(),ITimer服務就每隔一秒呼叫Client端的OnTick()方法,提供當前Server時間字串,直到Client呼叫Stop()為止。

擬訂好構想,實際在Visual Studion建立WCF Service專案,先定義ITimer,跟以前不同的是,ITimer需宣告SessionMode.Required指定Service Instance開啟Session模式,另外宣告CallbackContract=typeof(ITimerCallback),指定Client端必須實作ITimerCallback介面供Server呼叫。

using System.ServiceModel;
 
namespace WcfWas
{
    [ServiceContract(
        SessionMode=SessionMode.Required, 
        CallbackContract=typeof(ITimerCallback))]
publicinterface ITimer
    {
        [OperationContract]
void Start();
        [OperationContract]
void Stop();
    }
 
publicinterface ITimerCallback 
    {
        [OperationContract(IsOneWay = true)]
void OnTick(string time);
    }
}

ITimer.svc.cs長這樣:

using System;
using System.ServiceModel;
using System.Threading;
using System.Threading.Tasks;
 
namespace WcfWas
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
publicclass Timer : ITimer, IDisposable
    {
private Task task = null;
        ITimerCallback Callback = null;
        CancellationTokenSource canTknSrc = new CancellationTokenSource();
 
publicvoid Start()
        {
//透過OperationContext.Current取得Callback
            Callback = 
                OperationContext.Current.GetCallbackChannel<ITimerCallback>();
//使用CancellationToken控制Task執行何時結束
            var canTkn = canTknSrc.Token;
            task = Task.Factory.StartNew(() =>
            {
while (!canTkn.IsCancellationRequested)
                {
                    Thread.Sleep(1000);
if (canTkn.IsCancellationRequested) break;
if (Callback != null)
                    {
                        Callback.OnTick(
                            DateTime.Now.ToString("HH:mm:ss.fff"));
                    }
                }
            }, canTkn);
        }
 
publicvoid Stop()
        {
if (task != null)
            {
                canTknSrc.Cancel();
                task.Wait();
                task = null;
            }
        }
 
publicvoid Dispose()
        {
            Stop();
        }
    }
}

有幾個重點:

  1. 使用InstanceContextMode.PerSession。
  2. Start()由OperationContext.Current.GetCallbackChannel<ITimerCallback>()與Client寫的OnTick()事件搭上線。
  3. 每秒送時間回Client端的工作由Task.Factory.StartNew()另起一個執行緒處理,並用CancellationToken控制結束時機。(延伸閱讀:簡介.NET 4.0的多工執行利器--Task
  4. Stop()設定CancellationToken,並等待Task結束。
  5. 實作IDispose(),若Client端未呼叫Stop(),Instance銷毁前要強制結束作業。

要使用雙工服務時,必須選用支援雙工及Session的Binding(參考:WCF預設Binding),例如WsDualHttpBinding、NetTcpBinding,由於這些通訊協定預設啟用Windows認證會造成跨機器溝通的困擾,故修改web.config設定如下:

<bindings>
<netTcpBinding>
<bindingname="NoneSecurityNetTcpBinding">
<securitymode="None"></security>
</binding>
</netTcpBinding>
<wsDualHttpBinding>
<bindingname="NoneSecurityWsDualHttpBinding">
<securitymode="None"></security>
</binding>
</wsDualHttpBinding>
</bindings>
<services>
<servicename="WcfWas.Timer">
<endpointaddress=""binding="wsDualHttpBinding"contract="WcfWas.ITimer"
bindingConfiguration="NoneSecurityWsDualHttpBinding"></endpoint>
<endpointaddress="mex"binding="mexHttpBinding"contract="IMetadataExchange"/>
<endpointaddress=""binding="netTcpBinding"contract="WcfWas.ITimer"
bindingConfiguration="NoneSecurityNetTcpBinding"></endpoint>
</service>
</services>

將程式部署到IIS上(IIS才支援net.tcp),在WCF Client加入參照(記得要加上<security mode="None" />),使用以下程式進行測試:

//宣告一個類別實作ITimerCallback介面
publicclass OnTickHandler : WcfDuplex.ITimerCallback
        {
publicvoid OnTick(string time)
            {
                Console.WriteLine("Tick - " + time);
            }
        }
 
staticvoid Main(string[] args)
        {
//傳入OnTickHandler物件,建立InstanceContext
            InstanceContext ctx = new InstanceContext(new OnTickHandler());
//使用InstanceContext建構WCF Client物件
            WcfDuplex.TimerClient tc =
new WcfDuplex.TimerClient(ctx, "WSDualHttpBinding_ITimer");
//new WcfDuplex.TimerClient(ctx, "NetTcpBinding_ITimer");
            tc.Start();
            Console.WriteLine("WCFClient Start Timer");
//等待10秒
            Thread.Sleep(10000);
            tc.Stop();
            Console.WriteLine("WCFClient Stop Timer");
//等待五秒,確認不再有Callback
            Thread.Sleep(5000);
            Console.WriteLine("Test Done");
            Console.Read();
        }

雙工服務的Client寫法跟以前有些不同,為了讓Server反向呼叫Client端,需定義一個Callback Handler類別(即程式裡的OnTickHandler)實作ITimerCallback.OnTick。而建立TimerClient之前,要以OnTickHandler物件作為參數建構InstanceContext物件,再以此InstanceContext物件當參數建構TimerClient。如此,從Server端呼叫OperationContext.Current.GetCallbackChannel時才能跟Client端寫的OnTick()方法連結在一起。由於WCF服務同時支援WsDualHttpBinding及NetTcpBinding,故建構時需傳入Endpoint名稱指定通訊管道(參考前文)。

Start()後程式Thread.Sleep()等待10秒,這段期間,Server將每秒一次主動呼叫Client端的OnTick()方法印出當前時間。10秒後,Client呼叫Stop()並靜待5秒,確認Server不再觸發OnTick,種式結束。

測試成功,我也會寫WCF雙工服務囉!

WCF探勘12-WsDualHttpBinding的祕密

$
0
0

前篇文章試寫了WCF雙工服務,由於WsHttpBinding不支援雙工,故我們改用WsDaulHttpBinding及NetTcpBinding,分別用HTTP及TCP協定傳送資料。問題來了,大家都知道HTTP是單向的,Client端連上Server,每次送Request收Response後就銀貨兩訖,WCF服務端要如何主動呼叫Client端,難道要像SignalR一樣,使出Long Polling之類的奇技淫巧達成任務?答案是,不!Server得回頭開一條HTTP連線到Client端傳送Callback。跌破很多人的眼鏡吧?

打開Microsoft Network Monitor,執行前文的Timer.svc測試程式,錄得封包記錄如下:

172.28.1.1是Client端,172.28.1.227是Server端,執行期間除了有172.28.1.1呼叫172.28.1.227/WcfWas/Timer.svc的記錄,還有另一個由172.28.1.227呼叫172.28.1.1/feb5ab41-cd06-4678-a485-185503863379的連線。你沒看錯,Server端主動連上Client的80 Port,送出一個HTTP Request並接收回應,Client端也變成一個迷你Web Server了~

觀察由Server連至Client的反向HTTP連線,我們看到每秒四個封包的來回,並由SOAP內容中看到OnTime字眼及由Server傳回的time字串。

而由Client連至Server方面,我們也觀察到一秒一次四個封包的/WcfWas/Timer.svc呼叫。所以每次Server呼叫Client的同時,Client都要再呼叫一次Server。

換言之,每秒一次的Tick,總共要產生 235+65+639+152(Server=>Client) + 211+65+698+224(Client=>Server) 8個封包共 2,289 bytes的傳輸量。更重要的,這裡隱藏了一個嚴重問題:一般而言,若非Intranet環境,Client與Server很難直接對連,傳輸路徑少不了得通過一道以上的防火牆。Server要對外服務,防火牆允許外界連入不是什麼問題,但Server要逆向穿過保護Client機器的防火牆,難度可高了,而且連Windows自身的防火牆也可能有意見,故此一做法在現實Internet世界的可行性極低。故WsDualHttpBinding只適用Intranet可直接連通的環境,還得留意Client的防火牆軟體作梗。

看完WsDualHttpBinding有點掉漆的表現,再來看看NetTcpBinding:

從頭到尾,一條連線搞定。當然,Client/Server兩台機器要能直接連線,不能有Application Firewall/Proxy阻隔。再看看傳輸封包:

每秒一次的Callback,兩個封包,95+40=135 bytes,收工!

由以上的觀察,WsDualHttpBinding雖然名字中有HTTP,但穿透性沒有比較好,而每次Callback所傳輸的封包數及資料量(2289 vs 135)都遠不如NetTcpBinding有效率。要實做雙工服務,建議使用TCP為宜。由於使用WsDualHttpBinding有利於偵錯,故可以在開發期間用WsDualHttpBinding,上線時再改設定換成NetTcpBinding,魚與熊掌兼得,享受WCF架構的彈性。

最後的疑問,WsDualHttpBinding很廢,NetTcpBinding很難穿防火牆,難道WCF雙工服務註定無緣用在Internet環境?

除了預設Binding,無比彈性的WCF當然允許開發者自訂Binding,像Silverlight就開發了PollingDuplexHttpBinding,使用Polling技巧克服HTTP只允許單向的限制(原理可看這裡),但無法應用在非Silverlight程式。好消息是,.NET 4.5起,WCF增加生力軍,NetHttpBinding支援透過WebSocket解決雙工需求,留待下期分解。

TechDays 2015雜記-D1

$
0
0

一年一度的TechDays又到了,今年沒全程參與,基於對自己記憶力的不信任,聽的課程沒往年多,但還是胡謅亂記幾筆備忘,大伙兒隨便瞧,發現有錯再幫忙指正。

Keynote

簡略地帶出今年的重點在Azure、DevOps、UWP App、Office 365、PowerBI,雜記如下:

  • 微軟的改變:從MS到Apple發表會站台談起,MS已不執著於作業系統,願意在各種裝置、作業系統上推服務(Office on iOS/Android、Linux on Azure)。話峰一轉,忽然提了MS「不會看你的Email、不會看你的Data」(馬上低頭看了隨身攜帶的藍色石蕊試紙,果然有微微變紅 XD)
  • 雲服務的首要重點:建立信任感(Trustworthy),Azure已符合幾種主要規範,例如:軟硬體Patch推出後要在30天內完成部署
  • 30%虛擬機使用Open Source(Node.js, Linux, Docker...) Azure在這方便支援無虞,目前與Amazon並列雲端業者的兩大龍頭
  • Azure已有19個DataCenter、今年會再增加3個(印度),每個Data Center面積可停32(?)台747,容納五萬台伺服器
  • 金石堂個案:策略由增加伺服器加大頻寬-->移至雲端。評估對象包含台灣三大電信商、Google/Amazon/Azure。最後決定Azure的主要理由:中國機房、原廠服務,成本Azure:Amazon:Google = 1:2:5,而Azure PaaS有較高的自主性。
    成效:兩個機房減成一個,少50%,費用少80%,上線時程快10%
    2015金石堂電商平台會更向Azure移動:1)PaaS+IaaS 2) .NET + Java,其他O2O也會陸續搬過去
  • MS Operation Management Suite: 單一介面管理AWS/Azure
  • DevOps: 持續交付、持續監控、持續學習
    Developer與Tester角色混合,Data-Driven Decision
  • 微軟的轉型:Sprint 三週一次衝刺、 Dlivery 三個月一次交付、Release 一年一個大版本(原本週期為三年)
  • DevOps整合展示:將MS Project 甘特圖的Task發佈到TFS轉成工作項目,原始碼修改可跟工作項目建立關聯方便追蹤,一個點擊就完成程式上版,E2E測試過程出錯立即抓圖產生Bug單。結合PowerBI: 視覺化Bug項目,有多少人正在修,方便PM與開發團隊溝通
  • 大陸的應用實例:中國人民保險公司(?)的1000人開發團隊使用TFS機制實踐DevOps,其中95%為 Java開發者
  • Application Insights:持續線上測試、監控,產生即時報告(延伸閱讀
  • Cortana Analytics Suite:就是前陣子流行的猜年齡網站及最近的明星臉測試,背後的Oxford Project(人工智慧/機器學習)延伸成新服務
  • 個案研究:車聯網/勤崴國際 KingWay
    • 尖峰離峰流量差數十倍,即時負載調度,自動擴充伺服器數量
    • 資料處理:Event Hub(Message Queue,尖峰也不會掉資料)
    • 路況預測:Blob資料儲存,HDInsight/Machine Learning
    • 樂客車聯網:用統計值找出週末雪隧較鬆的時段
  • IoT物聯網仍處戰國時代,缺乏一致的標準,大家都在探索
    • 現場展示:分貝計(Device)->Event Hubs(資料輸入)->Stream Analytics(資料處理)->Power BI(資枓呈現),資料量每秒六百筆
  • Windows資安功能展示:
    • BYOD必須註冊納管才能存取企業檔案,存檔時自動加密
    • Word存檔時出現鎖頭Icon(文件使用公司範本,存檔自動加密),不允許的應用程式(Wordpad)打不開、分享到FB/Evernote時會被阻擋並通報
    • Enterprise Data Protection結合Azure RMS,給合作廠商的文件也可加密
    • 追蹤系統記錄文件在何時何地(地理位置)被開啟,支援iPhone/Android
    • 面孔識別登入
    • Outlook Web採多因素驗證(在陌生機器登入時需簡訊驗證)
    • 病毒檔案Blocked by Device Guard:白名單法,驗證過的Windows程式及App才能執行
    • Windows Hello 生物特癥:指紋、臉孔、虹膜(需硬體支援)另外結合TPM晶片,搭配指定裝置才能登入
    • Advanced Threat Analytics ATA,類似IDS,主動找出可疑活動供管理人員瀏覽
    • Enterprise Mobility Suite: 解決Gmail/Evernote 工作與私人空間模糊、BYOD問題
  • 2-3年內達到10億台Win10裝置,Universal Windows Platform(UWP) App前景可期
    84" Surface Hub/XBox/HoloLens/PC/Mobile/Devices+IoT,裝置繁多,寫一隻App就打通關
  • UWP Bridge: Objective-C/Java
  • Microsoft Edge本身就是UWP App,傳統網頁用IE11(用Group Policy控制)
    • 閱讀模式:只顯示核心內容(需要HTML5標籤配合,範例: Inside網站Eric文章
    • 閱讀清單:稍候閱讀,離線可用
    • 接受使用者Feedback決定新功能開發順序
    • Edge支援Gamepad API玩網頁遊戲,範例:Flight Arcade 3D飛行遊戲
    • 需要ActiveX(讀卡機)的網頁自動提示以IE11開啟
  • Office 365
    • Office 365 Video平台 ISO27018安全規範,可於企業內部分享影片、頻道、串流播放(e-Learning追蹤上課與否),可視為微軟版YouTube
    • 共同編輯功能:像Hackpad,但編輯軟體是Word
    • 跨平台跨裝置:PC/Android/iOS
    • Skype Tanslater:巫術等級的即時口譯,六種語音,50種翻譯
    • Social Engagement(CRM Online)
    • Outlook + Uber整合,Schedule/地址/Uber:靠Uber開發的Office Add-ins實現
    • Skype Meeting Broadcast不需要安裝軟體直接線上觀看

TechDays 2015筆記-D2

$
0
0

ASP.NET 5 by 保哥

完整投影片在這裡

  • ASP.NET 5為新一台ASP.NET,跨平台,Open Source,適合雲端也可在本地跑(未來還會支援Windows Server 2016 Nano Server、Docker),模組化,全新架構
  • 為什麼要砍掉重練?
    • ASP.NET 1.0已有15年歷史,包袱太重
    • 擺脫System.Web.dll,採模組化載入,減少資源使用,雲端執行較省成本(費用與CPU/RAM用量成正比)
    • 可與Apache、Self-Host其他HTTP管道整合,應用範圍變廣
  • .NET Execution Environment(DNX) ,新一代.NET Runtime,跨Win/Mac/Linux
  • 版本選擇:
    • .NET Framework(CLR), 元件完整,發行更新週期長, Windows Only。
    • .NET Core(CoreCLR) 為CLR子集合,元件較少但跨平台。CoreFX透過NuGet下載,有用到才載入,Windows版已好, Linux/OSX仍在開發中
    • Mono: 跟CLR相容,直接丟.EXE就可以跑,非官方解決方案
  • ASP.NET5 Beta8起(10/5 Release, RC 11月)不再加功能,只加文件
  • 開發伺服器:WebListener(Win Only,將來可能被淘汰)、Kestrel(跨平台)
    還是可以跑在IIS,同時支援傳統與新一代的Pipeline,故HttpHandler、HttpModule可以繼續用
  • 示範:
    • 使用CMDER
    • yo aspnet 建立ASP.NET 5專案樣板(此做法跨平台,但便利性不如VS2015)
    • dnu restore 要求NuGet下載用到的套件
    • dnu build 建置專案
    • code . 用Visual Studio Code開啟專案
    • dnx web 執行專案
  • 開發環境的變革
    • 改檔案後不需手動建置(背景偵測檔案改變就觸發編譯,缺點:難以掌握檔案何時Build完)
    • 前端後端分工整合:LESS、Sass、Typescrpit、Bootstrap、KO、NG、ReactJS、Gulp, Grunt, npm, Bower, Yeoman... 延伸閱讀 Gulp, Bower, Grunt是什麼鬼啦?
    • 有些指令如Environment.Machine使用時要小心,DNX 4.5可用、DNX Core 5.0不行,若
      project.json frameworks指定兩種平台要通用,將無法編譯。應改為二者共通的替代做法,或移除DNX Core 5.0
  • ASP.NET專案結構
    • .sln仍在
    • nuget.config指定packageSource(可切換來源改抓新版)
    • global.json projects指定目錄下有src及test兩個目錄,找到的專案將自動匯入sln,但已匯入的專案不會因修改projects消失
    • .xproj(取代.csproj),採用負向表列,排除的才要註明,跟csproj一樣可用於MSBuild。
      自動掃瞄檔案匯入專案,缺點是會不斷掃瞄,若機器不夠力會讓你想哭(註:我不怎麼喜歡這個點子,但這似是前端開發工具的主流玩法)
    • 大部分專案設定移入project.json,內容與Gulp共用(Node.js)
  • project.json結構
    • webroot: 靜態檔案的根目錄,預設值是wwwroot,裡面都是不需要編譯的內容
    • userScrectId: 不能重覆,開發階段使用
    • version: 1.0.0.-* 版號
    • dependecies: 參照的套件、模組(都來自NuGet)
    • commands:
      "web"/"kestrel",定義命令。"ef": "EntityFramework.Commands" -> dnx ef --blah foo.bar
    • frameworks: 平台對象
    • exclude: 不包含的資料夾wwwroot, node_modules, bower_commpents,不編譯不處理
    • publishExclude: "node_modules","bower_components", "**.xproj", "**.user", "**.vspscc"
    • scripts: "pubish":["npm install", "bower install", "gulp clean", "gulp "min"]
  • gulpfile.js <== css, js壓縮程序,用Node.js跑
  • config.js Connection String、AppSettings...
    wwwroot下還是有web.config,但只給IIS用,非IIS時無視
  • Startup.cs 很重要,取代global.asa的角色
    • 建構式
      if (env.IsDevelopment()) builder.AddUserScrets() <= 連線字串或Path,各開發機不同,執行時用screts.json覆寫config.json
      另一招,用OS環境變數覆寫config.json的設定
    • ConfigureService() 服務設定
      註冊EF、SQL元件、DbContext、FB/MSAccount登入整合
    • Configure()相當於Application_Start
      ASP.NET如何執行,開BrowserLink()、ErroPage()、Use*/UseMvc(),跟Pipeline有關,順序很重要
  • 觀念解析(與傳統ASP.NET差異很大)
    • 以服務為中心,用DI注入服務元件,有不同生命週期
      *services.AddSingleton<IMyApp, MyApp>()
      *services.AddScoped<IMyApp, MyApp>()
      *services.AddTransient<IMyApp, MyApp>()
  • Middleware層層套用的觀念釐清
    • loggerFramework.AddConsole()第一順位,保證大家都能用
    • UseStaticFiles()在UseIdentity()之前,靜態檔案不需驗證就能存取
    • 若UserDirectoryBrower()在UseMvc()之前,MVC Routing就失效了

註:綜合之前搞Hackpad小玩過ASP.NET 5的經驗,它還很新很嫩,元件跟文件還有不少缺口,現階段嚐鮮即可,假以時日方可委以重任。

Microsoft Edge by Eric

  • 瀏覽器發展時間軸,IE6-IE7時代幾無敵手,IE8時Chrome出現,IE6/7/8變臭(說好不提IE6/7/8 XD)
  • IE11之後將持續更新改進,但不會有IE12,IE13了
  • 裝置愈來愈多元化:Mobile/PC/XBox/Surface Hub/HoloLens/Wearables+IoT
  • Why Edge?
    • IE包袱太沈重,原本為Windows量身打造,跟OS綁太緊,作業系統不升級IE就不能升級。
    • 為保留相容模式必須付出龐大代價,加了新功能舊版邏輯卻不能丟,程式愈來愈肥
    • IE只為桌面設計,硬體需求、連線狀態思維不同
  • Edge啟動畫面預設呈現MSN新聞、氣象(建議內容)、最常瀏覽網站(Top Sites)
  • Demo: 開啟INSIDE網站,進入閱讀模式(其他瀏覽器也有),頁面只留內容,Banner/側邊選單消失。前題是網頁需正確使用HTML5標籤<content><article><aside><section><h3><h4>...。另外,亦可加入閱讀清單(READING LIST),可離線閱覽
  • Edge內建筆記功能,加入閱讀清單仍能保有筆記內容,可分享到其他APP
  • 無縫體驗,XBox/HoloLens上都有Edge
  • IE11相容模式
    企業情境管控:企業內網預設啟用企業模式
    若網頁需要整合ActiveX(例如:Web ATM需讀卡機):彈出提示開IE11,UI可勾選記憶起來以後一律用IE11開
  • 不再支援原生元件(ActiveX...)
  • Edge是UWP App,可跨裝置,透過市集升級
  • 相容性 vs 互通性
    用戶希望用了十幾年的網站跟用HTML5的網站都要能繼續用
  • 有很多網站開發時只針對行動Safari,用FF/IE看就慘不忍睹,Edge開啟接近iPhone所得
    當年IE Only,如今iOS Only,都一樣糟糕
  • Edge的功能採用投票制決定採納與否及開發順序 https://status.modern.ie
  • 當代前端攻城師須知:參考
  • 很酷的展示:開Visual Studio UWP App專案,用EntryPoint,ContentUri指向網站,當場包成UWP App。執行於Hosted Web,透過Windows物件可以存取相機、使用者通訊錄等資源
    Windows.UI.Popups.MessagesDialog(...) 顯示對話框
    Windows.ApplicationModel.Appointments.Add... 加入行事曆
  • Edge 64bit Only(無ActiveX相容的必要性),唯一保留的二進位擴充套件只有Flash

TechDays 2015筆記-D3

$
0
0

只聽了下午場,連續三個半天,今年我參加的應該算TechHalfDays 2015(誤) XD

Xamarin

  • 講師分享了Maker(創客)經驗
    Maker = 連結「想」與「做」的過程,有助找到答案並解決問題,更可能誘發新的創意與發明,是當前開創性動力的來源
  • HackNTU的專案:手貼玻璃識別開門,Intel開發板+Arduino-->電磁開關開啟3D列印出來的門
  • C# on Arduino的選項
  • 實機展示:netduino 3 wifi 板子控制LCD模組顯示文字(LCD模組Driver自己寫),SignalR網路聊天室,netduino接收聊天內容顯示在LCD。
  • Xamarin要解決跨裝置開發App的困擾
    開發環境多樣:Swift/Xcode、Java/Eclipse、C#/Visual Studio
    Xamarin用C#寫程式,轉譯成三個行動平台的原生程式
    iOS上用AOT方式編譯、Android用JIT編譯
  • 優點共用邏輯抽取成為共用程式庫,不同平台App只差在UI,70%-85%程式碼可共用
  • Xamarin.Form<=相同的UI模型,95%的程式碼共用
  • 插曲:線上聊天室好像有人開始試玩XSS XD
  • 自動測試功能,測試雲(Test Cloud):上傳UI測試,自動以不同裝置測試,回報結果。

SQL經典效能問題案例實戰

  • 來自微軟CSS部門SQL茶包射手的心得分享,完全是我的菜
  • 介紹效能瓶頸緊急處理步驟,有什麼工具可用?分享四個案例
  • 案例1 CPU使用率飆高
    SQL 2000->SQL 2008R2,VB6程式,上線前測試正常,效能OK。正式上線後,CPU居高不下。
    • VB6程式使用Ad-Hoc語法,編譯SQL指令頻率過高,CPU 100%
    • 好用工具:SSMS的Activity Monitor,活動監視器
    • 觀察到CPU%高、等候項目多、批次執行時間長
    • 等待類型:ASYNC_NETWORK_IO (等待網路傳輸)、CXPACKET(出現時代表SQL大量平行處理)
    • 資源等候(Resource Waits):等侯類型Network IO/Logging/Compilation (前幾年講Blocking的簡報裡有詳細介紹,搜尋未獲)
    • 聚焦最近且費時的查詢,觀注每分鐘執行的次數,找到語法顯示執行計劃,遺漏索引的詳細資料
    • 除了用活動監視器,也可跑SQL查詢系統檢視、資料表找出可疑的活動
    • SQL Server Property/進階/平行處理原則的成本臨界值,預設5,平行處理則的最大程度: 0不限CPU、1每個批次最多用1顆(延伸閱讀:認識平行(parallelism)處理,以MAXDOP、cost threshold for parallelism與max degree of parallelism選項為例
    • 舊版升級的SQL,有個「參數化」屬性:簡單->強制,相近的查詢共用編譯計劃,減少編譯時間
    • optimize for ad hoc workloads->如果Ad-Hoc的比重極高,可考慮,但是雙面刃,不適用.NET AP/SP(延伸閱讀:[SQL SERVER][Performance]別忘記開啟optimize for ad hoc workloads
    • CPU偏高觀察重點:
      • 連線數是否大量增加(不要漏掉壓力測試)
        分散使用量(調整商業邏輯)、升級硬體
      • 是否同時執行大型批次作業
      • 舊程式,使用太多AdHoc查詢
      • 執行計劃不佳?定期更新統計資訊、大量異動後即刻更新
      • 特別慢的作業:缺索引?分多批執行,離峰再做
  • 案例2 股市公司重要核心SP,執行時間從5ms升到3分鐘,軟硬體沒動,有更新統計資料
    常發生於營業時間開始,重啟SQL後問題消失(執行計劃跑掉)
    • 模擬:很大的資料表,WHERE prodId = @prodId,
      第一次查詢用Index Seek
      更新筆數過20%引發自動更新統計(Profiler可錄到AutoStat事件),SP會重新編譯執行計劃
      第二次查詢變Index Scan
    • Parameter Sniffing議題 (這個前陣子剛好探討過)
    • 解決之道:將執行計劃固定下來(Plan Guides
    • Parameter Sniffing處理要點
      • 找出TOP 5語法,將執行次數多、耗用資源多者挑出來調校,改善查詢計劃(例如:Index Scan)
      • 使用Partition Table
      • 一些Plan Guide
        Trace Flag 4136, OPTIMIZE FOR UNKNOWN
        OPTION (HASH JOIN)強迫使用雜湊聯結
        OPTIMIZE FOR UNKNOWN
        OPTIMIZE FOR (@CITY='Taipei')
        @hints = N'OPTION (QUERYTRACEON 4199, QUERYTRACEON 4136)'
  • 案例3 少數程式開發人員有sysadmin權限,部分開發人員有View System State權限
    某幾個系統更新後,系統效能愈來愈慢,但看不出任何硬體瓶頸
    • 根源:開發人員開了SQL Profiler玩吃到飽,所有SQL動作全都錄
    • 活動監視器的資源等候(Resource Waits)可以看到Trace Wait
    • 解決辦法:
      使用Server-Side Trace取代Profiler,Profiler UI先設定好條件,再匯出成Server-Side Trace。
      Server-Side Trace造成的負荷比Profiler UI小很多。
      記得要限DB ID、AppName,只特定追蹤特定的AP活動,減少負擔,結果較易判讀
    • 可排Job定期清理Server-Side Trace
  • 案例4 程式跑幾天後愈來愈慢,幾秒變十幾秒,特定單號出現Timeout,愈來愈多單號Timeout,重啟後問題消失
    • 嫌犯:Memory或Blocking
    • 模擬:DBCC TRACEON 1244 <=不要自動升級鎖定範圍,故意產生更多鎖定。開啟Transaction執行更新指令後,不Commit也不Rollback,留下一堆Lock
    • 用活動監視器封鎖者、源頭封鎖者
    • KILL封鎖者 Process前要注意:
      > 若它已耗用大量資源,Rollback也需耗用大量資源(可達1.5倍)。用總IO排名報表查詢
      > 刪除Process會不會影響前端作業?
    • 案例:Running->Rollback,等了一個小時,使用重覆Kill導致Deadlock,最後只能重啟SQL
    • 常見嫌犯:Idle時間長,Sleeping/Awaiting Command、IO高(Reads/Writes)時間又長,容易造成Blocking
    • 重建索引會產生Lock,大型資料表可能得以小時計,注意會不會影響其他程式
    • Transaction記得加Error Handling,一出錯馬上Rollback

跨國重大SQL管理實務

  • 趨勢科技實例分享,一位國際DBA的日常(只聽了五分鐘就發現自己在越級打怪,只能看熱閙長見識)
  • High Available的多種選擇:Always On、DB Mirror、Replication、Log Shipping
  • Log Shipping沒什麼人用,其實很強大,機器斷線很久也能繼續接關
  • 很華麗的Demo,資料庫横跨美國、日本、台灣
  • Always On也可以做Two Way Replication,祕技:Distributor拉出來
  • 防止有人將Recovery Mode由Full->Simple,DDL Trigger是一種做法,另一種方法是建Mirror在DB加上鎖定
  • 實務上DBA作業時很少用UI,不用精靈,幾乎全靠指令完成
  • Repication沒什麼除錯工具,全靠指令logread.exe,去Publisher抓資料,操作Distributor讀Tracton Log
  • Distributor Agent Profile千萬不要用預設值
    *Skip Error: 略過目的資料被改掉Replication失敗,避免中斷。某組設定自2010至今Replication沒壞掉過,不曾Initial(一次要十幾個小時),但要小聲說,不要被SQL聽到,否則… XD
  • Replication重做前要清設定,不然會建不起來,像是備份資料還原後冒出發行集,UI砍不掉(Pubisher缺Distributor之類的狀況)sp_removedbreplication @dbname = '...', @type = 'both'
  • Log Shipping很古老很好用
    Commit或未Commit都送,Mirror/Always On只送Commit的結果,需要AD認證處理Log檔案複製,類似MSDTC,機器間要相互認得
  • Log Shipping/Replication目的資料庫名稱可以不同,DB Mirroring/Always On不行,可以1對8
  • 當心Job用的是當地時區
  • 一個Log Shipping升級到Replication的複雜示範(超出我能力範圍,看不懂)

WCF探勘13-支援WebSocket的NetHttpBinding

$
0
0

探討WCF雙工服務時發現WsDualHttpBinding不實用(Server會回頭連Client的80 Port)、NetHttpBinding難穿防火牆,二者均難應用於Internet,有實作Polling的Silverlight版Binding又無法用於其他程式,查了資料,才發現.NET 4.5+增加了一個神奇的NetHttpBinding,支援WebSocket!

為什麼在研究預設Binding時會沒看到它?難道是我人呆眼瞎?重看.NET 4.5的Configuring System-Provided Bindings文件,還真的沒有耶 :P 猜想是.NET 4.5直接沿用舊版文件,沒補上新項目的緣故。用力再查過一回,在System-Provided Bindings發現另一份更完整的版本,就包NetHttpBinding、NetHttpsBinding等幾個先前未提過的Binding,這部分日後再來補完。

而依據文件的介紹:

NetHttpBinding 是為了使用 HTTP 或 WebSocket 服務而設計的繫結,其預設會使用二進位編碼,並會偵測其所搭配使用的是要求-回覆合約還是雙工合約,改變行為配合,也就是針對要求-回覆合約使用 HTTP,並針對雙工合約使用 WebSockets。 使用 WebSocketTransportUsage 設定即可覆寫這個行為:

  1. Always-這會強制使用 WebSockets,甚至用於要求-回覆合約。
  2. Never-這會避免使用 WebSockets。 嘗試將這個設定用於雙工合約會導致例外狀況。
  3. WhenDuplex-這是預設值,行為方式如上所述。

沿用前文的Timer.svc範例,要改用NetHttpBinding的很簡單,將endpoint的bind改成"netHttpBinding"、一切搞定!

<services>
<servicename="WcfWas.Timer">
<endpointbinding="netHttpBinding"contract="WcfWas.ITimer"/>
</service>
</services>

照慣例,開啟MNM觀察封包驗證效果:

觀察到二者建立連線後沒多久,Server便傳回HTTP 101,Switching procotols,要求升級成WebSocket。

之後Server端每秒一次的Callback,傳送的內容已不再是HTTP。第一次Callback傳回140 Bytes:

第二次起降到95 Bytes:

由以上結果,證實NetHttpBinding採二進位編碼並可使用WebSocket協定的特性,是可用於Internet的WCF雙工服務解決方案。

註:要使用WebSocket,Client、Server需為Windows 8、Windows Server 2012以上版本(或IIS Express 8 on Windows 2008 R2)。參考

TFS Power Tools導致桌面凍結

$
0
0

TFS Power Tools有個好用功能,在檔案總管加入右鍵選單提供簽入、簽出、版本比較等TFS操作,檔案、資料夾圖示也會加上最新版本(綠三角)、待簽入新増(紅十字)、待簽入修改(鉛筆)等狀態標示,不需開啟Visual Studio或TFS Explorer就能管理,十分方便。

但自從安裝後,電腦偶爾會發生桌面當住,滑鼠鍵盤全無反應的狀況,此時只能強制重開機或找另一台機器遠端桌面登入成其他使用者將當explorer.exe砍掉,狀況才會解除。

一開始不明所以,當機當得莫名其妙,直到有一次在事件檢視器看到當機期間TFSShellExt噴出大量警告及錯誤事件,才算有了眉目。

警告訊息為:TFS Shell Extension event:\n More than 5 failed calls to com provider.
錯誤訊息則為:TFS Shell Extension event:\n Failed to start COMProvider while calling CheckWorkstationCache.

用關鍵字搜索,只在TFS 2012 Power Tools討論區找到相關討論(在套件下載頁討論區找標題TFSSHellExt hangs explorer.exe after standby),問題在近兩年前就有人回報,陸續獲得不少回應補充,資訊不少但有點龐雜,總結歸納如下:

  1. 狀況多發生於睡眠/休眠的Windows被喚醒後,有網友發現連TFS的VPN斷線、鎖定螢幕時也可能發生
  2. 症狀為explorer.exe凍結,滑鼠鍵盤沒反應,事後可在事件檢視器看到大量TFSShellExt錯誤
    我自己的經驗是「其實桌面仍會回應,但速度慢到嚇人」,我曾耐著性子慢慢滑慢慢等,耗掉五分鐘終於打開工作管理員把Explorer砍掉解除危機。猜想應是Explorer陷入瘋狂迴圈,才會無暇回應使用者操作 。
  3. TFS 2012 Power Tools與TFS 2013 Power Tools都有人回報類似狀況
  4. MS RD於2013/11/28證實此屬TFS 2012 Power Tools已知Bug並能重現,但尚未修補好(TFS 2013 Power Tools推出於2014/4,問題仍在)
  5. 有幾種解決方法
    • 使用遠端桌面登入將explorer.exe砍掉
    • 強制重新開機
    • 使用TASKKILL /s <ip_address> /im explorer.exe從遠端砍掉所有Explorer
    • 移除TFS Power Tools的「Windows Shell Extension(x64)」功能
    • 砍掉tfs*.exe(TfsComProviderSvr.exe)讓Explorer恢復正常(但下次休眠會再發生)
    • 發揮無比耐心,每一步操作等數十秒,開啟工作管理砍掉explorer.exe

評估後,自已透過檔案總管處理TFS作業的機會不多,導致桌面當機的頻率不低,且處理起來頗棘手,只能忍痛移除Windows Shell Extension功能,等待官方修補好再裝。

2015五分山馬

$
0
0

路跑賽的正式名稱是2015基隆市市長盃暨忠孝獅子會基福馬拉松,但拎杯並不在意它是「立X洗衣液我是歌手」還是「X島媚腿機武媚娘傳奇」,五分山,我來了!

五點半趕到暖暖運動公園,看時間尚早便去晃了一圈。園區依山而建,規模不小,有網球場、環狀競速溜冰道等設施,但草長得挺高,帶點荒涼感。公園旁貌似汽車廠商,停了好多同款式的車子,有些還包著車套,應該是新車吧。

由於公園依山而建,缺少大塊平坦腹地,會場設在公園入口處,大會服務站及團體帳篷沿著車道列成一排,盥洗區及寄物區則設在公園管理中心旁邊。

看到少見的「礦泉水牆」,具備高度平行處理能力,能有效縮短工作佇列(Job Queue)長度,很出色的架構設計。

陸續有長官上台致辭,原本以為會拖到起跑時間,沒想到長官們很識趣,個個言簡意賅,劇情急轉直下八成讓主持人亂了方寸(誤),莫名地比預定時間提早五分鐘起跑。

起跑沒多久,就沿著東勢街爬上彎蜒的陡上路段,來到基平隧道。

隧道好~~~ 長~~~ 啊~~~有種怎麼跑都看不到盡頭的感覺 orz

跑出隧道,呼吸到新鮮空氣,但不到五百公尺就要折返,有種才逃出來又被抓回去的絕望感。算一算,今天的全馬有超過五公里是在隧道裡度過的,是一項全新體驗。

跑完隧道右轉進產業道路準備轉往106縣道直攻五分山氣象站。半路看到熟悉的背影,原來是石碇馬遇過的蜘蛛人先生。

跑完21公里,終於到了五分山公路的入口,之前開車來過兩回,今天首次要用雙腿征服這4公里的陡坡。順便讚美一下Fenix 3,隧道裡收不到訊號還能繼續計算速度、距離(猜想是用步頻估算),而且頗精準,歷經五公里隧道後里程也沒跑掉。聽身旁的跑友在聊,他的GPS錶過完隧道就平白多冒出五公里,本次超長隧道測試由Garmin勝出。

上山途中看到三四隻老鷹在空中盤旋,可惜手機拍不來,拍成類似鏡頭入塵的小黑點。

五分山的展望好得沒話說,可以俯瞰整個基隆、大台北,今天視野不算好,但遠方的基隆嶼仍清可辨,美景無價,爬這麼辛苦也值得!

坡坡爬了好久,怎麼雷達站還在那麼遠的山頭?目睹此幕,身旁有跑友都快崩潰惹~ XD

接近山頂,回首看見彎蜒山路上全是跟我一樣瘋狂的跑友,跑42公里兼爬升700米非正常人應有的休閒活動,所幸吾道不孤矣。

25K抵達全程最高點,五分山氣象站。雷達球體被蘇迪勒颱風吹壞了,見不到雷達球地標是小小的缺憾。

忘了提補給,我跑中段班,補給站供應充足,西瓜、蕃茄、香蕉、葡萄、檸檬、豆干、起司蛋糕、銅鑼燒、盬、梅粉、水、運動飲料、肌肉噴劑一應俱全。我愛小而美賽事!

出門前看氣象預報說今天紫外線指數高達9,還好天公作美,10點半後太陽才算完全露臉,已屬不錯的跑馬天。(最近幾場運氣都不錯)

過了雷達站就幾乎一路下坡了,趁體力尚足,催了點油門一路超車,最後5:13:32完賽,1255名選手中排名466。

很快地領了完賽成績(附資料夾,按個讚)、獎牌毛巾環保提袋,還有一套右下角印著跑者姓名的郵票套票,雖然版面設計採長輩分享祝福圖片風,擁有列印自己姓名的郵票套票還是一件很酷的事。

完賽成績印有各感應點的時間及排名是一大進步,我的成績屬漸入佳境型:
基平隧道 00:49:50 (644) –> 滴子水路 01:48:16 (671) –> 雷達站 03:17:37 (511) –> 終點 05:14:14 (472)

在場邊樹蔭下乘涼拉筋,看到還有一半以上寄物袋的主人還在路上奮鬥,心中燃起小小的成就感 XD

充滿獅子味的獎牌很性格,呵!第29馬入手~

附上高度圖以茲紀念 :P

C# TypeScript雙頻式View Model型別程式產生器範例

$
0
0

NG筆記3-使用TypeScript一文曾提及「另建程式碼產生器專案,將ViewModel規格轉成JavaScript(或TypeScript)、C#類別」的做法,方便Client及Server端共享一致的強型別ViewModel,規格如有更動,重跑程式產生器就能同步更新。

日前網友Ark詢問,有無上述做法的實際範例可供參考。先前其他專案也曾有類似程式產生器需求,手邊有運行多時的實例,但綁死過多專屬邏輯,複雜度過高難以抽離重用,算一算手邊的確缺少一組能獨立運行的淺顯範例,索性另起一套簡單的概念驗證(Proof Of Concept)範例,方便有興趣研究的同事網友參考。

範例程式碼我已放上Github,有需要的朋友請自取。以下則為簡單說明:

  1. 解決方案有三個專案:
    • CodeGen:程式產生器,其中的Models.xml為View Model定義,只有Player、Game兩組資料物件定義。
    • ViewModels:為共用程式庫(Class Library),CodeGen產生ViewModels.cs及額外ViewModel邏輯放在這裡。
    • Web:ASP.NET MVC應範例,內含CodeGen產生的ViewModel.ts(TypeScript)以及呼叫MVC方法由Server端取回ViewModel的範例
  2. Models.xml是模型定義,實務上我更愛用Excel,編輯修改方便,甚至可交給非IT人員維護。POC力求精簡,用XML意思到了就好。 
  3. 在CodeGen專案,Program.cs負責解析XML轉成結構化的屬性名稱、屬性型別資料。
    這部分沒什麼既定規則標準,全憑開發者自行定義欄位名稱、型別、註解該怎麼標示,只要確保XML文件與轉換邏輯匹配即可。支援的型別愈多,解析程式就愈複雜,例如要涵蓋陣列、泛型、自訂型別,需定義標示方式並加入對應的解析邏輯。
    我的範例只處理最基本的int、float、double、decimal、DateTime、bool、string,再加上Nullable。想要更彈性支援更多型別,請大家自由發揮。 CodeGen專案的ModelInfo及PropInfo型別用來將XML定義內容轉為.NET端的資料結構,方便後續應用。
  4. 將ModelInfo及PropInfo轉成C#及TypeScript型別的重任由執行期間T4範本一肩扛起,以下分別為產生C#型別及TypeScript型別的T4範本檔:


  5. 兩個T4範本的模型定義都來自this.Models(List<MethodInfo>),Models由Program.cs解析XML而得,要怎麼傳給T4範本呢?我習慣的做法是宣告與T4範本同名的Partial Class,新増接受List<ModelInfo>參數的建構式以及Models供T4範本使用,如此在T4中就可大大方方用this.Models取得Program.cs傳入的模型定義:
  6. Program.cs依以下方式將XML解析轉成List<ModelInfo>交給T4範本轉成BaseModels.cs及BaseModels.ts,產生的檔案分別寫入ViewModels專案及Web專案下的對應位置:
    staticvoid Main(string[] args)
            {
                var models = ReadModels();
                var t4csvm = new T4.CsViewModels(models);
                File.WriteAllText(@"..\..\..\ViewModels\BaseModels.cs", 
                    t4csvm.TransformText(), Encoding.UTF8);
                var t4tsvm = new T4.TsViewModels(models);
                File.WriteAllText(@"..\..\..\Web\Scripts\BaseModels.ts", 
                    t4tsvm.TransformText(), Encoding.UTF8);
            }
  7. 產生的BaseModels.cs長這樣:

    BaseModels.ts長這樣:
  8. 由於轉換所得的ViewModel型別只有屬性,如需入額外邏輯,在C#端可借用Partial Class的特性,為已有的Player型別增加建構式、屬性、方法。 


    注意:請避免直接修改BaseModels.cs,因為修改內容每次重跑CodeGen就會被覆寫。如果想調整BaseModels.cs,請直接修改T4範本,或使用Partial Class技巧實現。
    至於TypeScript型別,雖然沒有Partial Class的概念,但可以用繼承的做法加上自訂邏輯,要客製化不是問題。
  9. 如此,在C#端我們就有強型別的Player可用:

    在TypeScript端也有強型別的Player可用,Intellisense還能提供註解說明

未來若View Model要修改,調完XML重跑CodeGen,BaseModels.cs與BaseModels.ts就自動更新,程式端馬上有修正後的新版View Model可用,很酷吧?

希望程式範例跟說明內容夠清楚,如有不清楚之處歡迎回饋給我,我再盡力補充。

豆知識:Local System、Local Service與Network Service

$
0
0

同事詢問Windows服務執行身分的預設選項:Local System、Local Service、Network Service,權限各有何不同?這問題多年前我彷彿知道答案,如今大腦卻不爭氣地只有殘缺印象,爬文時習之,不亦悅乎。

Local System、Local Service、Network Service都是Windows預先定義的本機帳號,但跟一般帳號不同(LookupAccountName查不到),使用時不需密碼,可用於CreateServiceChangeServiceConfig(密碼隨便給)。

Local System

在本機具備最高權限(使用時務必小心),與BUILTIN\Administrators具備相同權限,可存取本機大部分資源,與遠端主機溝通時則使用該主機電腦帳號(<domain_name>\<computer_name>$) 。做為授權對象時,LocalSystem帳號可寫成NT AUTHORITY\SYSTEM、LocalSystem、<computer name>\LocalSystem。執行時期將使用預設使用者機碼(HKET_USERS\.DEFAULT)。

Local Service

在本機的權限相當於Users群組,目的在減小服務或程序遭入侵的損害範圍,存取遠端資源時使用匿名身分。做為授權對象時,Local Service可寫成NT AUTHORITY\LOCAL SERVICE。Local Service有自已的HKEY_USERS機碼(HKEY_USERS\S-1-5-19)

Network Service

在本機的權限相當於Users群組,旨在控制服務程序遭惡意操控時的損害範圍,對外存取遠端資源時使用該主機電腦帳號(<domain_name>\<computer_name>$)。做為授權對象時,Network Service帳號可寫成NT AUTHORITY\NETWORK SERVICE 。Network Service也有自已的HKEY_USERS機碼(HKEY_USERS\S-1-5-20)

延伸閱讀

【茶包射手日記】regsvr32 aspsmartupload.dll回報找不到DLL檔

$
0
0

接獲「ASP不死鳥親衛隊」報案,搬移x64 Server過程遇到「在測試台Windows 2012 R2註冊aspsmartupload.dll成功,但在正式台註冊失敗」的狀況!錯誤訊息為

The module "D:\some_folder\aspsmartupload.dll" failed to load.
Make sure the binary is stored at the specified path or debug it to check for problems with the binary or dependent .DLL files.
The specified module could not be found.

同一套程式檔案,測試台註冊成功、正式台註冊失敗,推測正式台缺少了aspsmartupload.dll所需的某個系統DLL,至於少了哪個DLL?茶包一哥Process Monitor展現威力的時候到了。

再跑一次regsvr32重現錯誤(記得要用SysWOW64的版本),Process Monitor輕輕鬆鬆指出關鍵所在:

漏Copy aspsmartupload.dll 到正式台是哪招啦?(冰斗)

感嘆殺雞錯用牛刀,準備以「低級錯誤」結案之際,同事坦承擺了烏龍,但另有隱情:正式台有兩台,先前是在另一台註冊失敗,該台機器確實已複製aspsmartupload.dll,也出現相同訊息,只要後來偵察時錯用未複製檔案的另一台正式機,故絕不是漏放aspsmartupload.dll這麼單純。複製好aspsmartupload.dll,再測試一次regsvr32註冊,果不其然,出現相同的錯誤訊息。

這是一個「撕開烏龍茶包,赫然發現裡面還包了一個小茶包」的概念。

再次開啟Process Monitor側錄檔案存取,一哥不愧為一哥,很快又抓出問題關鍵-缺了MSVBVM50.dll。

研判專案用的aspsmartupload.dll是VB5寫的(好老哇~腦中出現白髮老翁步履蹣跚吃力搬著檔案的畫面),執行時需要MSVBVM50.dll Runtime。測試台之所以註冊成功,是因為同一目錄有放MSVBVM50.dll,而部署到正式台時誤以為用不到就未複製。補上MSVBVM50.dll,aspsmartupload.dll註冊成功!

戰術檢討:開始處理時不該假設正式台檔案部署與測試台一致(射手守則第8條:Trust No One,Seeing Is Believing),基於兩台機器OS版本相同,一台成功、一台失敗的前題,優先比對二者檔案差異,應能更快找出問題,列為下次改進事項。


【茶包射手日記】SqlException Login failed for user '\$'.

$
0
0

在ASP.NET專案使用"Data Source=(local);Integrated Security=SSPI;Initial Catalog=MyDB"以Windows驗證連線本機SQL Server,Visual Studio測試正常,搬到IIS後出現以下錯誤:

System.Data.SqlClient.SqlException: Login failed for user '<Domain>\<Computer>$'.

Visual Studio測試時用的是IIS Express,會以當時登入Windows的帳號執行;移至IIS後執行身分不同可以理解,但怎麼會無端冒出電腦帳號(網域名稱\電腦名稱$)?我知道NETWORK SERVICE在存取遠端主機時會使用電腦帳號,但存取本機SQL為什麼會動用本機的電腦帳號?

爬文得知,原來這是個誤導性十足的錯誤訊息,跟遠端存取、本機電腦帳號沒啥關係,純粹是未設定 NT AUTHORITY\NETWORK SERVICE或IIS虛擬帳號的SQL存取權限造成。

主機為Windows 8.1/IIS 8.5,應用程式集區(名稱為BBDP)使用的身分為ApplicationPoolIdentity,故於SQL新增登入帳號對應到「IIS APPPOOL\BBDP」並授與權限,問題排除。

閒聊:Web Site Project為何沒落?

$
0
0

前幾天跟網友討論到:「WSP易改又好用,何以如今在冷宮?」


照片來源

Web Site Project(WSP)與Web Application Project(WAP)是ASP.NET網站的兩種專案類型態(延伸閱讀:Web Site Project vs Web Application Project ),身為從Visual Studio.NET (2002) ASP.NET 1.0開始寫起的老芋仔,有幸見證參與這一段歷史演變。

在ASP.NET 1.1/Visual Studio.NET 2003時代,測試網頁前必須等待Visual Studio編譯整個網站,12年前,最快的CPU是Pentium 4,SSD還沒發明,主流硬碟只有5400轉的時代,修改完到看見結果的等待過程,對急驚風(對,就是我)根本是種修行。有鑑於此,ASP.NET 2.0/Visual Studio 2005時代,Web Site Project誕生了!WSP不需要.csproj列舉管理網站檔案,在資料夾放入檔案就能加入新網頁,而網頁各自獨立且即時編譯,改完.aspx、.aspx.cs存檔再重載瀏覽就能看結果,少了編譯等待,Coding過程更流暢,雙手在鍵盤敲個不停的樣子特別帥(天曉得在寫什麼妖魔鬼怪),我就是這樣愛上WSP的。

只是,WSP也有WSP的缺點。例如:跨網頁共用邏輯只能放在App_Code、暫時移出檔案很麻煩(必須改名.excluded)、原始碼會在正式台曝光(註:可靠預先編譯解決)… 等,有一部分開發者仍舊偏好先編譯再執行的做法,為此微軟推出Web Appication Project(WAP)套件,採用先前整個網站編譯為單一DLL的做法,並在Visual Studio 2005 SP1時納入內建功能,自此,ASP.NET開發者有兩種選擇:WSP、WAP,功能相同,任君選擇。

Visual Studio 2008開始,ASP.NET陣營加入生力軍-ASP.NET MVC,ASP.NET MVC採用WAP編譯模式。而歷經Visual Studio 2008、2010、2012、2013到2015,Visual Studio同時支援WSP、WAP兩種專案型別,開發者可依偏好選擇。隨著ASP.NET MVC受到愈來愈多關注,WAP可以通吃MVC與Web Form形成優勢,只能寫Web Form的WSP逐漸式微。在Visual Studio 2012,WSP的地位正式浮現警訊-VS2012將不再支援Web Deployment Project,基於開發資源配置優先順序,Visual Studio用行動說明最後的抉擇。之後VS2013、VS2015延續相同策略,WDP只停在VS2010版,淡出歷史舞台。依下一代ASP.NET 5走向,可以預見WSP,乃至於Web Form也將退出主流之列(延伸閱讀:丞相,起風了!從ASP.NET 5的變革談起)。

但有個非常有趣的現象-最近較深入了解ASP.NET 5,發現它的專案結構與現行ASP.NET大異其趣:.xproj改為負向表列,偵測檔案變更自動編譯… (延伸閱讀:TechDays 2015筆記-D2)啊哈!不需列舉檔案,測試前不需手動編譯,不正是WSP精神的重現?我想起三國演義的開場白:天下大勢,分久必合,合久必分…

回到主題,WSP是怎麼沒落的?這是群體偏好共同演化的結果,沒人能給權威解答,但我身為「看著ASP.NET長大」的老骨頭,倒是有自已的觀察。WAP與WSP各有優劣,MSDN甚至有專文比較,在此不多細數,依我的觀點,WSP主要的優點與缺點如下:

優點:

  1. 檔案即改即測,一氣喝成,省去等待編譯的時間 ,讓Coding更流暢
  2. 更動aspx及aspx.cs即可新増或修改網頁,不要動到App_Code、bin及web.config,甚至連網站應用程式都不需重啟(但有特例),不影響其他網頁運作。如果你高興,甚至可以直接在正式台上改程式(實務上線環境很少會這樣玩就是了)
  3. 網站可以同時使用VB.NET及C#開發(實務上極罕見,可忽略)

缺點:

  1. 原始碼被部署到正式主機違反一般資安原則(靠編譯後發佈或WDP可以克服)
  2. 每個網頁自成DLL,載入程序繁瑣影響效能(可在WDP設定每個資料夾編成一顆DLL改善)
  3. 依賴將DLL放入BIN目錄形成參照,參照關聯不明確,除錯不易。BIN目錄需簽入版控,編譯後DLL檔案日期改變形成異動待簽入假象有點困擾
  4. 網頁採動態編譯,單元測試實施不易
  5. 跨網頁共用的程式碼只能集中在App_Code,不像WAP可以自由調置
  6. 不支援ASP.NET MVC、ASP.NET Web API
    (更新:透過一些技巧,在WSP還是有可能整合ASP.NET Web API,感謝網友忠實觀眾分享)

在我的工作環境,缺點1最致命,原始碼出現在正式主機是被禁止的,一律使用WDP先編譯再上線。而編譯策略採一個資料夾一顆DLL,好處是資料夾A的網頁有異動,上線時只更新資料夾A的DLL,將系統異動幅度減到最小。使用WDP後,更新aspx及aspx.cs增修單一網頁的第2項優點只適用於開發測試階段。而隨著時代演進,優點1愈來愈不明顯,首先是電腦硬體愈來愈強大,除了CPU變快,SSD的出現大幅提升運算效率,等待網站編譯對寫程式流暢度的影響不如以往明顯(ASP.NET 5敢採用「檔案一改就重新編譯」的模式,我想也基於「當代硬體已經很夠力」的假設,但我猜將未來應會增加手動執行編譯的選擇)。另外一方面,ASP.NET開發者多已具備架構概念,知道不該將建DB連線查資料表的邏輯一股腦塞進Button1_Click()事件,而是應該切割出企業邏輯層(Business Model)、資料存取層(DAL)另立獨立DLL。於是,愈來愈高比例的網頁修改其實是發生在企業邏輯層或資料存取層的DLL,需要編譯才生效,只剩下純UI面的異動可以即改即生效,優點1派上的用場就更少了。

在電腦硬體效能驟升與系統架構進化的夾擊下,WSP優勢不再,再加上ASP.NET MVC掘起,WebForm式微,WSP坐冷板凳的機率增加,近年來我只拿來順手寫寫展示或實驗性質的小專案,全部邏輯集中於單一網頁,改完存檔馬上看結果,充分發揮它的專長。至於正式專案,已上線專案雖然超過八成還是WebForm + WSP,短期內不可能改寫,但所有新開專案,基於會部分或全部使用ASP.NET MVC,WAP是唯一選擇。在我眼裡,即使WSP現階段還是中流砥柱,但榮景已過,只會逐漸淍零。

WSP終將沒落,但任何技術皆然(更甭提前端界那一堆短暫如煙火的「當紅」工具),都會歷經興起、全盛、沒落、消失的週期,身為開發者,也只能認識它、擁抱它、享受它、放下它,別帶一絲不捨。

這年頭身為開發者,千萬別跟你的工具、語言、程式庫談戀愛,唯絕情者得以倖存!

(別過頭偷偷拭淚)

圖說:照片是可愛的倉鼠,我永遠記得飼養介紹文章有段提醒:養牠之前必須有心理準備,倉鼠的惹人憐愛技能破表,飼主能從朝夕相處獲得難以想像的樂趣,但請記住,牠的壽命只有短短的兩年,所以遲早有一天…

【茶包射手日記】CSHTML ViewBag無法使用擴充方法

$
0
0

在ASP.NET MVC裡定義了一個擴充方法(Extension Method),打算在CSHTML中使用:(以下擴充方法為脫褲子放屁,純屬示範,目的在為為String新増一個GetLength()方法傳回字串長度)

namespace BBDPWeb.Models
{
publicstaticclass ExtMethodDemo
    {
publicstaticint GetLength(thisstring s)
        {
return s.Length;
        }
    }
}

在Action中,使用Title存入標題字串放入ViewBag,在CSHTML則加入using BBDPWeb.Models,確保引用ExtMethoDemo所在命名空間啟用擴充方法,既然Title為String,理論上ViewBag.Title.GetLength()應可取得字串長度。

@using BBDPWeb.Models
 
<!DOCTYPEhtml>
<htmlng-app="app">
<head>
<metacharset="utf-8"/>
<metaname="viewport"content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title.GetLength() - BBDP</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
<linkhref="~/Content/kendo/kendo.common.min.css"rel="stylesheet"/>
<linkhref="~/Content/kendo/kendo.afet.min.css"rel="stylesheet"/>

錯!CSHTML彈出錯誤訊息,指出Title沒有GetLength()這個方法。

一開始猛往「沒正確引用命名空間」方向追查,繞了半圈才發現問題出在ViewBag上。

ViewBag的型別為dynamic,故編譯階段難以判別ViewBag.Title型別為何,無法確認其為String就不會套用String專屬的擴充方法。

stackoverflow查到一段說明:(作者曾為微軟RD)

In regular, non-dynamic code extension methods work by doing a full search of all the classes known to the compiler for a static class that has an extension method that match. The search goes in order based on the namespace nesting and available "using" directives in each namespace.

That means that in order to get a dynamic extension method invocation resolved correctly, somehow the DLR has to know at runtime what all the namespace nestings and "using" directives were in your source code. We do not have a mechanism handy for encoding all that information into the call site. We considered inventing such a mechanism, but decided that it was too high cost and produced too much schedule risk to be worth it.

簡單地說,動態型別要使用擴充函式,必須在執行期間搜索所有關聯的命名空間,難度極高,故.NET Team未將其納入內建功能。要在動態物件(不限於MVC CSHTML,任何用到dynamic的地方都是如此)使用擴充函式,必須加上強型別宣告。

知道原委,將程式修改為((string)ViewBag.Title).GetLength(),就能正確呼叫擴充方法囉~

@using BBDPWeb.Models
 
<!DOCTYPEhtml>
<htmlng-app="app">
<head>
<metacharset="utf-8"/>
<metaname="viewport"content="width=device-width, initial-scale=1.0">
<title>@(((string)ViewBag.Title).GetLength()) - BBDP</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
<linkhref="~/Content/kendo/kendo.common.min.css"rel="stylesheet"/>
<linkhref="~/Content/kendo/kendo.afet.min.css"rel="stylesheet"/>
 

KendoGrid+Angular模版+欄位凍結 造成列高異常

$
0
0

Kendo UI 支援 Angular,手上 Angular 寫的專案幾乎都用 Kendo UI 網頁控制項刻介面。在KendoGrid的欄位設定中,欄位模版也可以嵌入 Angular 語法,挺好用的,如以下的例子:

將第一欄的template設定為字串"{{dataItem.CostCenter}}({{dataItem.CostCenterName}})",KendoGrid將執行Angular繫結,在欄位顯示"分公司代碼(分公司名稱)"。第二欄的template則使用另一種做法,撰寫函式接收dataItem參數,在其中讀取dataItem.Manager1,轉換成特定的HTML內容。網頁執行結果如下:

測試OK。但後方欄位較多排版擠不下,想起KendoGrid有個好用的欄位凍結功能,所以我在第一欄加上locked: true設定,讓後方過長的部分透過水平捲動呈現。

結果欄位是凍結了,列高卻莫名增高近一倍。

使用Chrome開發者工具偵察,發現指定 locked: true 後,<tr>會多出style="height: 53px"樣式,至於 53px 這個數字怎麼來?原因成謎。

又到了 Use the source, Luke!的時刻,找到相關函式設定中斷點,很快真相大白!

啟用欄位凍結功能時,KendoGrid實際上需要建立兩個<table>,左邊的Table放被凍結的欄位,右邊則是未凍結的欄位,以實現左邊欄位固定,右邊欄位可以左右捲動的效果。表格上下捲動時,得靠JavaScript讓左右Table同步位移。(對!就是這麼搞剛)。程式庫裡有個_adjustRowHeight函式,負責比較左右<table>同一列的高度,讓兩邊的列高一致(以數字較大者為準),才能無縫接合,一切聽起合理,問題是文字內容沒變,欄寬充裕,一列絕對放得下,監看offsetHeight1、offsetHeight2變數為何出現39跟54的懸殊對比?

將焦點移到中斷當下的網頁畫面(如下圖),答案揭曉。Angular繫結發生時機較晚,故在_adjustRowHeight()執行的當下,左方的{{dataItem.CostCenter}}({{dataItem.CostCenterName}})尚未置換成資料值,文字過長造成折行,撐大了列高。此時使用 template 函式的第二欄已產生繫結後的內容,高度正常,經_adjustRowHeight()同步,右方列高也放大到54px。之後左側第一欄Angular繫結完成出現分公司名稱,但被撐大的列高已回不去了。

知道問題根源,修改便非難事,將第一欄的template改成ng-bind,就可避免列高被冗長的{{…}}表示式撐大。

template: "<span ng-bind=\"dataItem.CostCenter + '(' + dataItem.CostCenterName + ')'\"></span>",

不過,這次觀察也讓我發現,KendoGrid支援Angular繫結,但執行順序為:template函式完成繫結 –> _adjustRowHeight() –> template Angular語法完成繫結。故 _adjustRowHeight() 抓不到Angular繫結後的真正列高,在某些情況可能形成誤差。若遇到這類問題,改用template函式應可避免。

WCF探勘14-使用By Reference傳遞參數

$
0
0

同事回報一起案例,某WCF服務的[OperationContract]方法宣告為void Blah(ref int i, ref string s),以傳址方式(By Reference)傳遞參數(延伸閱讀:Self Test - Value Type vs Reference Type),程式運作多時,詢問我-WCF可以使用 ref 傳參數嗎?

一隻黑天鵝默默在我面前飛過,看得我瞠目結舌…

令人訝異之處在於,依我的理解WCF的Client/Server身處不同程序(Process),不管輸入參數還是傳回結果,都得序列化、反序列化才能正確傳遞,既然不屬於同一程序,記憶體地址空間不同,何來傳「址」?

爬文後,在MSDN找到一段說明

Out and Ref Parameters

In most cases, you can use in parameters (ByVal in Visual Basic) and out and ref parameters (ByRef in Visual Basic). Because both out and ref parameters indicate that data is returned from an operation, an operation signature such as the following specifies that a request/reply operation is required even though the operation signature returns void.

由此可知,WCF真的支援在參數使用 ref、out!而隨後的說明暗示,使用 out 或 ref 時,即使傳回型別為 void,實際仍會傳回資料。意味WCF在背後做了一些處置,偷偷傳回標為 out、ref 的參數到客戶端,模擬傳值參數行為。

哼!我冷笑一聲,這種「偽」傳值的做法,應該三兩下就破功吧?為什麼WCF要提供容易踩雷的黑心規格?

那就做個實驗踢爆「偽」傳值參數的黑暗面吧!

我設計以下的資料物件,共有三個成員,屬性 PubProp 標註 [DataMember] 可序列化,NonSerProp 則是一般欄位,預期不在序列化範圍,與 PubProp 形成對照。另外, Check() 方法可印出 PubProp 及 NonSerProp 偵查資料內容。

using System.Runtime.Serialization;
 
namespace WcfDto
{
    [DataContract]
publicclass Foo
    {
privatestring _PubProp;
        [DataMember]
publicstring PubProp {
            get
            {
return _PubProp;
            }
            set
            {
                _PubProp = value;
            }
        }
 
publicstring NonSerProp = "Default";
 
publicstring Check()
        {
returnstring.Format("PubProp={0}, NonSerProp={1}",
                PubProp, NonSerProp);
        }
    }
}

建立一個 ByRefCall.svc,豪邁地使用 ref 傳址宣告 Test1(ref int i, ref stirng s) 及 Test2(ref Foo f) 兩個作業方法,被呼叫時修 i、s 及 f 值。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using WcfDto;
 
namespace WcfWas
{
    [ServiceContract]
publicinterface IByRefCall
    {
        [OperationContract]
void Test1(refint i, refstring s);
 
        [OperationContract]
void Test2(ref WcfDto.Foo f);
    }
 
publicclass ByRefCall : IByRefCall
    {
publicvoid Test1(refint i, refstring s)
        {
            i = 32767;
            s = "Darkthread";
        }
 
publicvoid Test2(ref Foo f)
        {
            f.PubProp = "Server";
            f.NonSerProp = "Server";
        }
    }
}

呼叫端長這樣,分別執行 Test1 及 Test2,並比對變數如何變化:

staticvoid Main(string[] args)
        {
            BRC.ByRefCallClient c = new BRC.ByRefCallClient();
int i = 1;
string s = "Jeffrey";
            Console.WriteLine("Before: i={0}, s={1}", i, s);
            c.Test1(ref i, ref s);
            Console.WriteLine("After: i={0}, s={1}", i, s);
 
            WcfDto.Foo f = new Foo();
            f.PubProp = "Client";
            f.NonSerProp = "Client";
            Console.WriteLine("Before: {0}", f.Check());
            c.Test2(ref f);
            Console.WriteLine("After: {0}", f.Check());
 
            Console.Read();
        }

執行結果如下:

Before: i=1, s=Jeffrey
After: i=32767, s=Darkthread
Before: PubProp=Client, NonSerProp=Client
After: PubProp=Server, NonSerProp=

Test1(ref i, ref s) 沒什麼意外,i 與 s 都被正確更新。但 Test2(ref f) 就精彩了,原本 PubProp 跟 NonSerProp 都是"Client",呼叫 Test2 後,PubProp 正確換成"Server",但 NonSerProp 被改掉,既不是"Client",也不是"Server",而呈現空白。實際應用時,這種未預期結果已可認定為Bug,可能導致系統出錯。

但,事情是怎麼發生的?

為進一步調查,我修改 Foo.PubProp ,在資料被外界修改時輸出 Environment.StackTrace 追查呼叫來源:

        [DataMember]
publicstring PubProp {
            get
            {
return _PubProp;
            }
            set
            {
                Debug.WriteLine(string.Format("Set PubProp=>{0}", value));
                Debug.WriteLine(Environment.StackTrace.ToString());
                _PubProp = value;
            }
        }

追蹤結果如下:(省略部分冗長內容)

Set PubProp=>Client
   於 System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
   於 System.Environment.get_StackTrace()
   於 WcfDto.Foo.set_PubProp(String value) 於 X:\WorkRoom\WCFLab\WcfDto\Foo.cs: 行 24
   於 WCFClient.Program.Main(String[] args) 於 X:\WorkRoom\WCFLab\WCFClient\Program.cs: 行 89
   … 省略 …
   於 System.Threading.ThreadHelper.ThreadStart()
Set PubProp=>Client
   於 System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
   於 System.Environment.get_StackTrace()
   於 WcfDto.Foo.set_PubProp(String value) 於 X:\WorkRoom\WCFLab\WcfDto\Foo.cs: 行 24
   於 ReadFooFromXml(XmlReaderDelegator , XmlObjectSerializerReadContext , XmlDictionaryString[] , XmlDictionaryString[] )
   於 System.Runtime.Serialization.ClassDataContract.ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context)
   於 System.Runtime.Serialization.XmlObjectSerializerReadContext.ReadDataContractValue(DataContract dataContract, XmlReaderDelegator reader)
    … 省略 …
   於 System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeParameterPart(XmlDictionaryReader reader, PartInfo part, Boolean isRequest)
   於 System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeParameter(XmlDictionaryReader reader, PartInfo part, Boolean isRequest)
   於 System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeParameters(XmlDictionaryReader reader, PartInfo[] parts, Object[] parameters, Boolean isRequest)
   … 省略 …
   於 System.ServiceModel.Channels.HttpPipeline.EmptyHttpPipeline.BeginProcessInboundRequest(ReplyChannelAcceptor replyChannelAcceptor, Action dequeuedCallback, AsyncCallback callback, Object state)
   於 System.ServiceModel.Channels.HttpChannelListener`1.HttpContextReceivedAsyncResult`1.ProcessHttpContextAsync()
   於 System.ServiceModel.Channels.HttpChannelListener`1.BeginHttpContextReceived(HttpRequestContext context, Action acceptorCallback, AsyncCallback callback, Object state)
   …省略…
Set PubProp=>Server
   於 System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
   於 System.Environment.get_StackTrace()
   於 WcfDto.Foo.set_PubProp(String value) 於 X:\WorkRoom\WCFLab\WcfDto\Foo.cs: 行 24
   於 WcfWas.ByRefCall.Test2(Foo& f) 於 X:\WorkRoom\WCFLab\WcfWas\ByRefCall.svc.cs: 行 31
   於 SyncInvokeTest2(Object , Object[] , Object[] )
   於 System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
   … 省略 …
Set PubProp=>Server
   於 System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
   於 System.Environment.get_StackTrace()
   於 WcfDto.Foo.set_PubProp(String value) 於 X:\WorkRoom\WCFLab\WcfDto\Foo.cs: 行 24
   於 ReadFooFromXml(XmlReaderDelegator , XmlObjectSerializerReadContext , XmlDictionaryString[] , XmlDictionaryString[] )
   … 省略 …
   於 System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
   於 WCFClient.BRC.ByRefCallClient.WCFClient.BRC.IByRefCall.Test2(Test2Request request) 於 X:\WorkRoom\WCFLab\WCFClient\Service References\BRC\Reference.cs: 行 152
   於 WCFClient.BRC.ByRefCallClient.Test2(Foo& f) 於 X:\WorkRoom\WCFLab\WCFClient\Service References\BRC\Reference.cs: 行 158
   於 WCFClient.Program.Main(String[] args) 於 X:\WorkRoom\WCFLab\WCFClient\Program.cs: 行 92
   … 省略 …

由 StackTrace 的程式位置,PubProp 總共被設定四次:

第一次,Main() f.PubProp = "Client"
第二次,有個 ReadFooFromXml() 函式由SOAP傳送內容讀出Foo,轉成輸入參數交給 ByRefCall.Test2
第三次,ByRefCall.Text2() 執行 f.PubProp = "Server"
第四次,Test2() 執行結果傳回客戶端,再用 ReadFooFromXml() 由 SOAP 讀取結果更新回Foo物件

第四次 StackTrace 有兩個程式行數值得注意。Reference.cs 158行呼叫152行,追到原始碼(如下圖),謎底揭曉。
使用 ref 時,WCF Proxy 會在背後宣告一個 inValue,將 Foo 放進 inValue.f,呼叫 Test2() 取得 retVal,再將原本的 inVal.f 換成 retVal.f。

在這種邏輯,retVal.f 有可能是另一顆新建立的 Foo,也有可能是原來的 f 物件,透過 ReadFooFromXml() 將屬性更新為 SOAP傳回內容。試過為 Foo 加入建構式,發現在 Client 端 Foo 只被建立一次,故可排除另建 Foo 的假設,物件 f 應該還是同一顆,藉由 ReadFooFromXml() 更新 PupProp 屬性,至於為什麼 NonSerProp 會被改成空白,得深入 ReadFooFromXml 邏輯一探究竟。至少,我們得到一項結論:

使用 By Reference 傳遞參數時,不在序列化範圍的屬性或欄位有可能出現非預期結果。

基於以上行為,我認為用 ref 傳遞傳遞Value Type(int、string、decimal…)還好,不致出問題。但用 ref 傳送物件參數是件危險的事,序列化範圍以外的屬性、欄位就有錯亂的風險,跟大家想像的傳統 ref 傳址行為不完全相同,再加上 By Referecne 傳遞違反 WCF 傳輸的本質,不如大家就忘掉「WCF 可以用 ref」這件事吧!

Viewing all 2433 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>