2014年1月15日 星期三

Azure Storage Explorer 輕鬆管理 Windows Azure 儲存體

Azure Storage Explorer 是一個可以輕鬆管理 Windows Azure 儲存體的工具,省去在一般管理時切換不同的 Blob、Table、Queue 甚至是不同的儲存體操作的時間,如果是私密的 Blob,可以很快的取得 Blob 在 SAS ( shared access signature ) 後的網址。初次使用,只需要做一次設定,往後即可方便且快速使用。

以下就說明 Azure Storage Explorer 安裝設定與基本操作。

1.

首先到 Azure Storage Explorer 官方網站 下載 Azure Storage Explorer 到本機硬碟。


2.

利用「傻瓜安裝法」將 Azure Storage Explorer 安裝到電腦。

3.

執行 Azure Storage Explorer 後會如同下圖狀態


4.

接下來就要做初始設定了,在 Azure Storage Explorer 點擊 Add Account 會跳出要輸入 Storage account name 和 Storage account key,此時要至 Windows Azure 選擇儲存體且取得儲存體的名稱與金鑰。如下圖所示設定:


5.

設定完成後,即可針對該儲存體的內容做新增或是調整,右側上方可以選擇該儲存體的 Blob、Table 或 Queue,而左上方可以新增、複製、更名、刪除 實體,而右邊中間區塊就是實體內的物件列表,若是 Blob 裡面就是檔案列表。雙擊某個檔案就會顯示該檔案資訊。


6.

由於 Table、Queue 操作較單純,功能很雷同,這裡就讓各位使用者去嘗試,在此針對 Blob 說明較獨特的功能做說明。

除了基本功能:檢視、複製、更名、刪除、上傳、下載,另外還有一個權限功能:

Blob 針對容器有三種權限設定,Private、Public Blob、Public Container,其中 Public Container 是只要知道這個檔案網址的人都可以對此檔案做動作,其他都必須要取得含有所謂的共享存取簽章( Shared Access Signature ) 的網址,以便讓系統知道這個網址是有權限讀取的。網址後面會帶取得這圖片的 token,而這 token 經過解密後,就可以得知使用者對這張照片所有的權限。

所以依照下圖所示,即可取得檔案含有共享存取簽章( Shared Access Signature ) 的網址。當然,你可以在這上面設定這張圖片的新增、寫入、刪除、列表功能,甚至是存取的時間。


Azure Storage Explorer 是一個很好用的工具,幾個鍵就等於在網頁上做很多動作,這可能要使用過後才會有這樣的感覺。


2014年1月14日 星期二

ASP.NET MVC 4 WebApi Help Page 建立自訂的說明文件

在建立 ASP.NET MVC 4 WebApi 專案,過程中它會建立一個 Help Page,協助開發人員對自己撰寫的 Controller 下註解,以便初次進入專案的開發人員快速了解每一個 Api 並且使用,且方便交接。


看這次的例子,此 Controller 有兩個 Api,如下圖所示:



看來此處的 Api 說明,並不是開發人員自己定義的,所以接下來的設定,可以使這些說明變成是開發人員定義的。

參考:Creating Help Pages for ASP.NET Web API

1.

在專案的 Areas 下有 Help Page 資料夾,這裡就是產生 Help Page 內容的 MVC,如有興趣可參考。 接著就把 App_Start 底下檔案 HelpPageConfig.cs 做修改:


config.SetDocumentationProvider(new XmlDocumentationProvider(
    HttpContext.Current.Server.MapPath("~/App_Data/XmlDocument.xml")));

把此行註解拿掉以啟用,此行意思是建立一個 XML 檔案來存放開發人員定義的說明文件,存放位置為 ~/App_Data/XmlDocument.xml。

2.

此時對專案點擊右鍵 > 屬性 > 建置,對 XML 文件檔案方框打勾,設定存放位置為 App_Data/XmlDocument.xml。



3.

再去修改剛剛的 Controller Api,使用三個斜線的註解去說明此 Api 的功能為何,如以下所示:

/// <summary>
/// [後台] 取得 單一部門 資料
/// </summary>
/// <param name="id">編號</param>
/// <param name="language">語系</param>
/// <param name="date">日期</param>
/// <returns></returns>
public async Task<OM_Department> Get(Guid id, string language, DateTime date)
{
 // ...
}

設定到此,已經算是完成了。最後執行專案看看結果:



而 App_Data/XmlDocument.xml 產生的內容為:
<?xml version="1.0"?>
<doc>
    <assembly>
        <name>Backend</name>
    </assembly>
    <members>
 
  ...
  
        <member name="M:Backend.Areas.orgmaintainer.Controllers.OM_DepartmentController.Get(System.Guid,System.String,System.DateTime)">
            <summary>
            [後台] 取得 單一部門 資料
            </summary>
            <param name="id">編號</param>
            <param name="language">語系</param>
            <param name="date">日期</param>
            <returns></returns>
        </member>
        
  ...
  
    </members>
</doc>

最後補充一點,必須每個 Api 上都要加上註解以說明,不然它是不會幫你自動產生,而會出現 「No documentation available.」。



2013年11月14日 星期四

ASP.NET MVC 4 中 Controller 與 ApiController 做讀取、新增、更新、刪除 ( CRUD )

