NHunspell – C#/.NET free Spell Checker

NHunspell 其實所指的就是 Hunspell 的 .NET 版本,而 Hunspell 則是 MySpell 的延伸專案,主要原理是根據 OpenOffice 建立的字典檔來判斷拼寫是否正確。

Requirement

Open Office Dictionaries

Hunspell

專案資源

雖然說 NHunspell 在 Nuget 上面就可以找到最新版本的 Resource 下載,但是缺少了字典檔以及 Hunspell 的 dll 可是不能執行的。預設 Hunspell 的 dll 以及字典檔需要在輸出目錄之下。

Sample Code

using (Hunspell hunspell = new Hunspell("en_us.aff", "en_us.dic"))
{
foreach (string word in words)
{

bool correct = hunspell.Spell(word);

// If the word is spelled incorrectly, add it to the list of words to be
// returned along with suggestions

List<string> suggestions = new List<string>();
if (!correct)
{
suggestions = hunspell.Suggest(word);
suggestions.Sort();
}
}
}

在使用 hunspell 物件的時候同時載入兩個字典檔 .aff & .dic,而在 Spell Check 的部分主要分為幾個方法

  1. Spell : 檢查拼寫是否正確
  2. Suggest : 取得建議的字詞

Reference

NHunspell: C#/.NET free Spell Checker
NHunspell Component for Visual Studio – CodeProject
Using NHunSpell in ASP.NET with JSON Web Service and jQuery
NHunspellHunspell for the .NET platform

觀察特定 IIS Application Pool 使用資源

其實是一個很常見的小技巧,不過不常用有的時候還是會忘記,在 IIS 下我們可能會執行多個 process 來處理多個 web Site ,那假如說今天某一個 Site 狀況不穩定的時候,怎麼用系統管理員觀察特定的 Site 。

image

在系統管理員中看到的都是 w3wp 的 process 這代表著一個個的 Application pool ,所以如果有多個 Web Site 共用 App Pool 就沒有辦法在這邊看出使用情況了,但是在系統管理員中的都是顯示 w3wp 雖然我們知道說這是代表 App Pool 的使用,可是並不知道說哪個對應到哪個 App Pool 。

系統管理員選擇顯示欄位

從顯示 > 選擇欄位,可以看到如下的畫面,在勾選 PID process Id 可以顯示出每一支 process 的 id,再利用這個欄位來做 App Pool 的對應。

image

IIS App Pool 顯示 PID (IIS6)

找到 process id 的對應之後接下來就是要顯示每支 App Pool 執行的 PID 這樣就可以找到每支 App Pool 使用的 CPU 還有記憶體資源。利用 IisApp.vbs 這一支內建的程式可以取得 App Pool PID。

image

這樣就可以跟系統管理員顯示的 w3wp.exe 做對應。

IIS App Pool 顯示 PID (IIS7)

如果是 IIS7 的話則是使用 appcmd list wp 來顯示對應的 PID

image

ASP.NET MVC Customize Base View

在寫 ASP.NET MVC 的時候,我幾乎都會讓 Controller 繼承一個自訂的 BaseController ,在某些情況下會方便不少,像是登入帳號共用 Session 或是一些其他共用的功能跟頁面驗證都可以利用繼承來大量設定。不過在跟 View 整合的時候就會在一頁一頁去用 ViewBag 或是 ViewData 傳參數,有時候很多 View 都會同時用到一樣的 ViewBag 或是 ViewData 。 其實在 ASP.NET MVC 也有 Base View 這種東西。

建立 Base View

Razor View 預設都會繼承 System.Web.Mvc.WebViewPage ,所以原理也是一樣,只要自訂一個類別先繼承 System.Web.Mvc.WebViewPage ,再讓 View 來繼承 Base 類別,就達到了在之間抽離一層來實作一些共用的方法。

public abstract class BaseViewPage<T> : 
System.Web.Mvc.WebViewPage<T>
{
...
}

