2016/12/29

AutoMapper - 自動對應 Model

寫了一年多的 MVC 後,發現強型別真是太方便了。透過 Model Binding 的方式,真的加快了程式開發效率。不過若專案功能很龐大時,免不了多了很多為了功能而制定的 ViewModel,因此若沒有工具來協助,要將 ViewModel 取得的資料對應到 Model,真的手會打到斷啊! 還好,發現了 AutoMapper 套件,真是拯救了我啊!

假設目前有一個檔案資訊的 Model
public class FilesInfo
{
  public Guid FILE_ID { get; set; }        
  public string MODULE_ID { get; set; }
  public Guid? REF_CONTENT_ID { get; set; }
  public string FILE_TITLE { get; set; }
  public string FILE_FILENAME { get; set; }
  public string FILE_DL_FILENAME { get; set; }
  public int FILE_DL_TYPE { get; set; }
  public string FILE_PATH { get; set; }
  public string FILE_LINK { get; set; }
  public string FILE_URL { get; set; }
  public string FILE_DESCRIPTION { get; set; }
  public bool IS_ACTIVE { get; set; }
}

此時因為頁面上的功能設計關係,我不需要原來 FilesInfo 中那麼多欄位,但需要添加一個註解 (Remark) 的屬性,所以 ViewModel 會變成這樣:
public class FileInfoEditViewModel
{
  public Guid FILE_ID { get; set; }
  public string FILE_TITLE { get; set; }
  public string FILE_LINK { get; set; }
  public string FILE_URL { get; set; }
  public string FILE_DESCRIPTION { get; set; }
  public bool IS_ACTIVE { get; set; }

  //New property
  public string Remark { get; set; }
}


現在,要將自 DB 取到的 FilesInfo 資訊直接對應到 FileInfoEditViewModel 內。當然可以用傳統土法鍊鋼方式一筆筆設定,但這次直接用 AutoMapper,幾行程式就搞定啦!
var file = context.FILE_INFO.Find("id_1");
Mapper.Initialize(x => x.CreateMap());
//我比較偏好這樣: Mapper.Initialize(x => x.CreateMap()); 比較能知道由哪個 Model 對應到哪個 Model
Mapper.Initialize(x => x.CreateMap var mFileInfoEdit = Mapper.Map(file);

上面程式碼執行後,因 FilesInfo 沒有 Remark 對應 Property, 所以 Remark 獲得 NULL。若要在 Mapping 的時候直接給定預設值,可以用 ForMember 指定:
Mapper.Initialize(x => x.CreateMap()
.ForMember(dest => dest.Remark, opt => opt.MapFrom(src => DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + " 建立"))
);
model.FileInfoEdit = Mapper.Map(file);

很方便吧!

AutoMapper:
https://github.com/AutoMapper/AutoMapper/wiki

Bootstrap Modal 中,Client 端 Model Validation 失效解決辦法


問題: 在 MVC 專案中,動態產生的表單項目 (如 Textbox, Checkbox 等) 在表單送出 (Submit) 時,無法進行 Client 端驗證

解法:

#1 確認必要的 .js 是否都載入
jquery.validate.js
jquery.validate.unobtrusive.js

MVC Bundles:
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include("~/Scripts/jquery.validate*"));

#2 確認 Web.Config 是否啟用 unobtrusive validation
也就是 appSettings 區塊內的 ClientValidationEnabled 跟 UnobtrusiveJavaScriptEnabled 都要設為 true


#3 動態的表單內容產出後,需要重新綁定 unobtrusive validation
$.validator.unobtrusive.parse('FORM_ID_OR_HTML_ELEMENT');
parse 地方設定 form (頁面上所有的 form) 或是指定的表單 ID
$.get(
'@Url.Action("GetContent", "RenderSource")', { act: 'view' },
  function (data) {
    $('#ContentContainer').html(data);
    //rebind unobtrusive validation
    $.validator.unobtrusive.parse('form');
});

在表單送出前,透過 $('表單ID').valid() 即可知道 Client 端驗證是否正確
$('form#FormID').valid();


參考內容
  1. 謙卑式jQuery檢核範例-動態變更檢核設定 
  2. MVC client side validation not working with modal or Bootstrap model 
  3. Unobtrusive validation not working with dynamic content 
  4. Unobtrusive validation not working on Ajax loaded partial view