在 ASP.NET MVC 4 架構上,WebApi ( ApiController ) 較適合做資料處理與提供的動作,而 MVC 4 Web ( Controller ) 內比較要配合 View 層資料顯示而做異動,這時候如果要把 ApiController 和 Controller 切開來,那這兩個部分的溝通就會很常使用了。如果你是用 WebApi 與純 HTML + javascript 的架構,若是一頁蒐集各資料表的某些資料,那在 View 那一段就必須要與 WebApi 溝通多次才能將資料湊齊,再加上關聯資料,那就更複雜了,另外一點,如果各個 WebApi 的 Url 寫在頁面上,可能會有資安的危險,因為 WebApi 是開放的資料,如果不是任何人都能存取,那就不能玩此架構了。

在 Api 與 View 之間在卡一個 Controller 或者是 WebApi,做資料中繼站,把資料蒐集好在一次送到 View,而 View 收到資料也只需要拆解資料後分散到各個欄位之中。最後要 submit 整包送回 Model 也是一樣在 Controller 或者是 WebApi 解析驗證後再分散到各個 Api ,這樣資安會大大的提升。

因此此篇要講解如何在 Controller 對 WebApi 做讀取、新增、更新、刪除 ( CRUD ) 的動作。

1.

在 BaseController ( 如果沒有 BaseController 就寫在該隻 Controller 內 ) 繼承 ApiController 或 Controller,再來建置幾個變數:
public class BaseApiController : ApiController 
{

    protected DefaultConnection db = new DefaultConnection(); // 資料庫連接

    protected HttpClient client;

    protected HttpResponseMessage response;

    public BaseApiController()
    {
        client = new HttpClient();
        client.BaseAddress = new Uri("Api Url");
    }
}

2.

這時候在可以在 Controller 去非同步取得 WebApi 的結果,在宣告回傳數值前要加上 async,並且回傳數值要用 Task 包起來,說明這是個數值是非同步取得的結果,於接收值端前面要加上 await 前綴參考,表示暫停執行方法,直到等候的工作完成。所以在程式中要引用參考:
using System.Threading.Tasks;

在接著非同步取得回來的 Json 字串要轉成類別形式,需要用到 JsonConvert 的方法,所以要在 NuGet 下載 Json.NET 外掛:

程式中要再引用參考:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

3.

在 Index 的 Function 中完整程式碼:
public async Task<ActionResult> Index()
{
    response = await client.GetAsync("api/RMS/CompanyApi/");
    string t_s = await response.Content.ReadAsStringAsync();
    var rms_company = JsonConvert.DeserializeObject<List<RMS_Company>>(t_s);

    return View(rms_company);
}

在 Details 的 Function 中完整程式碼:
public async Task<ActionResult> Details(Guid? id = null)
{
    response = await client.GetAsync("api/RMS/CompanyApi/" + id);
    string t_s = await response.Content.ReadAsStringAsync();
    var rms_company = JsonConvert.DeserializeObject<RMS_Company>(t_s);

    if (rms_company == null)
    {
        return HttpNotFound();
    }
    return View(rms_company);
}

在 Create 的 Function 中完整程式碼:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(RMS_Company rms_company)
{
    if (ModelState.IsValid)
    {
        setContent(JsonConvert.SerializeObject(rms_company));
        response = await client.PostAsync("api/RMS/CompanyApi", content);

        // db.RMS_Company.Add(rms_company);
        // db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(rms_company);
}

在 Edit 的 Function 中完整程式碼:
public async Task<ActionResult> Edit(Guid? id = null)
{
    // RMS_Company rms_company = db.RMS_Company.Find(id);
    response = await client.GetAsync("api/RMS/CompanyApi/" + id);
    string t_s = await response.Content.ReadAsStringAsync();
    var rms_company = JsonConvert.DeserializeObject<RMS_Company>(t_s);

    if (rms_company == null)
    {
        return HttpNotFound();
    }
    return View(rms_company);
}

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(RMS_Company rms_company)
{
    if (ModelState.IsValid)
    {
        rms_company.Updater = Guid.NewGuid();
        rms_company.UpdateOn = DateTime.Now;

        setContent(JsonConvert.SerializeObject(rms_company));
        response = await client.PutAsync("api/RMS/CompanyApi/" + rms_company.CompanyId, content);

        // db.Entry(rms_company).State = EntityState.Modified;
        // db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(rms_company);
}

在 Delete 的 Function 中完整程式碼:
public async Task<ActionResult> Delete(Guid? id = null)
{
    // RMS_Company rms_company = db.RMS_Company.Find(id);
    response = await client.GetAsync("api/RMS/CompanyApi/" + id);
    string t_s = await response.Content.ReadAsStringAsync();
    var rms_company = JsonConvert.DeserializeObject<RMS_Company>(t_s);
    
    if (rms_company == null)
    {
        return HttpNotFound();
    }
    return View(rms_company);
}

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> DeleteConfirmed(Guid id)
{
    // RMS_Company rms_company = db.RMS_Company.Find(id);
    response = await client.DeleteAsync("api/RMS/CompanyApi/" + id);
    // db.RMS_Company.Remove(rms_company);
    // db.SaveChanges();
    return RedirectToAction("Index");
}