在這邊必須要繼承 System.Web.Mvc.WebViewPage<T>  泛型的類別就是強型別的類別,加入一個判斷登入語系的 ViewBag ,這個 ViewBag 在 Base Controller 就會判斷完成,所以每一個頁面都會需要用到。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace BaseViewTest.Views
{
public abstract class BaseViewPage<T> :
System.Web.Mvc.WebViewPage<T>
{

public string Language
{
get
{
return ViewBag.Lang;
}
}

public override void Execute()
{

}
}
}

在 MVC 4 之後 System.Web.Mvc.WebViewPage<T> 必須要實作 Execute() 方法。

讓 View 繼承 Base View

建立完 Base View 之後就是要讓其他 View 繼承這個 Class ,這個就需要在 Web.Config 中設定,我是設定在 View 資料夾下的 Web.Config (不是最外層喔)

<pages pageBaseType="BaseViewTest.Views.BaseViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
</namespaces>
</pages>

在最外層的 Web.Config 也有 <Page pageBaseType> 標籤可以做屬性設定,不過一樣在內層也必須要再設定一次。

直接使用 Base View

接下來如果是需要使用到寫在 Base View 裡面的方法或屬性的時候就只要直接呼叫好了

@Language

Javascript Metro Style App (4) 利用 Web API 建置 Metro Style App Grid Application Service

在這篇會用最快的方式去建置提供 Metro Style App Grid Application 資料的 Web API

根據 Metro Style App 預設的 json 格式建立 ViewModel

public class MetroStyleApp
{
public class Group
{
public string key { get; set; }
public string title { get; set; }
public string subtitle { get; set; }
public string backgroundImage { get; set; }
public string description { get; set; }
public IEnumerable<Item> items { get; set; }
}

public class Item
{
public string title { get; set; }
public string subtitle { get; set; }
public string content { get; set; }
public string backgroundImage { get; set; }
public string description { get; set; }
public Group group { get; set; }
}
}

✕這邊要自訂名稱也是可以,不過在 Metro Style App Grid Application 裡面就要去修改相對應的 javascript 了

建立資料庫

Group

id int
name nvarchar
subTitle nvarchar
title nvarchar
backgroundImage nvarchar
description nvarchar

Item

id int
groupId int
subTitle nvarchar
title nvarchar
article nvarchar
backgroundImage nvarchar
description nvarchar

✕其實資料庫怎麼建都沒有關係,只要能轉型成為 ViewModel 格式就OK了,這邊只是個人的作法

將資料庫資料轉為 ViewModel 輸出

搭配 Entity Framework & Value Injecter

public IEnumerable<MetroStyleApp.Item> Get()
{
NearByEntities db = new NearByEntities();
List<Item> dblist = db.Items.ToList();
List<MetroStyleApp.Item> itemList =
dblist.Select(it => new MetroStyleApp.Item()
.InjectFrom(it, new
{
group = (MetroStyleApp.Group)new MetroStyleApp.Group()
.InjectFrom(it.group, new { key = it.group.name })
,
content = it.article
}
))
.Cast<MetroStyleApp.Item>().ToList();
return itemList;
}

Metro Style App Grid Application 欄位顯示區塊對應

在群組頁面顯示情況下

image

在內容頁面顯示情況之下

image
這邊的 Content 是可以接受 Html 語法的


MVC4 多了什麼新東西 – Web API
Metro Style App (1) 建立 Javascript Metro Style Project
ASP.NET C# MVC ViewModel with valueinjecter

WCF & WebAPI 序列化 Dictionary 物件到 Json 的格式問題

最近同事遇到一個問題,在 WCF 跟 ASP.NET MVC 4 的 WebAPI 下回傳 Dictionary 的物件的時候和 Json.net 序列化的結果不同,回傳的格式也不是那麼方便使用。

WCF & WebAPI 回傳 Dictionary 的格式

利用 Web API 建立

public object Get()
{
Dictionary<string, object> dic = new Dictionary<string, object>();
dic.Add("key1", "value1");
dic.Add("key2", "value2");
return dic;
}

他回傳的格式會是

[
{
Key: "key1",
Value: "value1"
},
{
Key: "key2",
Value: "value2"
}
]

但是這樣子回傳的 Json 物件在 Javascript 下並不方便直接使用,沒辦法用 object.key1 直接取值,或是 object[“key1”] 也沒辦法。比較理想的回傳格式應該是