移除 Windows 10 快速存取 (Quick Access) 清單

Windows 10 的快速存取 (Quick Access) 雖然很方便,但長久使用上仍有許多問題。
最近發現釘選某個資料夾,結果卻發現那個資料夾路徑或是屬性關係,
導致無法直接開啟目的資料夾
更扯的是,也沒辦法將那個釘選項目移掉 (沒錯! 滑鼠右鍵「取消釘選」沒有任何作用)
這時,只能靠 Google 大神找方法囉。

主要有兩種方法。
第一種是直接到設定的資料夾中直接刪除
在檔案總管中打上以下位置,直接到「最近使用」的資料夾
%AppData%\Microsoft\Windows\Recent\AutomaticDestinations
接著,將這個檔案刪掉:
f01b4d95cf55d32a.automaticDestinations-ms
輕鬆把快速存取釘選的項目全部刪除囉!

第二種,直接下指令吧
cmd.exe /c del "%AppData%\Microsoft\Windows\Recent\AutomaticDestinations\f01b4d95cf55d32a.automaticDestinations-ms"
執行後,快速存取清單也完全清除囉!

參考來源
Fix Quick access Pinned Shortcuts Stuck or Not Working Correctly in Windows 10

2016/10/23

[SQL] 取得分群 (Group by) 後,最新(大) / 最舊(小) 值

有時在操作 SQL 時,會遇到資料分群 (Group By) 後,取得每個群組最大 (新) 的項目。這裡參考了 Stack overflow 上一些解決方法。主要的概念都須用到子查詢,並在子查詢的操作過程中動一些小手段。

方法一:先利用 Group By 取得 Max 值後,在 Join 原表
SELECT t.Train, t.Dest, r.MaxTime
FROM (
      SELECT Train, MAX(Time) as MaxTime
      FROM TrainTable
      GROUP BY Train
) r
INNER JOIN TrainTable t
ON t.Train = r.Train AND t.Time = r.MaxTime

方法二:替子查詢內容 Group By 並添加流水號,再透過主查詢將需要的內容取出來
SELECT train, dest, time FROM ( 
  SELECT train, dest, time, 
    RANK() OVER (PARTITION BY train ORDER BY time DESC) dest_rank
    FROM traintable
  ) where dest_rank = 1

個人比較偏好方法二,簡潔有力呀!

參考來源:GROUP BY with MAX(DATE)


2016/09/29

[DB] JOIN 帶有 NULL 值欄位

有時因為某些因素,在表格做關聯時會有 JOIN 值為 NULL 的欄位。以下這個例子為例:

表格 Timeable: 列出每個時間點發車的列車會停靠的站點:
Time  Stop1    Stop2    Stop3      Stop4    Stop5
----  -------  -------  ---------  -------  ----------
0800  Taipei   Taoyuan  Taichung   Tainan   Kaohsiung
0820  NULL     Taoyuan  Taichung   NULL     Kaohsiung
0830  Taipei   NULL     Taichung   NULL     Kaohsiung
0840  Taipei   NULL     Taichung   Tainan   NULL
0855  NULL     Taoyuan  Taichung   NULL     Kaohsiung
0910  Taipei   NULL     Taichung   NULL     Kaohsiung

表格 CarType: 列出不同列車類型會停靠的站點:
CarType  Stop1    Stop2     Stop3     Stop4    Stop5
-------  -------  --------  --------  -------  ----------
      A  Taipei   Taoyuan   Taichung  Tainan   Kaohsiung
      B  Taipei   Taoyuan   Taichung  Tainan   Kaohsiung
      A  NULL     Taoyuan   Taichung  NULL     Kaohsiung
      C  Taipei   NULL      Taichung  NULL     Kaohsiung
      B  Taipei   NULL      Taichung  Tainan   NULL
      B  NULL     Taoyuan   Taichung  NULL     Kaohsiung
      A  Taipei   NULL      Taichung  NULL     Kaohsiung

依照最直覺的想法,應該會這麼下 SQL:
select t.Time, c.CarType, t.Stop1, t.Stop1, t.Stop3, t.Stop4, t.Stop5 from Timeable t
left join CarType c on
t.Stop1 = c.Stop1 and t.Stop2 = c.Stop2 and t.Stop3 = c.Stop3 and t.Stop4 = c.Stop4 and t.Stop5 = c.Stop5

