加入 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

動態修改 View Engine 的搜尋順序

應該有寫 ASP.NET MVC 都看過這個錯誤畫面,其實這邊就是預設的搜尋順序啦,如果找不到的時候就會出現下面的畫面。這個是 Home/Index 執行時候的畫面,預設動作會先找尋 WebPage View 再找尋 Razor (MVC 3 Later) View ,優先順序是 Views/{Controller}/{Action} > Views/Shared/{Action} 。
image
今天剛好遇到一個多語系支援的需求,但是又不是每一個頁面都支援全部的語系,因為內容頁面有些是另外由人工翻譯完才加入,希望在還沒翻譯完之前先顯示原文,可以根據直接加入檔案新增語言,所以我的想法先找語系資料夾下面的 View ,如果沒有的話再用原本的搜尋方式尋找。
image
如果在不同語系有另外設定的 View 就針對語系先套用,沒有的話就是預設的輸出了,這樣感覺很方便,而且假如說原本頁面的英文版本還沒翻譯好,也可以等翻譯完再加入,還沒加入之前瀏覽也不會也掛掉的問題。但是要這樣做預設的功能沒有支援,所以必須修改 View Engine 尋找 View 的順序,讓他在找尋的時候先根據語系尋找。

建立自訂 View Engine 繼承 RazorViewEngine

public class MultiLanguegeRazorViewEngine : RazorViewEngine

這個地方因為我只有用到 Razor 的 View 所以就只需要一個自訂的 View Engine 繼承 RazorViewEngign,如果有用到 Aspx 或是 ascx 的 View 就是需要另外一個 View Engine 繼承 WebFormViewEngine。最後在依照想要哪個View Engine 先做找尋的動作加入 ViewEngines。

修改 View Engine Formats 屬性

  • AreaMasterLocationFormats (找尋 Area 裡面的 MasterPage)
  • AreaPartialViewLocationFormats (找尋 Area 裡面的 ParttialView)
  • AreaViewLocationFormats (找尋 Area 裡面的 View)
  • MasterLocationFormats (找尋 MasterPage )
  • PartialViewLocationFormats (找尋 ParticalView)
  • ViewLocationFormats (找尋 View)

View Engine 有這幾個 Format 屬性,在建構子的時候修改,在不同情況下會根據不同 Format 做找尋的動作。如果有需要修改的 Format 在額外去做設定,不然就會以繼承的為準。

public MultiLanguegeRazorViewEngine()
: base()
{
ViewLocationFormats = new[] {
"~/Views/{1}/%1/{0}.cshtml",
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/%1/{0}.cshtml",
"~/Views/Shared/{0}.cshtml",
};

PartialViewLocationFormats = new[] {
"~/Views/{1}/%1/{0}.cshtml",
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/%1/{0}.cshtml",
"~/Views/Shared/{0}.cshtml",
};
}

Override 原本的方法

在搜尋 View 的時候會去呼叫 ViewEngine 裡面的這幾種方法,在呼叫的時候把剛剛加入 Formats 裡面的 “%1” 替換成想要的位置,會在這邊作處理是因為 controller 處理完之後再呼叫 View 的時候會將 controllerContext 參數傳進來,這樣就可以從傳進來的 controllerContext 作為判斷依據來動態改變 View 的搜尋順序。

protected override 
IView CreatePartialView(ControllerContext controllerContext,
string partialPath)

{

string Lang = controllerContext.Controller.ViewBag.Lang;

return base.CreatePartialView(controllerContext, partialPath.Replace("%1", Lang));
}



protected override
IView CreateView(ControllerContext controllerContext,
string viewPath, string masterPath)

{

string Lang = controllerContext.Controller.ViewBag.Lang;

return base.CreateView(controllerContext, viewPath.Replace("%1", Lang), masterPath.Replace("%1", Lang));
}



protected override bool FileExists(
ControllerContext controllerContext, string virtualPath)