{
"key1": "value1",
"key2": "value2"
}

解決方法

這個問題是 WCF & Web API 再回傳的時候序列化元件的邏輯處理的,其實嚴格來說並不是 Bug ,只是不好用而已,當然有其他幾種方法來處理。

  1. 直接組字串
  2. 回傳的時候用 Json.NET 序列化之後再回傳

這兩種都不是很好的做法,有點失去 WCF & Web API 原本的用意。

第三種作法

利用自訂的 class 去改變序列化的邏輯,在這邊我們建立

[Serializable]
public class JsonDictionary : ISerializable
{
Dictionary<string, object> dict = new Dictionary<string, object>();
public JsonDictionary() { }
public JsonDictionary(Dictionary<string, object> dic)
{
this.dict = dic;
}
protected JsonDictionary(SerializationInfo info, StreamingContext context)
{
foreach (var entry in info)
{
dict.Add(entry.Name, entry.Value);
}
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
foreach (string key in dict.Keys)
{
info.AddValue(key, dict[key], dict[key] == null ? typeof(object) : dict[key].GetType());
}
}

public void Add(string key, object value)
{
dict.Add(key, value);
}

public static explicit operator JsonDictionary(Dictionary<string, object> dic)
{
return new JsonDictionary(dic);
}
}

改變 GetObjectData 用以取代原先序列化的邏輯,並且加上 explicit operator 方便跟 Dictionary 做轉型的動作。

改寫之後的 Web API

public object Get()
{
Dictionary<string, object> dic = new Dictionary<string, object>();
dic.Add("key1", "value1");
dic.Add("key2", "value2");
return (JsonDictionary)dic;
}

在這邊再回傳的時候先轉型為剛剛自訂的 JsonDictionary ,可以發現的是一開始我就設定回傳型別為 object ,只是為了讓這個方法看起來比較好用一些。回傳前轉型回傳型態也是要跟著改變的。

{
"key1": "value1",
"key2": "value2"
}

這樣回傳的時候就會是想要的格式了。


JSON deserializer does not deserialize a Dictionary correctly

c# – .NET WCF Json deserializing a Dictionary<int, int> – Stack

c#WCF – POST JSON Dictionary without Key/Value Text – Stack

c# – Make ASP.NET WCF convert dictionary to JSON, omitting “Key

WCF JSON Serialization is flawed « Duncan Smart’s Weblog

.NET by Example: JSON services revisited: using a Dictionary as a

如何在 Razor 中使用 #if DEBUG 判斷

在經過不斷的嘗試還有搜尋之後

Razor 中沒有辦法使用 #if DEBUG 判斷!!

在動態編譯的 Razor 中是沒有辦法這樣做的,只能繞路由其他方式來處理。

由需要編譯的檔案中傳遞參數

例如說在 BaseController 加入

ViewBag.isDebug = false;
#if DEBUG
ViewBag.isDebug = true;
#endif

再由 ViewBag 傳遞參數到 Razor 中處理

@if (ViewBag.isDebug)
{
.....
}

或是利用 HttpContext.Current.IsDebuggingEnabled

在 Web.Config 中可以找到

<compilation debug="true" targetFramework="4.0">

這一段中的 debug=”true” 就是決定 HttpContext.Current.IsDebuggingEnabled 回傳的值,而預設的 Web.Release.Config 也有一段是

<compilation xdt:Transform="RemoveAttributes(debug)"/>

移除 debug=”true” 的屬性,所以只要利用

@if(HttpContext.Current.IsDebuggingEnabled)
{
...
}

就可以做判斷了,不過缺點是他只有 isDebug ,如果有自行新增 Symbol 或是用到 #if TRACE 就沒有辦法這樣處理。


如何善用「偵錯模式」進行 ASP.NET 網站或 .NET 程式開發

Javascript Metro Style App (2) 設定 package.appxmanifest

只有空殼的專案當然是不行的,接下來開始對整個專案進行最基本的客製化設定。開啟專案底下的 package.appxmanifest ,在這邊有幾個關於 APP 的設定。
image

Application UI

  • 顯示名稱
  • 敘述
  • 起始頁面
  • Logo 圖片
  • APP 支援的瀏覽模式