產生的結果,不覺得怪怪的嗎?
Time  CarType  Stop1   Stop2   Stop3     Stop4   Stop5
----  -------  ------  ------  --------  ------  ---------
0800        A  Taipei  Taipei  Taichung  Tainan  Kaohsiung
0800        B  Taipei  Taipei  Taichung  Tainan  Kaohsiung
0820     NULL  NULL    NULL    Taichung  NULL    Kaohsiung
0830     NULL  Taipei  Taipei  Taichung  NULL    Kaohsiung
0840     NULL  Taipei  Taipei  Taichung  Tainan  NULL
0855     NULL  NULL    NULL    Taichung  NULL    Kaohsiung
0910     NULL  Taipei  Taipei  Taichung  NULL    Kaohsiung

問題就在於關聯條件設定時,有些欄位是 NULL

在 SQL Server 中,有兩種解法:
解法一:土法煉鋼法,條件寫多一點吧!
select t.Time, c.CarType, t.Stop1, t.Stop1, t.Stop3, t.Stop4, t.Stop5 from Timeable t left join CarType c on
(t.Stop1 = c.Stop1 or t.Stop1 is NULL and c.Stop1 is NULL) and
(t.Stop2 = c.Stop2 or t.Stop2 is NULL and c.Stop2 is NULL) and
(t.Stop3 = c.Stop3 or t.Stop3 is NULL and c.Stop3 is NULL) and
(t.Stop4 = c.Stop4 or t.Stop4 is NULL and c.Stop4 is NULL) and
(t.Stop5 = c.Stop5 or t.Stop5 is NULL and c.Stop5 is NULL)

解法二:利用 2008 版本提供的 coalesce 方法:
select t.Time, c.CarType, t.Stop1, t.Stop1, t.Stop3, t.Stop4, t.Stop5 from Timeable t left join CarType c on
(coalesce(t.Stop1, '') = coalesce(c.Stop1, '')) and
(coalesce(t.Stop2, '') = coalesce(c.Stop2, '')) and
(coalesce(t.Stop3, '') = coalesce(c.Stop3, '')) and
(coalesce(t.Stop4, '') = coalesce(c.Stop4, '')) and
(coalesce(t.Stop5, '') = coalesce(c.Stop5, ''))

coalesce 中,會將 NULL 值以一個指定的值替代,所以最好塞入獨特且不重複的字元進去

所得到的答案:
Time  CarType  Stop1   Stop2   Stop3     Stop4   Stop5
----  -------  ------  ------  --------  ------  ---------
0800        A  Taipei  Taipei  Taichung  Tainan  Kaohsiung
0800        B  Taipei  Taipei  Taichung  Tainan  Kaohsiung
0820        A  NULL    NULL    Taichung  NULL    Kaohsiung
0820        B  NULL    NULL    Taichung  NULL    Kaohsiung
0830        C  Taipei  Taipei  Taichung  NULL    Kaohsiung
0830        A  Taipei  Taipei  Taichung  NULL    Kaohsiung
0840        B  Taipei  Taipei  Taichung  Tainan  NULL
0855        A  NULL    NULL    Taichung  NULL    Kaohsiung
0855        B  NULL    NULL    Taichung  NULL    Kaohsiung
0910        C  Taipei  Taipei  Taichung  NULL    Kaohsiung
0910        A  Taipei  Taipei  Taichung  NULL    Kaohsiung

2016/08/02

[Oracle] 指定或修改 Sequence/Trigger Pair 最新序號值

因專案關係,這一陣子比較有機會長期接觸 Oracle。Oracle 雖然功能強大,但很多功能在操作上不是非常方便,尤其是對初學者來說,若沒有一個讓人親近的工具,真的很難對它保有熱情。不過還好在朋友的推薦下,使用 Dell 開發的 Toad for Oracle,一用之下大驚!原來 Oracle 的世界也有一個媲美 SQL Server Management Studio 這套相對應的工具,而且感覺更加強大。