{

string Lang = controllerContext.Controller.ViewBag.Lang;

return base.FileExists(controllerContext, virtualPath.Replace("%1", Lang));
}

在 Global 修改 View Engine 成自訂的 View Engine

最後在Application_Start()的地方先清除預設的ViewEngines,在重新加入剛剛設定的MultiLanguegeRazorViewEngine。

protected void Application_Start()
{

ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new
MultiLanguegeRazorViewEngine());
}

這樣就大功告成了,ViewEngine 搜尋 View 的邏輯就會根據自訂的路徑順序來做搜尋的動作。

P.S.

Formats 參數設定是

  • {0}: ActionName
  • {1}:ControllerName
  • {2}:AreaName

有用到 AreaName 的地方就只有 Area 相關的 Formats,不是每個 Formats 都需要。


http://weblogs.asp.net/imranbaloch/archive/2011/06/27/view-engine-with-dynamic-view-location.aspx
Custom ViewEngine ASP.NET MVC 3 – Stack Overflow
Creating your own MVC View Engine For MVC Application

MVC4 多了什麼新東西 – Single Page Application (SPA)

ASP.NET MVC4 Beta 除了 Web API 也同樣新增了另一個專案範本,Single Page Application (SPA),為什麼會有只要一頁的專案,那應該只是拿來測試的吧,有必要特地新增一個專案範本嗎。
image
原來這邊的 Single Page 指的是用單一頁面完成整個網站的瀏覽,利用 javascript 直接建立起 Client 的系統,再透過 javascript 來處理 Request & Response ,好處是可以讓網頁動作更流暢.也可以結合 Web API 的概念,用同樣的 controller 同時對不同 Client 支援(當然 client 還是要分好幾套來寫),這樣想起來其實 SPA 根本就應該跟 Web API 放一起就好了啊。
spa.clientstack
建立的專案其實重點就是在 javascript 上面,這邊 .net 跟 Visual Studio 能提供的支援就少了很多了,但是我覺得將來漸漸這種類型的網站會愈來愈多,更重視在使用者互動上。
image


Steve Sanderson’s Knockout Blog

MVC4 多了什麼新東西 – Web API

image
Microsoft 在前幾天發表了 ASP.NET MVC4 Beta,長久以來 .net 在 Layout 方面都顯得較為弱勢,畢竟系統邏輯才是 Microsoft 的強項,從 ASP.NET WebPage > ASP.NET MVC > ASP.NET Web API 都一直在做著輕量化的工程,讓整個 Framework 不要去包裝太多。
Web API 這個 Framework 結合了 ASP.NET MVC 還有 WCF 的概念而成,讓 Web API 的 Service 變得更 RESTful 。也是 Microsoft 放手讓 .net developer 可以更加便利選擇 Client 端的技術,Web 的 jQuery , Extjs , Flash, iOS 的 Cocoa , Android Java , WindowsPhone Sliverlight …..

建立 Web API 專案

在安裝完 ASP.NET MVC4 Beta 之後,建立新的 ASP.NET MVC 4 Web Application
image
在選單中可以發現多了一個新的 Template – Web API
image
建立之後的檔案結構目錄依然是保持 ASP.NET MVC 的樣子,但是可以發現多了一個 ValueController ,這個就是 Web API 的重點了。
image
ValueController 裡面的程式碼,改由繼承 ApiController ,Method 名稱就直接對應 HttpMethod

public class ValuesController : ApiController{
// GET /api/values
public IEnumerable Get()
{
return new string[] { "value1", "value2" };
}

// GET /api/values/5
public string Get(int id)
{
return "value";
}

// POST /api/values
public void Post(string value)
{
}

// PUT /api/values/5
public void Put(int id, string value)
{
}

// DELETE /api/values/5
public void Delete(int id)
{
}
}

另外在 Global.asax Route 也可以看到多註冊了一組

routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);

利用 Chrome REST Console 測試