Metro App 對於圖片都有制式規格,為了讓整個 App 可以融入 Metro UI 的環境

  • Logo 150*150 (Metor UI 的方塊圖)
  • Wide Logo 310*150 (Metro UI 下使用者可以自行放大成長方圖)
  • Small Logo 30*30
  • Badge Logo 24*24
  • Splash Screen 620*300 (進入 APP Loading 顯示)

Capabilities

這邊主要是設定這個 APP 對於裝置上的功能,例如說本機音樂檔案播放、相機功能。
image

Declarations

這邊也是跟系統功能有關的部分,例如說請求成為放入 DVD 的時候的預設播放軟體。
image
image

Content Urls

設定 APP 在網路上的內容資料來源
image

Packaging

APP 本身的發佈資訊
image


Unable to load Package.appxmanifest on Visual – Ronald Widha
What is Package.appxmanifest File in Windows 8 Metro Style App

利用 Resource file 建立多國語系驗證訊息

在建立多國語系網站的時候,我們可以利用 Resource file 來幫助建置。

  • ASP.NET MVC 2 以上

如果有利用到 ValidationAttritube 來幫忙做資料驗證的話,最一般的使用方法是。

[Required(ErrorMessage = "標題為必填欄位")]
public string Title { get; set; }

在一般的狀況下這樣就很夠用了,不過由於需要多國語系,驗證訊息當然也要讓外國人看得懂。這邊的錯誤沒有辦法這樣設定了。

ErrorMessageResourceName

沒想到這些需求都很貼心地已經有相關功能了,我一開始還打算繞一大圈來處理。

[Required(ErrorMessageResourceName = "TitleIsRequired")]
public string Title { get; set; }

利用 ErrorMessageResourceName 的屬性,可以將 ErrorMessage 根據當下語系對應的 Resource file 裡面找出來。

ErrorMessageResourceType

光是只有設定 ErrorMessageResourceName  是不夠的。

[Required(ErrorMessageResourceName = "TitleIsRequired")
,ErrorMessageResourceType = typeof(ErrorMessage)]
public string Title { get; set; }

還需要設定 ErrorMessageResourceType  屬性,這邊的 ErrorMessageResourceType  就是對應 Resource 的類別。

Could not find any resources appropriate for the specified culture or the neutral culture.

在執行的時候或許會遇到這個錯誤,這也就是在輸出驗證訊息的時候找尋不到 Localize 的 Resource 的錯誤。

image

如果有用到自訂的 ValidationAttritube 實作 IClientValidatable

public IEnumerable<ModelClientValidationRule> GetClientValidationRules
(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule
{
ValidationType = "required",
ErrorMessage = this.ErrorMessageString
};
}

ErrorMessage = this.ErrorMessageString 才是針對多國語系的輸出。


如何讓 App_GlobalResources 裡的全域資源檔變成公開類別

加入 MVC SiteMap provider 到專案當中

MvcSiteMapProvider
之前開發網站的時候沒有用到類似的工具,或著是用自己定義的元件來做類似的事情,後來在同事推薦下加入到開發當中。

  • 加入 MvcSiteMapProvider 到專案
  • 定義 SiteMap 階層
  • 利用 SiteMap 物件產生選單列表
  • 自行定義 Display Template
  • 搭配角色權限使用 SiteMap
  • 動態產生 SiteMapNode
  • 動態切換多個 SiteMapProvider

加入 MvcSiteMapProvider 到專案

  • 利用 Nuget >  MvcSitemapProvider
  • Nuget  的 Package Manager Console PM> Install-Package MvcSitemapProvider

一開始可以看到加入的檔案中 Mvc.sitemap 就是定義 sitemap 的檔案,檔案格式是用 XML ,預設的部分有加入 Home 跟 About 的部分。

<mvcSiteMapNode title="Home" controller="Home" action="Index">
<mvcSiteMapNode title="About" controller="Home" action="About"/>
</mvcSiteMapNode>

除了 Mvc.sitemap 也會在 Views/Shared/DisplayTemplates 加入配合產生 Ment 的 Display Templates 。

定義 SiteMap 階層