不過離題了。這次不是要記錄 Toad 多強,而是遇到了序列問題。
新版 Oracle 12 不太清楚,但就我使用 9.2 的經驗中得知 Oracle 並沒有像 SQL Server 一樣提供自動識別編號的功能,雖說如此,但仍可利用 Sequence 及 Trigger 搭配達到相同的效果。當然,這要寫 SQL 的,但透過 Toad,其實非常容易達到:

CREATE OR REPLACE TRIGGER USER.TEST_TRG
BEFORE INSERT
ON USER.TEST
REFERENCING NEW AS New OLD AS Old
FOR EACH ROW
BEGIN
  :new.ID := TEST_SEQ.nextval;
END TEST_TRG;
建完 Sequence/Trigger Pair 後資料表執行一段時間,若突然有「直接跳號到指定編號」需求時,這時就得要動些手腳了:

首先,要先知道目前已知的編號 (如: 168) 及目標編號 (如: 210) 是多少
select max(ID) from TEST;  --取得: 168                                   --或
select TEST_SEQ.currval from dual; --使用 currval 取得當前 Sequence 值
換算兩者差距,將其設為 Increment (如: 210-168 + 1 = 41)
alter sequence TEST_SEQ increment by 41;
執行 Sequence,直接跳號到 209
select TEST_SEQ.nextval from dual; --跳號到 209
接著將 Increment 改回 1
alter sequence TEST_SEQ increment by 1;

接下來新增的序號就從 210 開始編囉

2016/07/18

[CSS] Display v.s. Visibility

有時候總是搞混 CSS 的 disable 及 visibility。簡單來說,visibility 只是將指定物件的內容隱藏起來,實際上物件仍會被 Render 出來。若使用 disable, 則會連同物件也一起隱藏。而 jQuery 的 show() 及 hide() 則是透過 disable 的操作來實現隱藏或顯示效果。

jQuery 很方便,但有時為了不想太依賴框架,會想用原生的 JavaScript 來操作物件的顯示或隱藏。雖然不是很直覺,且要寫的落落長,但還是紀錄一下:

//display 操作
element.style.display = 'none';           // Hide = $(object).hide();
element.style.display = 'block';          // Show = $(object).show();
element.style.display = 'inline';         // Show
element.style.display = 'inline-block';   // Show

//visibility 操作
element.style.visibility = 'hidden';      // Hide
element.style.visibility = 'visible';     // Show

2016/07/02

[DB] 將 Group by 後的內容濃縮至一列,並以逗號分隔每筆群組資料

不知為何,過去總是很抗拒 SQL 的
group by
指令。不過因為接手的幾個案子需要大量使用到 SQL 的聚合函數 (Aggregation, 如:
Sum, Max, Min
...),因此就得跟它面對了。而這幾個案子中的需求情境,並不是要加總或計算
Select
出來的最大、最小值,而是將資料
group by
後,將各組的每筆資料以單一符號分隔,並濃縮成一列顯示。這是種比較特別的聚合方法,因此就值得紀錄一下啦。

先看看範例
SELECT * FROM CITY;

 C_ID  C_TYPE  C_NAME
-----  ------  ---------
    1  TW      Nantou
    2  TW      Hsinchu
    3  TW      Chiayi
    4  D       Taipei
    5  D       Taichung
    6  TW      Hualian
    7  TW      Taitung
    8  D       Tainan
    9  D       Kaohsiung
需求:
  1. 我想要把以上這些城市依照
    C_TYPE
    欄位進行分組
  2. 接著要把各組內容濃縮成一列
  3. 每筆資料以半形逗號進行分隔
產生的結果:
CITY_TYPE    CITY_ID      CITY_NAME
----------   ----------   -------------------------------------
        D    4,5,8,9      Taipei,Taichung,Tainan,Kaohsiung
       TW    1,2,3,6,7    Nantou,Hsinchu,Chiayi,Hualian,Taitung

當然也可以透過程式,以迴圈的方式處理。不過有些資料處理是資料庫的強項,能在資料庫處理掉的手續,當然就交給資料庫去代勞囉。究竟這需求要怎麼處理呢?由於首次遇到的案子是使用 Oracle 資料庫,而且還是非常骨董級的 9.2i 版,因此就很勤勞的找出 SQL Server 及 Oracle 兩版本的解法 (Opps.... Oracle 各版本寫法又不太一樣) 詳細細節就不多說了,直接看 Code 吧。

使用 Oracle 9.2i