image

image

回傳的結果

image

也支援 Xml

image


Tutorial: Your First Web API

Video: Your First Web API

Building HTTP services with ASP.NET Web API in MVC 4 Beta

Chrome 擴充功能 – REST Console

SQL Server 利用產生指令碼功能複製資料庫

以往要複製還原都是用 SQL 資料庫內建的備份還原功能,將檔案備份到 .bak 檔案之中,還原的時候也可以選擇日期版本,可以說是相當的便利。

但是在某些狀況下並沒有辦法使用內建的備份還原功能

  1. 內建的備份還原必須是針對本機的檔案,如果要針對遠端的資料庫就必須先把備份檔案複製到資料庫的機器上面。
  2. 必須具備資料庫備份還原的權限

常常在不同環境下並沒有那麼完整的權限,但是又必須複製資料庫的時候就可以用到。

資料庫>右鍵>產生指令碼

image

image

再轉出的時候有一個進階選項可以進入做更完整的設定

image

如果需要複製資料庫資料,就需要在這邊做設定的動作。我一開始雖然知道這個功能,可是不知道的是還有複製資料的部分,都是先產生 Table Schema 再慢慢轉入資料,現在想想真的太傻了。

image

最後只要把產出的 script 拿到新資料庫上面執行就好了,不過速度上會慢非常多喔,跟使用 bak 備份還原比較起來。

ModelStateValue 常遇到的同名衝突

ASP.NET MVC Model Binder 在 mvc 中處理掉所有 Client Side 跟 Server Side 的對應,處理掉很多繁瑣的小細節,寫起來變得非常的直覺。但是就是因為太直覺了,讓人往往忘記它的存在,Model Binder 發生的問題也是非常容易鬼打牆的地方。遇到這種問題我之前也是非常相信直覺的判斷,但是事實證明不能太鐵齒,遇到問題還是趕快把 .pdb 加進來一起偵錯吧
我個人很喜歡在 View 裡面用強型別 Model 配合 HTml.Helper 來做輸出的動作,不過有時候會遇到 Helper 輸出結果跟預設的想像不同的狀況。預設的想法就是 Helper 應該會輸出 Model 當下狀態的結果,不過反覆檢查下才知道 Helper 動作並不是如此。

ModelStateValue 的同名衝突

Html.Helper 在強型別配合下並不是直接輸出 Model 裡面的值,他一樣會同時參考其他資料來源,所以當使用 HtmlHelper 的時候,會容易因為呼叫的方式而預想成是直接根據強型別 Model 來作輸出。

Binding 進 ModelStateValue 裡面的資料

ModelState 在外部存取的時候是 readOnly 所以沒有辦法直接加入值,最常遇到的狀況就是在 get 或 post 的 Data 資料被 binding 進去 ModelStateValue 裡面,為了重現這個狀況
首先我建立一個當作強型別的 Model

public class HomeIndexModel{
public string Phone { get; set; }
}
相關的 Controller
public ActionResult Index(HomeIndexModel model)
{
model.Phone = "0";
return View(model);
}
View
@model ModelStateValue.Models.HomeIndexModel<section class="features">
@using (Html.BeginForm())
{
@Html.TextBoxFor(it => it.Phone)
<input type="submit" value="submit" />
}
</section>

首先在一開始進入頁面的時候,helper 顯示出來是 controller 設定的 0。

image

之後輸入其他值並且 Submit 回 Server Side。

image

Model 的值也確實被改變了。

image

但是之後回傳頁面卻還是 Post 回傳的值。

image

這個問題當初確實困擾我一陣子,當然有很多方法可以避免,但是跟我原本對 MVC 的想法有一些衝突,其實也就是對整個 Framework 不夠熟悉才會這樣。雖然已經被設置為強型別的 View ,但是 Html Helper 的資料來源並不是強型別 Model 而是 ModelState

image