從預設的 .sitemap 檔案就可以看出基本的架構,利用 XML 階層的概念配合整個網站的架構。一開始加入 SiteMap 的另一個好處是會逼得自己了解整個網站的邏輯,遇到規劃上的問題也容易發現。在這邊我直接用 ASP.NET MVC 4 Beta 的預設專案 Template 做 SiteMap。

<mvcSiteMapNode title="首頁" controller="Home" action="Index">
<mvcSiteMapNode title="關於 MVC 4" controller="Home" action="About"/>
<mvcSiteMapNode title="會員登入" controller="Account" action="Login"/>
<mvcSiteMapNode title="註冊會員" controller="Account" action="Register"/>
<mvcSiteMapNode title="變更密碼" controller="Account" action="ChangePassword"/>
</mvcSiteMapNode>

利用 SiteMap 物件產生選單列表

定義完 SiteMap 之後,可以用來幫助我們產生一些頁面上的元件。比較常用的就是 Header 的 Menu 或是 SiteMap Path 之類的。

Html.MvcSiteMap().SiteMap()

我替換掉 Layout.cshtml 下的 menu 並且換上 MvcSiteMapProvider 的 Helper。

<nav>
@Html.MvcSiteMap().SiteMap()
</nav>

產生出來的 Html

<ul id="menu">
<li><a href="/">首頁</a> </li>
<li><a href="/Home/About">關於 MVC 4</a> </li>
<li><a href="/Account/Login">會員登入</a> </li>
<li><a href="/Account/Register">註冊會員</a> </li>
<li><a href="/Account/ChangePassword">變更密碼</a> </li>
</ul>

image
這邊預設的 menu 比較簡單,如果想要能有華麗一點的多階層 menu 就是要修改 Template 配合 javascript css。

自行定義 Display Template

在評估的時候,大家都會蠻害怕如果 Helper 的格式不能變動怎麼辦,或是說被元件綁死,但是其實完全都是可以自行定義的。之前有提到一開始加入的 Display Templates 資料夾裡面就是產出的格式,MvcSiteMapProvider 做的事只是利用 XML 裡面的定義幫忙產生物件的階層。Html 顯示的邏輯都是自訂的。我比較習慣 Razor 的寫法,所以移除掉了 ascx 的部分,但是其實做的事情都是一樣的。

  • MenuHelperModel.cshtml > @Html.MvcSiteMap().Menu()
  • SiteMapHelperModel.cshtml > @Html.MvcSiteMap().SiteMap()
  • SiteMapNodeModel.cshtml > 單一 Node 顯示方式
  • SiteMapNodeModelList.cshtml > List<Node> 顯示方式
  • SiteMapPathHelperModel.cshtml > @Html.MvcSiteMap().SiteMapPath()
  • SiteMapTitleHelperModel.cshtml > @Html.MvcSiteMap().SiteMapTitle()

這只是預設的設定,其實只要在使用方法的時候多加入參數就可以。

@Html.MvcSiteMap().Menu("想要用的template.cshtml")

搭配角色權限使用 SiteMap

在設定 SiteMapNode 的時候也有一個屬性 Roles 可以做結合

<mvcSiteMapNode title="系統管理" roles="SystemAdmin,Supervisor"
area="Admin" controller="Home" action="Index">
<mvcSiteMapNode title="會員管理" roles="SystemAdmin,Supervisor"
area="Admin" controller="Account" action="Index"/>
</mvcSiteMapNode>

這邊的 roles 就是整合的 .net 的 Forms Authentication 的 Roles 屬性,當沒有權限頁面就不會出現在 SiteMap 裡面。這邊只是針對 SiteMap 部份顯示與否,如果直接存取 action 還是可以進入。不過我們可以配合這個邏輯來自訂 ActionFilter。

public class RolesAuthenticationAttribute : ActionFilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (SiteMap.CurrentNode == null)
{
throw new UnauthorizedAccessException();
}
}
}

只要將 ActionFilterAttribute 套用到 BaseController 或是只想針對個別套用也可以,這樣在權限控管上就可以由 sitemap 來做統一的設定。

自訂角色可以參考 ASP.NET 自訂角色的方式(不用實做 Role Provider)