使用
ROW_NUMBER()
SYS_CONNECT_BY_PATH
達成。
--ROW_NUMBER() and SYS_CONNECT_BY_PATH functions in Oracle 9i
------------------------------------------------------------------
SELECT C_TYPE as CITY_TYPE,
    LTRIM(MAX(SYS_CONNECT_BY_PATH(C_ID,',')) 
       KEEP (DENSE_RANK LAST ORDER BY curr),',') AS CITY_ID,
       LTRIM(MAX(SYS_CONNECT_BY_PATH(C_NAME,','))
       KEEP (DENSE_RANK LAST ORDER BY curr),',') AS CITY_NAME
FROM   (SELECT C_TYPE,
               C_ID,
               C_NAME,
               --C_Name
               ROW_NUMBER() OVER (PARTITION BY C_TYPE ORDER BY C_ID) AS curr,
               ROW_NUMBER() OVER (PARTITION BY C_TYPE ORDER BY C_ID) -1 AS prev,
               --C_ID               
               ROW_NUMBER() OVER (PARTITION BY C_TYPE ORDER BY C_ID) -1 AS prev2
        FROM   CITY)
GROUP BY C_TYPE
CONNECT BY prev = PRIOR curr AND C_TYPE = PRIOR C_TYPE
START WITH curr = 1;

使用 Oracle 11g Release 2

使用
LISTAGG
達成。
SELECT C_TYPE AS CITY_TYPE,
 LISTAGG(C_ID, ',') WITHIN GROUP (ORDER BY C_ID) AS CITY_ID,
 LISTAGG(C_NAME, ',') WITHIN GROUP (ORDER BY C_ID) AS CITY_NAME
FROM   CITY
GROUP BY C_TYPE;
參考:String Aggregation Techniques

使用 SQL Server 2008 以上版本

運用
XML PATH
來達成。
SELECT C_TYPE AS CITY_TYPE,
       CITY_ID = STUFF(
                         (SELECT ', ' + CAST(C_ID AS VARCHAR)
                          FROM CITY
                          WHERE C_TYPE = x.C_TYPE
                            FOR XML PATH(''), TYPE).value('.[1]', 'varchar(max)'), 1, 2, ''),
       CITY_NAME = STUFF(
                           (SELECT ', ' + C_NAME
                            FROM CITY
                            WHERE C_TYPE = x.C_TYPE
                              FOR XML PATH(''), TYPE).value('.[1]', 'varchar(max)'), 1, 2, '')
FROM CITY AS x
GROUP BY C_TYPE;
參考:How to use COALESCE with multiple rows and without preceding comma?

使用 SQL Server 2017 以上版本

(2018-12-12 補充)
偶然間在網路文章中發現有人提出使用這個版本才支援的原生函式
STRING_AGG
,微軟真佛心,這語法真是簡單又好用 (不過…目前工作用的系統都還在用 2008R2 咧…新語法雖然好用,但現階段用不到呀)。
SELECT
    C_TYPE AS CITY_TYPE
   ,STRING_AGG(C_ID, ',') AS CITY_ID
   ,STRING_AGG(C_NAME, ',') AS CITY_NAME
FROM CITY po WITH (NOLOCK)
GROUP BY C_TYPE
參考:[小菜一碟] SQL Server FOR XML 退休,欄位資料合併讓 STRING_AGG 來。

Oracle 11gR2 跟 SQL Server 版本的寫法,目前為止只有私底下寫而已,專案上還沒用到。至於 Oracle 9.2i 的版本對於資料量少的資料表來說,真的是很方便。但若是資料量達到上萬筆,效能真的差到不行,最後還是放棄這種寫法。要這麼寫的話,請先衡量資料量再用吧。

2016/07/01