而因為 Post 回 Server 的時候 PostData 已經綁定進入 ModelState 所以在輸出的時候抓到的並不是強型別 Model 的值,而是一開始被 binding 的部分。


http://stackoverflow.com/questions/9529730/view-does-not-affect-models-changes

ASP.NET MVC 開發心得分享 (6):小心使用 FormCollection

ASP.NET MVC 開發心得分享 (4):微調 Model Binder 屬性

ASP.NET MVC 開發心得分享 (12):Model Binder 的陷阱

設定 Visual Studio 偵錯 Microsoft .NET 元件

之前想要對 ASP.NET MVC Framework 元件做偵錯的時候我都是下載原始碼專案,然後把參考的部分改掉,還要額外設定 Config 的部分(我都是參考這篇)。後來才發現,如果只是想要看到逐步偵錯的程式碼,其實可以不必要這麼麻煩,微軟的部分元件有開放 .pdb 可以看看程式碼到底了些什麼事,到底是元件的 bug 還是根本就是使用的方式錯了。

Visual Studio 2010 > Debug > Options and Settings > Debugging > General

  1. 取消 Enable Just My Code
  2. 勾選 Enable Source Server Support

image

Visual Studio 2010 > Debug > Options and Settings > Debugging > Symbols

image

在這邊我只想要設定 System.Web.Mvc.DLL 的話,就選擇 Only specifieds modules > Specify Modules

image

按下確定之後就會從 Server 上面下載可用的 .pdf 。也會出現要你同意 License 的部分

image

之後一樣設定中斷點在想進入的地方,如果要看 Htm.TextBoxFor 到底幫你做了些什麼

image

一樣 F11 就可以進入逐步偵錯了

image

不過比起加入原始碼參考還是有些不足的地方,尋找至定義在這裡就沒辦法使用囉,如果要看更詳細的資料還是下載原始碼吧,或是另外開起來當作手動參考,就可以不用修改 Web.Config 那些設定,也可以直接 Release 發佈。

image


Stepping into ASP.NET MVC source code with Visual Studio debugger
Setting up Visual Studio 2010 to step into Microsoft .NET Source

利用 ThemeRoller 設計版面

themroller-mobile-logo
我是到前一陣子才發現 ThemeRoller 這個好用到一個不行的工具,調整版面對我來說實在是一個很痛苦的事情,又是哪個東西改個顏色、改個寬度、改個配色,東修西改一半的開發時間都花在這上面了。如果有要用 jQuery Mobile 來開發的人,強烈建議一開始就使用 ThemeRoller
image
一開始進入頁面就可以看到這個編輯器,預設會先有三個空白的 theme 可以做設定,而左側功能列就是有相關的選項可以做選擇,可以看到基本模組都是可以做調整的。在設定完之後只要按下左上方的 Download Theme 就可以做匯出的動作,include 的方法也非常簡單,匯出的時候就有說明了。
image
在右上方輸入 Theme 的自訂名稱,並且在下載之後加入專案當中,也要在頁面中引用,要在 jQuery Mobile 的 js 跟 css 之前。不過有用過  jQuery Mobile 原本就有預設五種 Theme 可以使用,在官網也有介紹,當一加入 ThemeRoller 產生出來的 css 檔案之後,原本的 Theme 效果就會被覆蓋掉了,如果說想要保留原本的預設樣式要怎麼做呢?幸好還有另外一個 import 功能。
image
選擇右上角的 Import Default Theme 就可以載入 jQuery Mobile 預設的範本,不用害怕會覆蓋掉現在已經在運作的 jQuery Mobile ,或是說如果想要預設的樣式還要自己設定,如果已經有 ThemeRoller 匯出的檔案也只要複製 css 直接匯入就可以看到之前修改的結果。
image
image
如果開啟 Inspector 可以直接點選畫面上想要調整的地方,左側功能列表就自動開啟相對應的設定
官方還有另一個 valencia 樣式可以選擇。
image