動態產生 SiteMapNode

如果說需要動態產生 SiteMap 節點,像是 Blogger 這樣,Menu 是根據資料庫或其他來源來產生,那就需要動態來產生。

<mvcSiteMapNode title="" controller="Page" action="Index"
dynamicNodeProvider="MasonPrint.MVC4.Beta.Utilities.DynamicNodeProviders.PageDynamicNodeProvider
, MasonPrint.MVC4.Beta"/>

利用設定 DynamicNodeProvider 來做到動態節點。

public class PageDynamicNodeProvider : DynamicNodeProviderBase
{
public override IEnumerable<DynamicNode> GetDynamicNodeCollection()
{
DatabaseEntities1 db = new DatabaseEntities1();
var returnValue = new List<DynamicNode>();
//根據資料來源建立節點
foreach (var item in db.Pages.ToList())
{
DynamicNode node = new DynamicNode();
node.Title = item.Title;
node.ParentKey = "Page";
node.Key = "Page_" + item.Id;
node.RouteValues.Add("id", item.Id);

returnValue.Add(node);
}

// Return
return returnValue;
}
}

動態切換多個 SiteMapProvider

如果需要動態切換不同的 SiteMapProvider

SiteMap.Provider.ParentProvider = SiteMap.Providers["MvcSiteMapProvider1"];

需要注意事項

  1. .Sitemap 設定中 Key 必須是唯一值
  2. 如果 Action 不存在則 SiteMap 物件中節點不會產生
  3. SiteMap 物件建立之後會暫存在 Server 端

本篇專案下載


ASP.NET MVC SiteMap provider

Documenting [Authorize] attribute usage, producing a report

NuGet Package Manager ★★★★★ – The AppStore in Visual Studio

概略解釋 Forms Authentication 的運作

ASP.NET 自訂角色的方式(不用實做 Role Provider)

MVC 4 多了什麼新東西 – Bundling and Minification

以往在加入靜態檔案例如說 Css & Scripts 的時候,都是針對一個一個檔案在 Html 中加入參考。等到專案需要上線的時候,再將 Css 或是 js 檔案整合成同一個,並且將檔案最小化來獲得更好的 Loading 速度。

Bundling and Minification

所以說為了讓這些工作可以更簡化,.NET 加入了這個功能,編輯的時候一樣是分開檔案編輯。在 MVC 3 我的作法是在 Layout 的部分加入如下的檔案。

<script src="@Url.Content("~/Scripts/jquery-1.7.1.min.js")" 
type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.mobile-1.0.1.min.js")"
type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")"
type="text/javascript"></script>

能夠參考 minified 過都盡量參考 minified 過的檔案,可是也會造成在需要修改編輯的時候難以編輯,或著是利用 debug 判斷要參考的檔案。

image

可以看到這邊花的時間是 78+78+141 ms, 那在 mvc 4 提供的這個方法就是只要加入一行。

<script src="@Url.Content("~/Scripts/js")" type="text/javascript"></script>

這邊就會抓取 Scripts 資料夾底下的所有 .js 檔案,這是我的 Scripts 資料夾。

image

在這邊 mvc 4 會自動把 Scripts 裡面的 js 檔案整合成同一個(Bundling ),並且作 minified 的動作(Minificaton),從瀏覽器也可以看到,檔案確實已經整合而且 minified 過。

image

這邊檔案大小約等於原本的三個檔案相加(因為原本參考的是 .min 檔案),但是 loading 的速度反而增加了,因為 request 的數量降低了,進而提升檔案的回傳速度。

image

同樣的方法也適用在 css 上,並且支援 less css

image

  • Bundling and Minification 除了在 Mvc 4 中支援之外,.net framework 4.5 也會支援這種做法。
  • 目前還在 Beta 版本,經過實測會發現如果習慣邊測邊改 javascript 會遇到很大的不便,因為 .net 將 Bundling and Minification 的檔案暫存起來,造成修改過後效果沒有辦法在 browser 重新整理之後馬上顯示出來。
  • 如果是檔案加入需要有一定順序會比較麻煩,因為目前版本是由檔名排序加入。


bundling and minification support