[C#] 在 WebForm 中透過 WebMethod,提供 jQuery Ajax 非同步功能

過去寫程式遇到問題,最直覺的方式就是找 Google 大神。不過久了發現,有些問題總是一而在、再而三的重複出現。雖然對先前的解決方式總有印象,但還是得耗費許多時間一一從 Google 大神找出的資料中一一篩選解答。另外,雖然還有 Evernote 這個獨角獸可用,但每次把資料擷取下來後,需要時還是得花時間一一篩選。看來,還是自己消化後,寫篇文章會比較有印象吧。

前一陣子主要利用 MVC 開發系統。而最近接手的案子因為沒什麼時間改寫,因此還是重回傳統 Web Form 的方式撰寫。最近發現有個透過前端 jQuery 呼叫後台方法的需求。這方法在 MVC 開發中非常方便 (Controller 回傳 JsonResult 即可),但換到 Web Form 中卻一時困住了。此時才發現:只要將方法標示為 WebMethod,幾乎也可滿足我要的需求。

不過,實際開發後,才發現 Web Form 的開發方式需要注意的地方實在太多了。以下就來記錄一下。

WebForm 寫法:

  1. 先在 Code behind 中建立一個 Web Method,並指定為
    [WebMethod]
    (行 17)。由於預設中不會載入
    System.Web.Services
    ,因此要手動加入。
  2. 需要注意的是:WebMethod 的 function 一定要指定為
    static
  3. 接著,處理一些要做的事情,並建立要回傳的 JSON 字串
  4. 行 20,在回傳 JSON 時,我使用 Json.NET 來序列化字串。其實也可以用 .NET 內建的方法,不過 Json.NET 的方法很直覺,個人比較推。不過記得使用前要載入 Newtonsoft.Json (可至 Nuget 安裝,指令:
    Install-Package Newtonsoft.Json)

//WebMethodTest.aspx.cs
//Code-Behind

using Newtonsoft.Json;
using System;
using System.Web.Services;

namespace ESH_LOMS
{
    public partial class WebMethodTest : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
        }

        [WebMethod]
        public static string ajax_test(int? id, Boolean is_test, string cmd = "")
        {
            var result = new { id = id.Value, cmd = cmd, is_test=is_test};
            return JsonConvert.SerializeObject(result);
        }
    }
}
在 View 的地方 (我忘了 WebForm 怎麼稱呼,MVC 寫習慣了,就都叫他 View 吧),主要是處理 JQuery 的地方。主要注意的點有:
  1. 行 4 直接指定 WebMethod 的位置
  2. 行 5 的
    data
    變數中,指定要傳送給
    ajax_test
    方法的參數。重點是:直接傳一整組字串
  3. WebMethod 會直接將結果回傳給 行 8 的
    response
    字串。此時型態為 Object。若需拆解回傳的單一屬性,須將回傳字串先行 Parse 過,這樣才能使用類似
    obj.id
    方式取得單一屬性。
$(function() {
    $.ajax({
        type: "POST",
        url: "http://localhost/WebMethodTest.aspx/ajax_test",
        data: "{is_test:true, id: 1, cmd:'test'}",
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        success: function(response) {
            var obj = JSON.parse(response.d);
            alert(obj.id + ", " + obj.cmd + ", " + obj.is_test);
        }
    });
});


MVC 寫法

至於 MVC 的寫法就簡單許多了。只要在  Controller 中建立回傳 JsonResult 類型的 Action 即可,免除建立 Json 字串後須序列化這道手續。
//Controller
public JsonResult ajax_test(int? id, Boolean is_test, string cmd = "")
{
  return Json(new
  {
    id = id.Value,
    cmd = cmd,
    is_test = is_test
  });
}

而 View 的地方,基本上跟 WebForm 一樣。不過為了更方便利用回傳的 Json 內容,在 $.ajax 中進行一些小動作 (參閱: 黑大《【答客問】使用jQuery.ajax傳送複雜參數到ASP.NET MVC》:
//View
(function($) {
    //包裝一下 jQuery $.ajax 的呼叫方式
    $.extend($, {
        //=========== Replace item[0][propName] to item[0].propName for MVC model binder ===========
        postMvc: function(url, data, succ, dataType) {
            return $.ajax({
                url: url,
                type: "POST",
                dataType: dataType,
                data: data,
                success: succ,
                traditional: true,
                beforeSend: function(xhr, settings) {
                    settings.data = settings.data.replace(/%5D%5B(.+?)%5D=/g, "%5D.$1=");
                }
            });
        }
    });
})(jQuery);

$(function() {        
    $.postMvc(
        '@Url.Action("ajax_test")',
        {
            id : 1,
            is_test : true
            cmd = 'Test'
        },
        function(data) {
            alert(data.id + ", " + data.cmd + ", " + data.is_test);
        },
        'json');
});