IBotDataStore.FlushAsync Exception: The data is changed

前言

最新有個需求就是要判斷目前 User 是不是在進行某項作業, 如果不是就推送包含 Button 的訊息給 User, 不然就推文字通知訊息等目前的作業完成後,再顯示待辦的 Button 訊息給 User .

研究

最簡單的就是將這個 flag 寫在 BotState 之中,所以我們可以在使用者加入時,將它的 Conversation 記錄下來,如下,

if (message.Type == ActivityTypes.ConversationUpdate)
{
Func<ChannelAccount, bool> isChatbot =
channelAcct => channelAcct.Id == message.Recipient.Id;
if (message.MembersAdded.Any(isChatbot))
{
var memberAdded = message.MembersAdded.FirstOrDefault();
//這裡請自行處理將該 user 的 Conversation 儲存起來,為了方便,這裡將它存在一個變數之中
WebApiApplication._ConversationReference = message.ToConversationReference();
}
}

然後在申請作業記錄這個 Flag ,當然,結束時請記得將這個 Flag 清除哦!

using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, context.Activity.AsMessageActivity()))
{
var botDataStore = scope.Resolve<IBotDataStore<BotData>>();
var key = Address.FromActivity(context.Activity);
var botUserData = await botDataStore.LoadAsync(key, BotStoreType.BotUserData, default(CancellationToken));
botUserData.SetProperty<string>(“dialog”, “RootDialog:MessageReceivedAsync”);
await botDataStore.SaveAsync(key, BotStoreType.BotUserData, botUserData, default(CancellationToken));
await botDataStore.FlushAsync(key, default(CancellationToken));
}

可是執行時,卻發生了「Exception: The data is changed」的錯誤,如下圖,
[Exception: The data is changed]

因為 botframework 會自動去幫我們將 State 存檔,所以當它存檔時,發現狀態被改掉了,所以就出現「Exception: The data is changed」的錯誤 .

嗯, 那怎麼辦呢? 那就調整程式, 不要去呼叫 FlushAsync, 讓 botframework 去存就可以了, 所以程式調整如下,

using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, context.Activity.AsMessageActivity()))
{
var botDataStore = scope.Resolve<IBotDataStore<BotData>>();
var key = Address.FromActivity(context.Activity);
var botUserData = await botDataStore.LoadAsync(key, BotStoreType.BotUserData, default(CancellationToken));
botUserData.SetProperty<string>(“dialog”, “RootDialog:MessageReceivedAsync”);
await botDataStore.SaveAsync(key, BotStoreType.BotUserData, botUserData, default(CancellationToken));
//await botDataStore.FlushAsync(key, default(CancellationToken));
}

所以我們的通知程式就可以透過 ConversationReference 轉回 Activity, 取回 BotState, 例如,我新增一個 api method 來顯示 BotState , 如下,

[Route(“api/Messages/GetDialogData”)]
[HttpGet]
public async Task<HttpResponseMessage> GetDialogData()
{
var resp = new HttpResponseMessage(HttpStatusCode.OK);
if (WebApiApplication._ConversationReference != null)
{
var message = WebApiApplication._ConversationReference.GetPostToBotMessage();
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message.AsMessageActivity()))
{
var botDataStore = scope.Resolve<IBotDataStore<BotData>>();
var key = Address.FromActivity(message);
var botUserData = await botDataStore.LoadAsync(key, BotStoreType.BotUserData, default(CancellationToken));
var dialogData = botUserData.GetProperty<string>(“dialog”);
resp.Content = new StringContent($”<html><body>Dialogs:{dialogData}</body></html>”, System.Text.Encoding.UTF8, @”text/html”);
}
}
return resp;
}

所以在 Dialog 中儲存 State 時 , 可以注意一下這種狀況哦 ^_^

Demo 專案可以參考 rainmakerho/Exception_DataChanged

參考資料

Microsoft Bot Framework: Exception: The data is changed

透過 Microsoft BotFramework-WebChat 的 botchat.js 連接 Botframework 做的 Chatbot ,輕鬆 Web Application

我們透過 Microsoft BotFramework 來製作 Chatbot 程式後,除了可以接各透的 IM Channel 外,最快速的就是將它整合到現有的 Web Application 之中。Microsoft BotFramework-WebChat 已有提供範例讓我們去整合。
但是一般的網站並不需要一下子就顯示 WebChat ,而是在下方需要一個機器人的小圖示,按下去之後再顯示出 WebChat 。 繼續閱讀 “透過 Microsoft BotFramework-WebChat 的 botchat.js 連接 Botframework 做的 Chatbot ,輕鬆 Web Application”

OWASP ZAP 憑證安裝的方式(OWASP ZAP Certificate)

問題

透過 ZAP 去錄 https 時,如果沒有安裝憑證時,在 Browser 就會出現「 “Your connection is not private”, “你的連線並不安全”」的警告訊息,如下圖所示。
Chrome的警告

Firefox的警告

解法

為什麼會出現那個警告訊息呢? 因為 Browser 用的那個憑證已經不是原本連接的那個 host 的憑證,而變成了 OWASP Zed Attack Proxy Root CA 了哦! 而這個憑證並沒有被電腦 Trust 所以就會有那個警告訊息哦!
那要怎麼辦呢? 就是把憑證裝進去電腦的信任區。
以下我們就一步步來說明,

1.儲存 ZAP 憑證

開啟 OWASP ZAP ,Tools -> Options -> Dynamic SSL Certificates -> Save
儲存 ZAP 憑證

2.匯入 ZAP 憑證

DbClick剛才儲存的憑證檔,按下「Install Certificate…」,並將憑證存到「Trusted Root Certification Authorities」之中,如下,
Certificate Import Wizard

記得要將憑證存到「Trusted Root Certification Authorities」之中
存到「Trusted Root Certification Authorities」

最後按下 Finish 就可以了哦!
Import 完成

FIREFOX 匯入 ZAP 憑證

因為 firefox 憑證是自已管理的,所以 firefox 也要匯入 ZAP 憑證 ,如果會用 firefox 來錄的話,可以省略這步哦!
firefox 選項 -> 隱私權與安全性 (或在網址列輸入 about:preferences#privacy) -> 檢視憑證
隱私權與安全性

匯入 ZAP 憑證
匯入 ZAP 憑證

勾選「信任此憑證機構以識別網站」
勾選「信任此憑證機構以識別網站」

透過上述的方式,我們就可以錄 https 的網站,而不會一直出現警告訊息。 如果不會再錄的話,就請記得將 ZAP 的憑證移除哦!

透過 WinDbg 來找出 ASP.NET CPU 100% ASP.NET 程式的問題

問題

我們有一個 ASP.NET 的系統部署到 IIS 上(Windows 2012, .NET 4.x),有時候會導致 w3wp.exe 吃掉所有的 CPU 資源,一直要等到應用程式回收後,程式再重新啟動後就正常了。這種狀況不定期會發生。

解法

我們可以使用 WinDbg 來找出導致 IIS CPU 100% 的原因,方法如下,

  • 透過「工作管理員」來「建立傾印檔案」
    當發生 IIS 導致 CPU 100% 時,開啟「工作管理員」,按右鍵選取「建立傾印檔案」。要依 Web 應用程式的平台(x86/x64)來開啟「工作管理員」(x86的工作管理員在 C:\Windows\SysWOW64\taskmgr.exe )。
  • 安裝 WinDbg
    請參考 Debugging Tools for Windows (WinDbg, KD, CDB, NTSD) 來安裝對應的版本。
  • 設定 Symbol Path
    先建立一個 C:\RTX64_SYMBOLS 目錄(依您自行定義),然後開啟 Command 設定 _NT_SYMBOL_PATH 的環境變數,如下,

    set _NT_SYMBOL_PATH=srv*C:\RTX64_SYMBOLS*https://msdl.microsoft.com/download/symbols

    set _NT_SYMBOL_PATH所以在 command 中,輸入 set 後,就可以看到 _NT_SYMBOL_PATH 的設定值哦,

    _NT_SYMBOL_PATH當然,如果常常會用到的話,就直接設定到 環境變數之前,下次 debug 時,就不用再設定一次了哦! 詳細請參考Set up your system to use Microsoft’s public Symbol Server

  • 在 WinDbg 中找問題
    • 載入 sos.dll
      WinDbg 有 x86/x64 的版本,我是使用 x64 的版本,所以 sos.dll 的路徑是 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\sos.dll,您輸入如果發生錯誤的話,請使用 C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll 。
    • 設定 Symbol 檔目錄
      !sym noisy
      .cordll -ve -u -l
    • 透過 !runaway 顯示Threads所佔的時間
      輸入 !runaway 後,會顯示各 Thread 所花費的時間,花最多的會在最上面,如下圖,

      !runaway

      !runaway

    • 透過 ~[thread]s 切換到該 thread 的位置
      由上圖所示,Thread 40 佔最多時間,所以我們切到它的位罝去,

      ~40s

      ~40s

    • 透過 !clrstack 來查看呼叫堆疉
      !clrstack

      !clrstack從上圖可以發現,應該是有關 Dictionary 操作的問題,而它是由我們系統中 TemplateCfg.initial() 這個 Method 所引起的。

  • 檢視並調整程式碼
    開啟 TemplateCfg 程式碼,在 initial 這個 Method 之中會建立 Dictionary 物件,Clear 它,並 Insert 資料,但這些 Dictionary 的變數卻又設定成 static 。TemplateCfg.initial()即然這些 Dictionary 是 static 的,而且它們值又都是相同的,就沒有必要每次 request 時,就重新建立並 insert 這些資料。
    所以將 initial 裡面的 Code 搬到 static constructor 。
    調整完程式碼後,從去年觀察到目前,已經沒有再發生 CPU 100% 的狀況。

參考資料

Debugging in Production Part 1 – Analyzing 100% CPU Usage Using Windbg
WinDBG 應用實例:找出 ASP.NET CPU 100% 原因
Debugging Tools for Windows (WinDbg, KD, CDB, NTSD)
Set up your system to use Microsoft’s public Symbol Server
High CPU Hangs – 05
中小型研发团队架构实践:生产环境诊断利器WinDbg帮你快速分析异常情况Dump文件
Intro to WinDBG for .NET Developers
.NET Debugging Demos Lab 4: High CPU Hang – Review

無法載入檔案或組件 ‘log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=692fbea5521e1304’ 或其相依性的其中之一

最近同事詢問他們使用 Crytal Report Viewer 時,會發生 log4net 版本衝突的問題。如下圖所示,
CrystalReportViewer Runtime Error

如果是不同版本衝突的話,可以參考 如何讓不同 PublicKeyToken 的 DLL assemblyBinding 到可以用版本? 透過 assemblyBinding 的方式就可以解決。 但剛好我們的 AP 自已用的 log4net 版本也是 1.2.10.0,所以是 相同版本,不同的 PublickToken 的衝突。 詳細訊息如下,

沒有辦法解決 “log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821” 和 “log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=692fbea5521e1304” 之間的衝突。
任意選擇 “log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821”。

有查到 Error: Could not load log4net assembly 似乎是因為不同的平台(x86/x64) 所用的 log4net 會不同。
x86(32位元)用的 PublicToken 是 692fbea5521e1304,x64(64位元)或是 AnyCPU 用的 PublicToken 則是 1b44e1d426115821。
而我們 ap 平台是 x64 的,但 Crystal Report Viewer 卻使用了 x86(32位元) 的 log4net 。

嗯… 我想應該是那裡設定出了錯吧 …

跟同事討論後,同事又說只有在某台機器上才會出現那個錯誤,如果是透過 Browser 直接使用系統就正常,透過 VS.NET 啟動程式去 Debug 時,就會出現那個錯誤。
我想是因為如果透過 Browser 去使用系統的話,是連到 IIS ,而 IIS 設定的是 x64(64位元);如果透過 VS.NET 則是連到 IIS Express,預設它是使用 x86(32位元)。
於是筆者將 IIS 上的應用程式集區設定成 啟用 32 位元,再執行程式,則一樣會出現 log4net 的衝突狀況(  重現問題了 …^_^)。
所以就請同事在 VS.NET 中設定讓 IIS Express 使用 64 位元就沒問題了哦!
工具 -> 選項 -> (search textbox 中輸入 iis) , 並勾選 將 64 位元版本的 IIS Express 用於網站和專案(U) 選項勾起來就可以了哦! 如下,
將 64 位元版本的 IIS Express 用於網站和專案(U)

如果您是系統出了錯,請 Check 應用程式集區是否設定成了 「啟用32位元應用程式」哦!