日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

ASP.NET MVC: 构建不带 Web 窗体的 Web 应用程序(转载)

發布時間:2024/7/19 asp.net 52 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ASP.NET MVC: 构建不带 Web 窗体的 Web 应用程序(转载) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

我 從事專業開發迄今為止已有 15 年,在此之前,我利用業余時間從事開發至少也有 10 年了。與我這一代的大多數人一樣,我是從 8 位計算機起步,然后轉用 PC 平臺的。隨著計算機的復雜性日益增加,我編寫的應用程序涵蓋了從小型游戲到個人數據管理再到控制外部硬件的各項功能。 不過,在我職業生涯的前半段,我編寫的所有軟件都有一個共同點:即,都是運行在用戶桌面 上的本地應用程序。我最早是在 90 年代初期聽說萬維網這件新生事物。那時我發現,通過構建 Web 應用程序,可以讓我輸入我的考勤卡信息而不必再費時費力從工作場所趕回辦公室。 一言以蔽之,我感覺很是困惑。我當時滿腦子是面向桌面的理念,很難接納這種無狀態的 Web。要添加很多讓人頭疼的調試、我沒有 UNIX 服務器的超級用戶訪問權限,再加上這個奇怪的角括號,這些因素使年輕時的我止步不前,又重返桌面開發渡過了幾年時光。 我遠離了 Web 開發領域,雖然這領域顯然很重要,但我并沒有真正理解其編程模型。然后,Microsoft? .NET Framework 和 ASP.NET 發行了。盡管它與桌面應用程序編程有許多相似之處,但終于有了可以讓我從事 Web 應用程序編程的框架。我可以構建窗口(頁面),將控件與事件掛鉤,而設計器使我不必處理那些討厭的角括號。最妙的是,ASP.NET 會通過查看狀態自動為我處理 Web 的無狀態性質!我又重新找回了程序員的快樂 ... 至少在一段時間內是如此。 隨著經驗的增加,我的設計內容也隨之豐富。我早已掌握了幾種最佳實踐,并將其應用到桌面 應用程序編程。其中的兩種就是:
  • 分離關注點:不要將 UI 邏輯與基礎行為混合在一起。
  • 自動單元測試:編寫自動測試以驗證您的代碼是否按預期執行。
這些是適用于任何技術的基本原則。分離關注點是一項可幫助您處理復雜問題的基本原則。在 同一個對象內混合多種責任(如計算剩余的工時、設置數據格式并繪圖)會給維護帶來很大的負擔。而自動測試對于獲得生產質量的代碼同時仍保持條理性至關重 要,尤其是當您更新現有項目時更是如此。 ASP.NET Web 窗體使入門變得非常簡單,但另一方面,要將我的設計理念應用到 Web 應用程序卻并非易事。Web 窗體堅持以 UI 為側重點;其基本單位為頁面。首先設計 UI 并拖曳控件。只需將應用程序邏輯融入頁面的事件處理程序(與為 Windows? 應用程序啟用的 Visual Basic? 非常相似)就萬事大吉,這一點非常吸引人。 但進一步的頁面單元測試常常有很大困難。您必須先啟動所有 ASP.NET,然后才能在“頁面”對象的生命周期內運行該對象。盡管可以通過發送 HTTP請求到服務器或自動化瀏覽器來測試 Web 應用程序,但這類測試非常脆弱(更換一個控制 ID 測試就會中斷)、難以設置(您必須以完全相同的方式在每位開發人員的計算機上設置該服務器)并且運行緩慢。 當我開始構建更復雜的 Web 應用程序時,Web 窗體提供的抽象概念(如控件、視圖狀態和頁面生命周期)就開始添亂而不是幫忙了。我需要花越來越多的時間來配置數據綁定(并編寫大量的事件處理程序對其進 行正確配置)。我不得不想辦法縮減視圖狀態的大小以便更快加載我的頁面。Web 窗體要求每個 URL 均存在物理文件,這對于動態站點(例如 wiki)非常困難。而成功編寫一個自定義的 WebControl 是一個非常復雜的過程,需要全面了解頁面生命周期和 Visual Studio? 設計器。 自從在 Microsoft 工作開始,我就一直與其他人分享關于各種 .NET 難題的體驗并希望可以解決一些難題。最近,作為開發人員參加有關模式與實踐的 Web 客戶端軟件工廠項目 (codeplex.com/websf) 時,我遇到了一個這樣的機會。特別是,模式與實踐交付的內容之一就是自動單元測試。在 Web 客戶端軟件工廠中,我們建議使用 Model View Presenter (MVP) 模式構建可測試的 Web 窗體。 簡而言之,MVP 并非將您的邏輯放入頁面中,而是讓您構建自己的頁面,頁面 (View) 只需調用單獨的對象,即 Presenter。Presenter 對象隨即執行響應視圖上活動必需的任何邏輯,通常通過使用其它對象 (Model) 訪問數據庫、執行業務邏輯等。一旦這些步驟完成后,Presenter 會更新視圖。這種方法提供了可測試性,因為表示器從 ASP.NET 管道中隔離出來;它與視圖通過界面進行通信并可脫離頁面獨立進行測試。 MVP 的這種功能實現有點笨;您需要單獨的視圖界面,并且您必須在源代碼文件中編寫許多事件轉發函數。但如果您想要在 Web 窗體應用程序中得到可測試的 UI,這差不多是最佳途徑。任何改進均需要在基礎平臺中做出更改。
模型視圖控制器模式 幸運的是,ASP.NET 團隊聽取了象我這樣的開發人員的意見,并且已經著手開發一種新的 Web 應用程序框架,該框架與您所熟知并喜愛的 Web 窗體處于同一層級,但采用一組完全不同的設計目標:
  • 使用 HTTP 和 HTML—不隱藏。
  • 可測試性貫穿整個框架之內。
  • 幾乎在每個點均可擴展。
  • 對輸出進行總體控制。
由于此新框架基于模型視圖控制器 (MVC) 模式,因此其名稱為 ASP.NET MVC。MVC 模式最初在 70 年代發明,是 Smalltalk 技術的一部分。正如我將在本文中所展示的,它實際上非常適合 Web 的性質。MVC 將您的 UI 分為三種不同的對象:用于接收和處理輸入的控制器;包含您域邏輯的模型;以及用于生成輸出的視圖。在 Web 環境中,輸入為 HTTP 請求,而請求流程與圖 1 類似。 Figure 1?MVC 模式請求流程?(單 擊該圖像獲得較大視圖) 這實際上與 Web 窗體中的過程完全不同。在 Web 窗體模型中,輸入進入頁面(視圖),然后視圖負責處理輸入并生成輸出。而 MVC 中這些責任是分開的。 因此,您可能立即會產生以下一種想法:“嘿,這太好了。我應該如何使用它?”或“為什么 我要編寫這些對象,以前只需要編寫一個對象?”這兩個問題都問得很好,最好通過示例來進行解釋。因此,我將使用 MVC Framework 編寫一個小型 Web 應用程序以說明其優點。
創建控制器 要繼續進行,您將需要安裝 Visual Studio 2008 并獲得 MVC Framework 的副本。在撰寫本文時,ASP.NET 擴展的 2007 年 12 月社區技術預覽 (CTP) 中已提供了這些內容 (asp.net/downloads/3.5-extensions)。您可能想要獲取擴展 CTP 和 MVC 工具包,其中包括一些非常有用的幫助程序對象。一旦下載并安裝 CTP 后,您將在“新建項目”對話框中獲得名為“ASP.NET MVC Web 應用程序”的新項目類型。 選擇“MVC Web 應用程序”項目后,會為您提供一個與常用網站或應用程序稍有不同的解決方案。該解決方案模板會創建一個帶有一些新目錄的 Web 應用程序(如圖 2 中所示)。特別是 Controllers 目錄包含各種控制器類,而 Views 目錄(及其所有子目錄)包含了各種視圖。 Figure 2?MVC 項目結構? 我將會編寫一個非常簡單的控制器,返回 URL 中傳遞的名稱。右鍵單擊 Controllers 文件夾并選擇“添加項目”以顯示常用的“添加項目”對話框以及一些新增加的內容,包括 MVC 控制器類和幾個 MVC 視圖組件。在此例中,我將添加一個非常富有想象力、名為 HelloController 的類: 復制代碼 using System;
using System.Web;
using System.Web.Mvc;
namespace HelloFromMVC.Controllers
{
public class HelloController : Controller
{
[ControllerAction]
public void Index()
{
...
}
}
}
控制器類比頁面簡單得多。實際上,唯一真正必需做的就是從 System.Web.Mvc.Controller 中衍生并將 [ControllerAction] 屬性置于您的操作方法中。操作是調用以響應特定 URL 請求的一種方法。操作負責執行所需的一切處理,然后呈現一個視圖。我將通過編寫一個將名稱傳遞到視圖的簡單操作著手,如下所示: 復制代碼 [ControllerAction]
public void HiThere(string id)
{
ViewData["Name"] = id;
RenderView("HiThere");
}
操作方法會通過 ID 參數從 URL 接收該名稱(稍后會介紹方法),將其存儲在 ViewData 集合中,然后呈現名為 HiThere 的視圖。 在討論如何調用此方法,或該視圖的顯示內容之前,我希望說一說可測試性。還記得我之前關 于測試 Web 窗體頁面類有多難的評論嗎?控制器的測試簡單得多。實際上,控制器可以直接實例化,而調用操作方法無需任何附加的基礎結構。您不需要 HTTP 上下文,也不需要服務器,只要測試工具即可。作為示例,我在圖 3 中為此類包括了 Visual Studio Team System (VSTS) 測試單元。 ?Figure?3?Controller Unit Test 復制代碼 namespace HelloFromMVC.Tests
{
[TestClass]
public class HelloControllerFixture
{
[TestMethod]
public void HiThereShouldRenderCorrectView()
{
TestableHelloController controller = new
TestableHelloController();
controller.HiThere("Chris");
Assert.AreEqual("Chris", controller.Name);
Assert.AreEqual("HiThere", controller.ViewName);
}
}
class TestableHelloController : HelloController
{
public string Name;
public string ViewName;
protected override void RenderView(
string viewName, string master, object data)
{
this.ViewName = viewName;
this.Name = (string)ViewData["Name"];
}
}
}
下面將進行幾項操作。實際的測試相當簡單:實例化該控制器,使用預期的數據調用該方法, 然后檢查呈現的視圖是否正確。我通過創建測試專用的子類覆蓋 RenderView 方法進行檢查。這可以縮短實際創建 HTML 的時間。我只關心是否將正確的數據發送到視圖以及是否呈現了正確的視圖。我不關心此測試視圖本身的底層詳細信息。
創建視圖 當然,最終我必須生成一些 HTML,因此,讓我們創建該 HiThere 視圖。要進行此操作,首先,我將在解決方案中的 Views 文件夾下創建名為 Hello 的新文件夾。默認情況下,控制器將在 Views\<控制器前綴> 文件夾(控制器前綴為控制器類的名稱去掉 "Controller" 字樣)中查找視圖。因此,對于 HelloController 呈現的視圖,它會在 Views\Hello 中查找。解決方案的查找結果如圖 4 所示。 Figure 4?將視圖添加到項目中?(單 擊該圖像獲得較大視圖) 視圖的 HTML 如下所示: 復制代碼 <html >
<head runat="server">
<title>Hi There!</title>
</head>
<body>
<div>
<h1>Hello, <%= ViewData["Name"] %></h1>
</div>
</body>
</html>
應注意以下幾件事。沒有 runat="server" 標記。沒有 form 標記。沒有控件聲明。實際上,這看起來更象傳統的 ASP 而不是 ASP.NET。請注意,MVC 視圖僅負責生成輸出,因此其不需要任何 Web 窗體頁面所需的事件處理或復雜控件。 MVC Framework 借用了 .aspx 文件格式作為一種有用的文本模板語言。如果需要,甚至可以使用源代碼,但默認情況下,源代碼文件如下所示: 復制代碼 using System;
using System.Web;
using System.Web.Mvc;
namespace HelloFromMVC.Views.Hello
{
public partial class HiThere : ViewPage
{
}
}
沒有頁面初始化或加載方法,沒有事件處理程序,除了基類聲明以外沒有任何內容,基類聲明 為 ViewPage 而不是 Page。這就是 MVC 視圖所需的一切。運行該應用程序,導航至 http://localhost:<端口>/Hello/HiThere/Chris,您將看到如圖 5 所示的內容。 Figure 5?成功的 MVC 視圖?(單 擊該圖像獲得較大視圖) 如果您看到的并非如圖 5 所示,而是難以理解的意外情況,請不要驚慌。如果您將 HiThere.aspx 文件設置為 Visual Studio 中的活動文檔,則當按 F5 后,Visual Studio 將嘗試直接訪問 .aspx 文件。由于 MVC 視圖要求控制器在顯示前運行,因此嘗試直接導航至該頁面將不起作用。只需將該 URL 編輯為與圖 5 中所示的內容相匹配,即可正常工作。 MVC Framework 如何知道調用我的操作方法?該 URL 甚至沒有文件擴展名。答案是 URL 路由。如果您仔細查看 global.asax.cs 文件,則會看到如圖 6 所示的代碼段。全局 RouteTable 會存儲 Route 對象的集合。每個 Route 說明一個 URL 窗體以及對其進行何種操作。默認情況下,會向該表中添加兩個路由。第一個是該方法的內容。它說明每個 URL 在服務器名后均由三部分組成,第一部分應為控制器名,第二部分為操作名稱,而第三部分為 ID 參數。 ?Figure?6?Route Table 復制代碼 public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
// Change Url= to Url="[controller].mvc/[action]/[id]"
// to enable automatic support on IIS6
RouteTable.Routes.Add(new Route
{
Url = "[controller]/[action]/[id]",
Defaults = new { action = "Index", id = (string)null },
RouteHandler = typeof(MvcRouteHandler)
});
RouteTable.Routes.Add(new Route
{
Url = "Default.aspx",
Defaults = new {
controller = "Home",
action = "Index",
id = (string)null },
RouteHandler = typeof(MvcRouteHandler)
});
}
}
復制代碼 Url = "[controller]/[action]/[id]"
此默認路由是能讓我的 HiThere 方法得以調用的路由。請記住此 URL:http://localhost/Hello/HiThere/Chris?此路由將 Hello 與控制器、HiThere 與操作以及 Chris 與 ID 一一對應。MVC Framework 隨即創建 HelloController 實例,調用 HiThere 方法,然后將 Chris 作為 ID 參數的值傳遞。 此默認路由為您提供了許多功能,但您也可以添加自己的路由。例如,我想要一個真正友好的 站點,好友們只需輸入他們的姓名即可獲得個性化的問候。如果我在路由表的頂部添加以下路由 復制代碼 RouteTable.Routes.Add(new Route
{
Url = "[id]",
Defaults = new {
controller = "Hello",
action = "HiThere" },
RouteHandler = typeof(MvcRouteHandler)
});
隨后,我只需訪問 ,我的操作仍處于調用狀態,而我將會看到熟悉的友好問候。 系統如何知道調用哪個控制器和操作?答案是 Defaults 參數。它利用新的 C# 3.0 匿名類型語法來創建一個偽詞典。Route 上的 Defaults 對象可包含任意附加的信息,對于 MVC,它還可以包含一些眾所周知的條目:即控制器和操作。如果 URL 中沒有指定控制器或操作,則其將使用 Defaults 中的名稱。這就是為什么即使我在 URL 中忽略它們,但仍可以將我的請求映射到正確的控制器和操作。 還有一件事需要注意:還記得我說過“添加到表格的頂部”嗎?如果您將其置于底部,將會出 現錯誤。路由根據先到先得的原則進行工作。當處理 URL 時,路由系統會自上至下瀏覽表格,并且使用第一個匹配的路由。在本例中,默認路由 "[controller]/[action]/[id]" 匹配,因為它們是操作和 ID 的默認值。這樣,它會繼續查找 ChrisController,但我沒有控制器,因此會出現錯誤。
稍大的示例 現在,我已經說明了 MVC Framework 的基礎知識,將為您展示一個更大的示例,實現比僅顯示字符串更多的功能。wiki 是一種可以在瀏覽器中進行編輯的網站。可以輕松地添加或編輯頁面。我使用 MVC Framework 編寫了一個小型的示例 wiki。“編輯此頁面”屏幕如圖 7 所示。 Figure 7?編輯主頁?(單擊該 圖像獲得較大視圖) 您可以檢查本文的代碼下載以查看如何實現底層 wiki 邏輯。現在我想重點說明 MVC Framework 如何使 Web 上的 wiki 獲取變得簡單。讓我們先設計 URL 結構。我想要以下各項:
  • /[pagename] 顯示該名稱的頁面。
  • /[pagename]?version=n 顯示頁面的請求版本,其中 0 = 當前版本,1 = 以前的版本,以此類推。
  • /Edit/[pagename] 打開該頁的編輯屏幕。
  • /CreateNewVersion/[pagename] 是為提交編輯而傳入的 URL。
讓我們從 wiki 頁面的基本顯示開始。我為它創建了一個名為 WikiPageController 的新類。接下來,我會添加一個名為 ShowPage 的操作。啟動的 WikiPageController 如圖 8 所示。ShowPage 方法相當簡單。WikiSpace 和 WikiPage 類分別表示一組 wiki 頁面和特定的頁面(及其修訂)。此操作只需加載模型并調用 RenderView。但此處的 "new WikiPageViewData" 行是什么意思? ?Figure?8?WikiPageController Implementation of ShowPage 復制代碼 public class WikiPageController : Controller
{
ISpaceRepository repository;
public ISpaceRepository Repository
{
get {
if (repository == null)
{
repository = new FileBasedSpaceRepository(
Request.MapPath("~/WikiPages"));
}
return repository;
}
set { repository = value; }
}
[ControllerAction]
public void ShowPage(string pageName, int? version)
{
WikiSpace space = new WikiSpace(Repository);
WikiPage page = space.GetPage(pageName);
RenderView("showpage",
new WikiPageViewData
{
Name = pageName,
Page = page,
Version = version ?? 0
});
}
}
我前面的示例說明了一種將數據從控制器傳遞到視圖的方法:即 ViewData 詞典。詞典非常方便,但也很危險。它們幾乎包含一切內容,您不能獲取內容的任何 IntelliSense?,并且由于 ViewData 詞典屬于 Dictionary<string, object> 類型,它將消耗內容,您必須計算所有一切。 當您了解在視圖中將需要什么數據后,就可以傳遞強類型化的 ViewData 對象。在我的示例中,我創建了一個簡單的對象 (WikiPageViewData),如圖 9 中所示。此對象將 wiki 頁面信息帶到視圖,同時還攜帶了一些實用工具方法,執行獲取 wiki 標記的 HTML 版本這類任務。 ?Figure?9?WikiPageViewData Object 復制代碼 public class WikiPageViewData {
public string Name { get; set; }
public WikiPage Page { get; set; }
public int Version { get; set; }
public WikiPageViewData() {
Version = 0;
}
public string NewVersionUrl {
get {
return string.Format("/CreateNewVersion/{0}", Name);
}
}
public string Body {
get { return Page.Versions[Version].Body; }
}
public string HtmlBody {
get { return Page.Versions[Version].BodyAsHtml(); }
}
public string Creator {
get { return Page.Versions[Version].Creator; }
}
public string Tags {
get { return string.Join(",", Page.Versions[Version].Tags); }
}
}
現在,我已經定義了視圖數據,那么,我如何使用它呢?在 ShowPage.aspx.cs 中,您將看到以下內容: 復制代碼 namespace MiniWiki.Views.WikiPage {
public partial class ShowPage : ViewPage<WikiPageViewData>
{
}
}
請注意,我將基類類型定義為 ViewPage<WikiPageViewData>。這意味著頁面的 ViewData 屬性為 WikiPageViewData 類型,而不是象以前示例中的“Dictionary”。 .aspx 文件中的實際標記非常簡單: 復制代碼 <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
AutoEventWireup="true" CodeBehind="ShowPage.aspx.cs"
Inherits="MiniWiki.Views.WikiPage.ShowPage" %>
<asp:Content
ID="Content1"
ContentPlaceHolderID="MainContentPlaceHolder"
runat="server">
<h1><%= ViewData.Name %></h1>
<div id="content" class="wikiContent">
<%= ViewData.HtmlBody %>
</div>
</asp:Content>
請注意,當引用 ViewData 時,我沒有使用索引操作符 []。由于我現在有強類型化的 ViewData,我可以直接訪問該屬性。不需要進行任何計算,而 Visual Studio 會提供 IntelliSense。 目光敏銳的讀者將會注意到此文件中的 <asp:Content> 標記。沒錯,“母版頁”確實可以與 MVC 視圖配合使用。并且“母版頁”還可以成為視圖。讓我們看看“母版頁”的源代碼: 復制代碼 namespace MiniWiki.Views.Layouts
{
public partial class Site :
System.Web.Mvc.ViewMasterPage<WikiPageViewData>
{
}
}
相關標記如圖 10 中所示。現在,“母版頁”將獲得與視圖完全相同的 ViewData 對象。我已經將“母版頁”的基類聲明為 ViewMasterPage<WikiPageViewData>,因此,我擁有了正確類型的 ViewData。我會在那里設置各種 DIV 標記以對頁面進行布局,填寫版本列表,然后以常用內容占位符收尾。 ?Figure?10?Site.Master 復制代碼 <%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="MiniWiki.Views.Layouts.Site" %> <%@ Import Namespace="MiniWiki.Controllers" %> <%@ Import Namespace="MiniWiki.DomainModel" %> <%@ Import Namespace="System.Web.Mvc" %> <html > <head runat="server"> <title><%= ViewData.Name %></title> <link href="http://http://www.cnblogs.com/Content/Site.css" rel="stylesheet" type="text/css" /> </head> <body> <div id="inner"> <div id="top"> <div id="header"> <h1><%= ViewData.Name %></h1> </div> <div id="menu"> <ul> <li><a href="http://Home">Home</a></li> <li> <%= Html.ActionLink("Edit this page", new { controller = "WikiPage", action = "EditPage", pageName = ViewData.Name })%> </ul> </div> </div> <div id="main"> <div id="revisions"> Revision history: <ul> <% int i = 0; foreach (WikiPageVersion version in ViewData.Page.Versions) { %> <li> <a href="http://<%= ViewData.Name %>?version=<%= i %>"> <%= version.CreatedOn %> by <%= version.Creator %> </a> </li> <% ++i; } %> </ul> </div> <div id="maincontent"> <asp:ContentPlaceHolder ID="MainContentPlaceHolder" runat="server"> </asp:ContentPlaceHolder> </div> </div> </div> </body> </html> 另一件需要注意的事是對 Html.ActionLink 的調用。以下是呈現幫助程序的一個示例。各種視圖類均具有兩種屬性,Html 和 Url。每種均有輸出 HTML 代碼塊的有用方法。在本例中,Html.ActionLink 獲取一個對象(此處為匿名類型)并通過路由系統將其返回。這將會生成一個 URL,該 URL 將路由至我指定的控制器和操作。這樣一來,無論我如何更改路由,“編輯此頁面”鏈接將始終指向正確的位置。 您可能還注意到,我還不得不依靠手動構建鏈接(到先前頁面版本的鏈接)。遺憾的是,當前 的路由系統在涉及查詢字符串時生成 URL 的功能不是十分完善。這應會在框架的后續版本中得到修復。
創建表單和回發 現在,讓我們看看控制器上的 EditPage 操作: 復制代碼 [ControllerAction]
public void EditPage(string pageName)
{
WikiSpace space = new WikiSpace(Repository);
WikiPage page = space.GetPage(pageName);
RenderView("editpage",
new WikiPageViewData {
Name = pageName,
Page = page });
}
同樣,該操作所做的不多—它只是呈現指定頁面的視圖。視圖中的內容變得更加有趣,如圖 11 中所示。此文件構建了一個 HTML 表單,但沒有出現 Runat="server"。Url.Action helper 用于生成表單回發的 URL。其中還使用了幾種不同的 HTML 幫助程序(如 TextBox、TextArea 和 SubmitButton)。它們會出色完成您的預期目標:為各種輸入字段生成 HTML。 ?Figure?11?EditPage.aspx 復制代碼 <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="EditPage.aspx.cs" Inherits="MiniWiki.Views.WikiPage.EditPage" %> <%@ Import Namespace="System.Web.Mvc" %> <%@ Import Namespace="MiniWiki.Controllers" %> <asp:Content ID="Content1" ContentPlaceHolderID="MainContentPlaceHolder" runat="server"> <form action="<%= Url.Action( new { controller = "WikiPage", action = "NewVersion", pageName = ViewData.Name })%>" method=post> <% if (ViewContext.TempData.ContainsKey("errors")) { %> <div id="errorlist"> <ul> <% foreach (string error in (string[])ViewContext.TempData["errors"]) { %> <li><%= error%></li> <% } %> </ul> </div> <% } %> Your name: <%= Html.TextBox("Creator", ViewContext.TempData.ContainsKey("creator") ? (string)ViewContext.TempData["creator"] : ViewData.Creator)%> <br /> Please enter your updates here:<br /> <%= Html.TextArea("Body", ViewContext.TempData.ContainsKey("body") ? (string)ViewContext.TempData["body"] : ViewData.Body, 30, 65)%> <br /> Tags: <%= Html.TextBox( "Tags", ViewContext.TempData.ContainsKey("tags") ? (string)ViewContext.TempData["tags"] : ViewData.Tags)%> <br /> <%= Html.SubmitButton("SubmitAction", "OK")%> <%= Html.SubmitButton("SubmitAction", "Cancel")%> </form> </asp:Content> 處理 Web 編程最頭疼的事情之一就是表單中的錯誤。更確切地說,您想要顯示錯誤信息,但同時想要保留原來輸入的數據。我們都有過那種經歷,在填寫一張有 35 個字段的表單時出現一個錯誤,程序卻只是提供一堆錯誤信息和一張新的空白表單。MVC Framework 使用 TempData 存儲以前輸入信息,以便可以重新填入表單。這是 ViewState 實際上在 Web 窗體中變得非常簡單的原因,因為保存控件的內容幾乎是自動的。 我想在 MVC 中如法炮制,因此引入了 TempData。TempData 是一種詞典,與非類型化的 ViewData 很相似。不過,TempData 的內容僅針對單一請求存在,隨后就會被刪除。要了解如何使用此方法,請參閱圖 12,NewVersion 操作。 ?Figure?12?NewVersion Action 復制代碼 [ControllerAction] public void NewVersion(string pageName) { NewVersionPostData postData = new NewVersionPostData(); postData.UpdateFrom(Request.Form); if (postData.SubmitAction == "OK") { if (postData.Errors.Length == 0) { WikiSpace space = new WikiSpace(Repository); WikiPage page = space.GetPage(pageName); WikiPageVersion newVersion = new WikiPageVersion( postData.Body, postData.Creator, postData.TagList); page.Add(newVersion); } else { TempData["creator"] = postData.Creator; TempData["body"] = postData.Body; TempData["tags"] = postData.Tags; TempData["errors"] = postData.Errors; RedirectToAction(new { controller = "WikiPage", action = "EditPage", pageName = pageName }); return; } } RedirectToAction(new { controller = "WikiPage", action = "ShowPage", pageName = pageName }); } 首先,它創建一個 NewVersionPostData 對象。這是另一個幫助程序對象,具有存儲記入的內容和進行某些驗證的屬性和方法。為加載 postData 對象,我將使用 MVC 工具包的幫助程序。UpdateFrom 實際上是工具包提供的擴展方法,它使用反射將表單字段的名稱與我的對象中屬性的名稱相對映。最終結果是,所有字段值均載入到我的 postData 對象中。不過,UpdateFrom 使用起來確實有缺點,由于它直接從 HttpRequest 獲取表單數據,使單元測試變得更為困難。 NewVersion 檢查的第一項是 SubmitAction。如果用戶單擊“確定”按鈕并確實想要發布編輯的頁面,則此項檢查將通過。如果此處有任何其它值,操作會重定向回 ShowPage,只是重新顯示原來的頁面。 如果用戶確實單擊了“確定”,則檢查 postData.Errors 屬性。這將在記入內容上運行一些簡單的驗證。如果沒有任何錯誤,我會將新版本的頁面重新寫入 wiki。不過,如果出現錯誤,情況會變得饒有趣味。 如果出現錯誤,我會設置 TempData 詞典的各個字段,以便其包含 PostData 的內容。然后,我會重定向回“編輯”頁面。現在,由于已設置 TempData,頁面將重新顯示以用戶上次記入的值初始化的表單。 處理記入、驗證和 TempData 的這個過程現在變得有些煩瑣,并且需要多做一些手動工作。將來發行的版本應包括至少會將一些 TempData 檢查自動化的幫助程序方法。關于 TempData 的最后一個注意事項是:TempData 的內容存儲在用戶的服務器端會話中。如果您關閉會話,TempData 將無法正常工作。
創建控制器 現在,wiki 的基礎已在發揮功效,但繼續進行之前,我想要明確實現中的以下幾個要點。例如,Repository 屬性用于分離 wiki 的邏輯與物理存儲。您可以提供在文件系統(正如我所做的那樣)、數據庫或您想要的任何位置中存儲內容的存儲庫。遺憾的是,我需要解決兩個問題。 首先,我的控制器類與具體的 FileBasedSpaceRepository 類緊密地連在一起。我需要一個默認值,以便在屬性沒有設置時,也能合理使用。更糟的是,磁盤上文件的路徑在這里也是硬編碼的。最起碼,它應取自配置。 其次,我的對象必須依賴存儲庫,否則無法運行。對于良好的設計,存儲庫實際應為構造函數 參數,而不是屬性。但我無法將其添加到構造函數中,因為 MVC Framework 要求控制器上的構造函數不能有參數。 幸運的是,我可以通過一個擴展性掛接擺脫此限制:即控制器工廠。控制器工廠的功能正如其 名稱所指:它創建 Controller 實例。您只需要創建一個類實現 IControllerFactory 接口并向 MVC 系統注冊即可。您可以為所有控制器或僅為指定的類型注冊控制器工廠。圖 13 所示為 WikiPageController 的控制器工廠,其現在將存儲庫作為構造函數參數傳遞。在這種情況下,實現非常煩瑣,但它可以創建能使用更強大工具(特別是依賴關系注入容器)的控制器。 無論如何,現在我擁有了將控制器依賴關系分離到對象中(易于管理和維護)的所有詳細信息。 ?Figure?13?Controller Factory 復制代碼 public class WikiPageControllerFactory : IControllerFactory { public IController CreateController(RequestContext context, Type controllerType) { return new WikiPageController( GetConfiguredRepository(context.HttpContext.Request)); } private ISpaceRepository GetConfiguredRepository(IHttpRequest request) { return new FileBasedSpaceRepository(request.MapPath("~/WikiPages")); } } 此工作的最后一步是向框架注冊工廠。通過 ControllerBuilder 類可進行此操作,方法是將以下行添加到 Application_Start 方法中的 Global.asax.cs(路由前后均可): 復制代碼 ControllerBuilder.Current.SetControllerFactory(
typeof(WikiPageController), typeof(WiliPageControllerFactory));
這將注冊 WikiPageController 的工廠。如果此項目中有其他控制器,它們不會使用此工廠,因為此工廠僅針對 WikiPageController 類型進行了注冊。如果您想要將工廠設置為供所有控制器使用,還可以調用 SetDefaultControllerFactory。
其他擴展點 控制器工廠只是框架擴展性的起點。本文中無法詳述所有的細節,因此我將僅僅說明要點。首 先,如果您想要輸出的內容不是 HTML,或想要使用其他模板引擎而不是 Web 窗體,可將控制器的 ViewFactory 設為其他項。您可以實現 IviewFactory 界面,然后即可完全控制如何生成輸出。這對于生成 RSS、XML 或圖形非常有用。 正如您所見到的,路由系統非常靈活。但路由系統中沒有任何內容是 MVC 專用的。每個路由均有一個 RouteHandler 屬性;目前為止,我始終將其設為 MvcRouteHandler。但可以實現 IRouteHandler 界面并將路由系統與其他 Web 技術掛接。將來推出的框架將附帶 WebFormsRouteHandler,并且其他技術也會在將來利用通用路由系統的優勢。 控制器并非必須從 System.Web.Mvc.Controller 衍生。控制器需要做的僅僅是實現 IController 界面,該界面只有稱為 Execute 的一種方法。您可以從中進行任何操作。另一方面,如果您想將 Controller 基類的幾種行為組合在一起,您可以覆蓋 Controller 的許多虛擬函數:
  • OnPreAction、OnPostAction 和 OnError 可讓您將每個已執行操作上的預處理和后處理連接起來。OnError 為您提供在控制器內處理錯誤的機制。
  • 當 URL 路由到控制器但控制器沒有實現路由中請求的操作時,會調用 HandleUnknownAction。默認情況下,此方法會拋出一個異常,但您可以用所需的操作覆蓋默認值。
  • InvokeAction 是一種方法,它負責解決調用何種操作方法并進行調用。如果您想要自定義過程(例如,除去 [ControllerAction] 屬性的要求),應使用該方法。
還有其他幾種針對 Controller 的虛擬方法,但這些方法主要是測試掛接而不是作為擴展點。例如,RedirectToAction 是虛擬的,因此您可以創建實際并不進行重定向的衍生類。這樣,您不需要完全運行 Web 服務器就能測試重定向操作。
要告別 Web 窗體嗎? 現在您可能在想:“Web 窗體會面臨怎樣的命運?MVC 會取代它嗎?”答案是否定的!Web 窗體是一種普及技術,Microsoft 將繼續支持并改進它。它在許多應用程序中發揮著重要的作用;例如,可使用 Web 窗體創建典型的 Intranet 數據庫報表應用程序,所花的時間比使用 MVC 編寫短得多。此外,Web 窗體支持大量的控件,許多控件均具備非常先進的功能,可以大大提高效率。 那么,什么時候應該選擇 MVC 呢?這主要取決于您的要求和喜好。您是否正在為獲得想要的 URL 格式而煩惱?您是否想要對 UI 進行單元測試?以上情況均需要依靠 MVC。反之,如果您要顯示許多數據,提供可編輯的網格和優良的樹形視圖控件?那么,您暫時最好還是使用 Web 窗體。 今后,MVC Framework 很可能在 UI 控制部分有所改進,但在便利性上,它可能始終不及 Web 窗體,因為后者具備大量拖曳功能。同時,ASP.NET MVC Framework 為 Web 開發人員提供了一種在 Microsoft .NET Framework 中構建 Web 應用程序的新方法。Framework 針對可測試性設計、推倡使用 HTTP 并且幾乎在每個點均可擴展。對于那些想要完全控制其 Web 應用程序的開發人員來說,這是一個對 Web 窗體的誘人補充。
Chris Tavares 是 Microsoft 模式和實施方案小組的一名開發人員,他致力于幫助開發社區了解在 Microsoft 平臺上構建系統的最佳實踐。他還是 ASP.NET MVC 小組的虛擬成員,幫助您設計新的框架。可以通過 cct@tavaresstudios.com 與 Chris 取得聯系。 我 從事專業開發迄今為止已有 15 年,在此之前,我利用業余時間從事開發至少也有 10 年了。與我這一代的大多數人一樣,我是從 8 位計算機起步,然后轉用 PC 平臺的。隨著計算機的復雜性日益增加,我編寫的應用程序涵蓋了從小型游戲到個人數據管理再到控制外部硬件的各項功能。 不過,在我職業生涯的前半段,我編寫的所有軟件都有一個共同點:即,都是運行在用戶桌面 上的本地應用程序。我最早是在 90 年代初期聽說萬維網這件新生事物。那時我發現,通過構建 Web 應用程序,可以讓我輸入我的考勤卡信息而不必再費時費力從工作場所趕回辦公室。 一言以蔽之,我感覺很是困惑。我當時滿腦子是面向桌面的理念,很難接納這種無狀態的 Web。要添加很多讓人頭疼的調試、我沒有 UNIX 服務器的超級用戶訪問權限,再加上這個奇怪的角括號,這些因素使年輕時的我止步不前,又重返桌面開發渡過了幾年時光。 我遠離了 Web 開發領域,雖然這領域顯然很重要,但我并沒有真正理解其編程模型。然后,Microsoft? .NET Framework 和 ASP.NET 發行了。盡管它與桌面應用程序編程有許多相似之處,但終于有了可以讓我從事 Web 應用程序編程的框架。我可以構建窗口(頁面),將控件與事件掛鉤,而設計器使我不必處理那些討厭的角括號。最妙的是,ASP.NET 會通過查看狀態自動為我處理 Web 的無狀態性質!我又重新找回了程序員的快樂 ... 至少在一段時間內是如此。 隨著經驗的增加,我的設計內容也隨之豐富。我早已掌握了幾種最佳實踐,并將其應用到桌面 應用程序編程。其中的兩種就是:
  • 分離關注點:不要將 UI 邏輯與基礎行為混合在一起。
  • 自動單元測試:編寫自動測試以驗證您的代碼是否按預期執行。
這些是適用于任何技術的基本原則。分離關注點是一項可幫助您處理復雜問題的基本原則。在 同一個對象內混合多種責任(如計算剩余的工時、設置數據格式并繪圖)會給維護帶來很大的負擔。而自動測試對于獲得生產質量的代碼同時仍保持條理性至關重 要,尤其是當您更新現有項目時更是如此。 ASP.NET Web 窗體使入門變得非常簡單,但另一方面,要將我的設計理念應用到 Web 應用程序卻并非易事。Web 窗體堅持以 UI 為側重點;其基本單位為頁面。首先設計 UI 并拖曳控件。只需將應用程序邏輯融入頁面的事件處理程序(與為 Windows? 應用程序啟用的 Visual Basic? 非常相似)就萬事大吉,這一點非常吸引人。 但進一步的頁面單元測試常常有很大困難。您必須先啟動所有 ASP.NET,然后才能在“頁面”對象的生命周期內運行該對象。盡管可以通過發送 HTTP請求到服務器或自動化瀏覽器來測試 Web 應用程序,但這類測試非常脆弱(更換一個控制 ID 測試就會中斷)、難以設置(您必須以完全相同的方式在每位開發人員的計算機上設置該服務器)并且運行緩慢。 當我開始構建更復雜的 Web 應用程序時,Web 窗體提供的抽象概念(如控件、視圖狀態和頁面生命周期)就開始添亂而不是幫忙了。我需要花越來越多的時間來配置數據綁定(并編寫大量的事件處理程序對其進 行正確配置)。我不得不想辦法縮減視圖狀態的大小以便更快加載我的頁面。Web 窗體要求每個 URL 均存在物理文件,這對于動態站點(例如 wiki)非常困難。而成功編寫一個自定義的 WebControl 是一個非常復雜的過程,需要全面了解頁面生命周期和 Visual Studio? 設計器。 自從在 Microsoft 工作開始,我就一直與其他人分享關于各種 .NET 難題的體驗并希望可以解決一些難題。最近,作為開發人員參加有關模式與實踐的 Web 客戶端軟件工廠項目 (codeplex.com/websf) 時,我遇到了一個這樣的機會。特別是,模式與實踐交付的內容之一就是自動單元測試。在 Web 客戶端軟件工廠中,我們建議使用 Model View Presenter (MVP) 模式構建可測試的 Web 窗體。 簡而言之,MVP 并非將您的邏輯放入頁面中,而是讓您構建自己的頁面,頁面 (View) 只需調用單獨的對象,即 Presenter。Presenter 對象隨即執行響應視圖上活動必需的任何邏輯,通常通過使用其它對象 (Model) 訪問數據庫、執行業務邏輯等。一旦這些步驟完成后,Presenter 會更新視圖。這種方法提供了可測試性,因為表示器從 ASP.NET 管道中隔離出來;它與視圖通過界面進行通信并可脫離頁面獨立進行測試。 MVP 的這種功能實現有點笨;您需要單獨的視圖界面,并且您必須在源代碼文件中編寫許多事件轉發函數。但如果您想要在 Web 窗體應用程序中得到可測試的 UI,這差不多是最佳途徑。任何改進均需要在基礎平臺中做出更改。
模型視圖控制器模式 幸運的是,ASP.NET 團隊聽取了象我這樣的開發人員的意見,并且已經著手開發一種新的 Web 應用程序框架,該框架與您所熟知并喜愛的 Web 窗體處于同一層級,但采用一組完全不同的設計目標:
  • 使用 HTTP 和 HTML—不隱藏。
  • 可測試性貫穿整個框架之內。
  • 幾乎在每個點均可擴展。
  • 對輸出進行總體控制。
由于此新框架基于模型視圖控制器 (MVC) 模式,因此其名稱為 ASP.NET MVC。MVC 模式最初在 70 年代發明,是 Smalltalk 技術的一部分。正如我將在本文中所展示的,它實際上非常適合 Web 的性質。MVC 將您的 UI 分為三種不同的對象:用于接收和處理輸入的控制器;包含您域邏輯的模型;以及用于生成輸出的視圖。在 Web 環境中,輸入為 HTTP 請求,而請求流程與圖 1 類似。 Figure 1?MVC 模式請求流程?(單 擊該圖像獲得較大視圖) 這實際上與 Web 窗體中的過程完全不同。在 Web 窗體模型中,輸入進入頁面(視圖),然后視圖負責處理輸入并生成輸出。而 MVC 中這些責任是分開的。 因此,您可能立即會產生以下一種想法:“嘿,這太好了。我應該如何使用它?”或“為什么 我要編寫這些對象,以前只需要編寫一個對象?”這兩個問題都問得很好,最好通過示例來進行解釋。因此,我將使用 MVC Framework 編寫一個小型 Web 應用程序以說明其優點。
創建控制器 要繼續進行,您將需要安裝 Visual Studio 2008 并獲得 MVC Framework 的副本。在撰寫本文時,ASP.NET 擴展的 2007 年 12 月社區技術預覽 (CTP) 中已提供了這些內容 (asp.net/downloads/3.5-extensions)。您可能想要獲取擴展 CTP 和 MVC 工具包,其中包括一些非常有用的幫助程序對象。一旦下載并安裝 CTP 后,您將在“新建項目”對話框中獲得名為“ASP.NET MVC Web 應用程序”的新項目類型。 選擇“MVC Web 應用程序”項目后,會為您提供一個與常用網站或應用程序稍有不同的解決方案。該解決方案模板會創建一個帶有一些新目錄的 Web 應用程序(如圖 2 中所示)。特別是 Controllers 目錄包含各種控制器類,而 Views 目錄(及其所有子目錄)包含了各種視圖。 Figure 2?MVC 項目結構? 我將會編寫一個非常簡單的控制器,返回 URL 中傳遞的名稱。右鍵單擊 Controllers 文件夾并選擇“添加項目”以顯示常用的“添加項目”對話框以及一些新增加的內容,包括 MVC 控制器類和幾個 MVC 視圖組件。在此例中,我將添加一個非常富有想象力、名為 HelloController 的類: 復制代碼 using System;
using System.Web;
using System.Web.Mvc;
namespace HelloFromMVC.Controllers
{
public class HelloController : Controller
{
[ControllerAction]
public void Index()
{
...
}
}
}
控制器類比頁面簡單得多。實際上,唯一真正必需做的就是從 System.Web.Mvc.Controller 中衍生并將 [ControllerAction] 屬性置于您的操作方法中。操作是調用以響應特定 URL 請求的一種方法。操作負責執行所需的一切處理,然后呈現一個視圖。我將通過編寫一個將名稱傳遞到視圖的簡單操作著手,如下所示: 復制代碼 [ControllerAction]
public void HiThere(string id)
{
ViewData["Name"] = id;
RenderView("HiThere");
}
操作方法會通過 ID 參數從 URL 接收該名稱(稍后會介紹方法),將其存儲在 ViewData 集合中,然后呈現名為 HiThere 的視圖。 在討論如何調用此方法,或該視圖的顯示內容之前,我希望說一說可測試性。還記得我之前關 于測試 Web 窗體頁面類有多難的評論嗎?控制器的測試簡單得多。實際上,控制器可以直接實例化,而調用操作方法無需任何附加的基礎結構。您不需要 HTTP 上下文,也不需要服務器,只要測試工具即可。作為示例,我在圖 3 中為此類包括了 Visual Studio Team System (VSTS) 測試單元。 ?Figure?3?Controller Unit Test 復制代碼 namespace HelloFromMVC.Tests
{
[TestClass]
public class HelloControllerFixture
{
[TestMethod]
public void HiThereShouldRenderCorrectView()
{
TestableHelloController controller = new
TestableHelloController();
controller.HiThere("Chris");
Assert.AreEqual("Chris", controller.Name);
Assert.AreEqual("HiThere", controller.ViewName);
}
}
class TestableHelloController : HelloController
{
public string Name;
public string ViewName;
protected override void RenderView(
string viewName, string master, object data)
{
this.ViewName = viewName;
this.Name = (string)ViewData["Name"];
}
}
}
下面將進行幾項操作。實際的測試相當簡單:實例化該控制器,使用預期的數據調用該方法, 然后檢查呈現的視圖是否正確。我通過創建測試專用的子類覆蓋 RenderView 方法進行檢查。這可以縮短實際創建 HTML 的時間。我只關心是否將正確的數據發送到視圖以及是否呈現了正確的視圖。我不關心此測試視圖本身的底層詳細信息。
創建視圖 當然,最終我必須生成一些 HTML,因此,讓我們創建該 HiThere 視圖。要進行此操作,首先,我將在解決方案中的 Views 文件夾下創建名為 Hello 的新文件夾。默認情況下,控制器將在 Views\<控制器前綴> 文件夾(控制器前綴為控制器類的名稱去掉 "Controller" 字樣)中查找視圖。因此,對于 HelloController 呈現的視圖,它會在 Views\Hello 中查找。解決方案的查找結果如圖 4 所示。 Figure 4?將視圖添加到項目中?(單 擊該圖像獲得較大視圖) 視圖的 HTML 如下所示: 復制代碼 <html >
<head runat="server">
<title>Hi There!</title>
</head>
<body>
<div>
<h1>Hello, <%= ViewData["Name"] %></h1>
</div>
</body>
</html>
應注意以下幾件事。沒有 runat="server" 標記。沒有 form 標記。沒有控件聲明。實際上,這看起來更象傳統的 ASP 而不是 ASP.NET。請注意,MVC 視圖僅負責生成輸出,因此其不需要任何 Web 窗體頁面所需的事件處理或復雜控件。 MVC Framework 借用了 .aspx 文件格式作為一種有用的文本模板語言。如果需要,甚至可以使用源代碼,但默認情況下,源代碼文件如下所示: 復制代碼 using System;
using System.Web;
using System.Web.Mvc;
namespace HelloFromMVC.Views.Hello
{
public partial class HiThere : ViewPage
{
}
}
沒有頁面初始化或加載方法,沒有事件處理程序,除了基類聲明以外沒有任何內容,基類聲明 為 ViewPage 而不是 Page。這就是 MVC 視圖所需的一切。運行該應用程序,導航至 http://localhost:<端口>/Hello/HiThere/Chris,您將看到如圖 5 所示的內容。 Figure 5?成功的 MVC 視圖?(單 擊該圖像獲得較大視圖) 如果您看到的并非如圖 5 所示,而是難以理解的意外情況,請不要驚慌。如果您將 HiThere.aspx 文件設置為 Visual Studio 中的活動文檔,則當按 F5 后,Visual Studio 將嘗試直接訪問 .aspx 文件。由于 MVC 視圖要求控制器在顯示前運行,因此嘗試直接導航至該頁面將不起作用。只需將該 URL 編輯為與圖 5 中所示的內容相匹配,即可正常工作。 MVC Framework 如何知道調用我的操作方法?該 URL 甚至沒有文件擴展名。答案是 URL 路由。如果您仔細查看 global.asax.cs 文件,則會看到如圖 6 所示的代碼段。全局 RouteTable 會存儲 Route 對象的集合。每個 Route 說明一個 URL 窗體以及對其進行何種操作。默認情況下,會向該表中添加兩個路由。第一個是該方法的內容。它說明每個 URL 在服務器名后均由三部分組成,第一部分應為控制器名,第二部分為操作名稱,而第三部分為 ID 參數。 ?Figure?6?Route Table 復制代碼 public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
// Change Url= to Url="[controller].mvc/[action]/[id]"
// to enable automatic support on IIS6
RouteTable.Routes.Add(new Route
{
Url = "[controller]/[action]/[id]",
Defaults = new { action = "Index", id = (string)null },
RouteHandler = typeof(MvcRouteHandler)
});
RouteTable.Routes.Add(new Route
{
Url = "Default.aspx",
Defaults = new {
controller = "Home",
action = "Index",
id = (string)null },
RouteHandler = typeof(MvcRouteHandler)
});
}
}
復制代碼 Url = "[controller]/[action]/[id]"
此默認路由是能讓我的 HiThere 方法得以調用的路由。請記住此 URL:http://localhost/Hello/HiThere/Chris?此路由將 Hello 與控制器、HiThere 與操作以及 Chris 與 ID 一一對應。MVC Framework 隨即創建 HelloController 實例,調用 HiThere 方法,然后將 Chris 作為 ID 參數的值傳遞。 此默認路由為您提供了許多功能,但您也可以添加自己的路由。例如,我想要一個真正友好的 站點,好友們只需輸入他們的姓名即可獲得個性化的問候。如果我在路由表的頂部添加以下路由 復制代碼 RouteTable.Routes.Add(new Route
{
Url = "[id]",
Defaults = new {
controller = "Hello",
action = "HiThere" },
RouteHandler = typeof(MvcRouteHandler)
});
隨后,我只需訪問 ,我的操作仍處于調用狀態,而我將會看到熟悉的友好問候。 系統如何知道調用哪個控制器和操作?答案是 Defaults 參數。它利用新的 C# 3.0 匿名類型語法來創建一個偽詞典。Route 上的 Defaults 對象可包含任意附加的信息,對于 MVC,它還可以包含一些眾所周知的條目:即控制器和操作。如果 URL 中沒有指定控制器或操作,則其將使用 Defaults 中的名稱。這就是為什么即使我在 URL 中忽略它們,但仍可以將我的請求映射到正確的控制器和操作。 還有一件事需要注意:還記得我說過“添加到表格的頂部”嗎?如果您將其置于底部,將會出 現錯誤。路由根據先到先得的原則進行工作。當處理 URL 時,路由系統會自上至下瀏覽表格,并且使用第一個匹配的路由。在本例中,默認路由 "[controller]/[action]/[id]" 匹配,因為它們是操作和 ID 的默認值。這樣,它會繼續查找 ChrisController,但我沒有控制器,因此會出現錯誤。
稍大的示例 現在,我已經說明了 MVC Framework 的基礎知識,將為您展示一個更大的示例,實現比僅顯示字符串更多的功能。wiki 是一種可以在瀏覽器中進行編輯的網站。可以輕松地添加或編輯頁面。我使用 MVC Framework 編寫了一個小型的示例 wiki。“編輯此頁面”屏幕如圖 7 所示。 Figure 7?編輯主頁?(單擊該 圖像獲得較大視圖) 您可以檢查本文的代碼下載以查看如何實現底層 wiki 邏輯。現在我想重點說明 MVC Framework 如何使 Web 上的 wiki 獲取變得簡單。讓我們先設計 URL 結構。我想要以下各項:
  • /[pagename] 顯示該名稱的頁面。
  • /[pagename]?version=n 顯示頁面的請求版本,其中 0 = 當前版本,1 = 以前的版本,以此類推。
  • /Edit/[pagename] 打開該頁的編輯屏幕。
  • /CreateNewVersion/[pagename] 是為提交編輯而傳入的 URL。
讓我們從 wiki 頁面的基本顯示開始。我為它創建了一個名為 WikiPageController 的新類。接下來,我會添加一個名為 ShowPage 的操作。啟動的 WikiPageController 如圖 8 所示。ShowPage 方法相當簡單。WikiSpace 和 WikiPage 類分別表示一組 wiki 頁面和特定的頁面(及其修訂)。此操作只需加載模型并調用 RenderView。但此處的 "new WikiPageViewData" 行是什么意思? ?Figure?8?WikiPageController Implementation of ShowPage 復制代碼 public class WikiPageController : Controller
{
ISpaceRepository repository;
public ISpaceRepository Repository
{
get {
if (repository == null)
{
repository = new FileBasedSpaceRepository(
Request.MapPath("~/WikiPages"));
}
return repository;
}
set { repository = value; }
}
[ControllerAction]
public void ShowPage(string pageName, int? version)
{
WikiSpace space = new WikiSpace(Repository);
WikiPage page = space.GetPage(pageName);
RenderView("showpage",
new WikiPageViewData
{
Name = pageName,
Page = page,
Version = version ?? 0
});
}
}
我前面的示例說明了一種將數據從控制器傳遞到視圖的方法:即 ViewData 詞典。詞典非常方便,但也很危險。它們幾乎包含一切內容,您不能獲取內容的任何 IntelliSense?,并且由于 ViewData 詞典屬于 Dictionary<string, object> 類型,它將消耗內容,您必須計算所有一切。 當您了解在視圖中將需要什么數據后,就可以傳遞強類型化的 ViewData 對象。在我的示例中,我創建了一個簡單的對象 (WikiPageViewData),如圖 9 中所示。此對象將 wiki 頁面信息帶到視圖,同時還攜帶了一些實用工具方法,執行獲取 wiki 標記的 HTML 版本這類任務。 ?Figure?9?WikiPageViewData Object 復制代碼 public class WikiPageViewData {
public string Name { get; set; }
public WikiPage Page { get; set; }
public int Version { get; set; }
public WikiPageViewData() {
Version = 0;
}
public string NewVersionUrl {
get {
return string.Format("/CreateNewVersion/{0}", Name);
}
}
public string Body {
get { return Page.Versions[Version].Body; }
}
public string HtmlBody {
get { return Page.Versions[Version].BodyAsHtml(); }
}
public string Creator {
get { return Page.Versions[Version].Creator; }
}
public string Tags {
get { return string.Join(",", Page.Versions[Version].Tags); }
}
}
現在,我已經定義了視圖數據,那么,我如何使用它呢?在 ShowPage.aspx.cs 中,您將看到以下內容: 復制代碼 namespace MiniWiki.Views.WikiPage {
public partial class ShowPage : ViewPage<WikiPageViewData>
{
}
}
請注意,我將基類類型定義為 ViewPage<WikiPageViewData>。這意味著頁面的 ViewData 屬性為 WikiPageViewData 類型,而不是象以前示例中的“Dictionary”。 .aspx 文件中的實際標記非常簡單: 復制代碼 <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
AutoEventWireup="true" CodeBehind="ShowPage.aspx.cs"
Inherits="MiniWiki.Views.WikiPage.ShowPage" %>
<asp:Content
ID="Content1"
ContentPlaceHolderID="MainContentPlaceHolder"
runat="server">
<h1><%= ViewData.Name %></h1>
<div id="content" class="wikiContent">
<%= ViewData.HtmlBody %>
</div>
</asp:Content>
請注意,當引用 ViewData 時,我沒有使用索引操作符 []。由于我現在有強類型化的 ViewData,我可以直接訪問該屬性。不需要進行任何計算,而 Visual Studio 會提供 IntelliSense。 目光敏銳的讀者將會注意到此文件中的 <asp:Content> 標記。沒錯,“母版頁”確實可以與 MVC 視圖配合使用。并且“母版頁”還可以成為視圖。讓我們看看“母版頁”的源代碼: 復制代碼 namespace MiniWiki.Views.Layouts
{
public partial class Site :
System.Web.Mvc.ViewMasterPage<WikiPageViewData>
{
}
}
相關標記如圖 10 中所示。現在,“母版頁”將獲得與視圖完全相同的 ViewData 對象。我已經將“母版頁”的基類聲明為 ViewMasterPage<WikiPageViewData>,因此,我擁有了正確類型的 ViewData。我會在那里設置各種 DIV 標記以對頁面進行布局,填寫版本列表,然后以常用內容占位符收尾。 ?Figure?10?Site.Master 復制代碼 <%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="MiniWiki.Views.Layouts.Site" %> <%@ Import Namespace="MiniWiki.Controllers" %> <%@ Import Namespace="MiniWiki.DomainModel" %> <%@ Import Namespace="System.Web.Mvc" %> <html > <head runat="server"> <title><%= ViewData.Name %></title> <link href="http://http://www.cnblogs.com/Content/Site.css" rel="stylesheet" type="text/css" /> </head> <body> <div id="inner"> <div id="top"> <div id="header"> <h1><%= ViewData.Name %></h1> </div> <div id="menu"> <ul> <li><a href="http://Home">Home</a></li> <li> <%= Html.ActionLink("Edit this page", new { controller = "WikiPage", action = "EditPage", pageName = ViewData.Name })%> </ul> </div> </div> <div id="main"> <div id="revisions"> Revision history: <ul> <% int i = 0; foreach (WikiPageVersion version in ViewData.Page.Versions) { %> <li> <a href="http://<%= ViewData.Name %>?version=<%= i %>"> <%= version.CreatedOn %> by <%= version.Creator %> </a> </li> <% ++i; } %> </ul> </div> <div id="maincontent"> <asp:ContentPlaceHolder ID="MainContentPlaceHolder" runat="server"> </asp:ContentPlaceHolder> </div> </div> </div> </body> </html> 另一件需要注意的事是對 Html.ActionLink 的調用。以下是呈現幫助程序的一個示例。各種視圖類均具有兩種屬性,Html 和 Url。每種均有輸出 HTML 代碼塊的有用方法。在本例中,Html.ActionLink 獲取一個對象(此處為匿名類型)并通過路由系統將其返回。這將會生成一個 URL,該 URL 將路由至我指定的控制器和操作。這樣一來,無論我如何更改路由,“編輯此頁面”鏈接將始終指向正確的位置。 您可能還注意到,我還不得不依靠手動構建鏈接(到先前頁面版本的鏈接)。遺憾的是,當前 的路由系統在涉及查詢字符串時生成 URL 的功能不是十分完善。這應會在框架的后續版本中得到修復。
創建表單和回發 現在,讓我們看看控制器上的 EditPage 操作: 復制代碼 [ControllerAction]
public void EditPage(string pageName)
{
WikiSpace space = new WikiSpace(Repository);
WikiPage page = space.GetPage(pageName);
RenderView("editpage",
new WikiPageViewData {
Name = pageName,
Page = page });
}
同樣,該操作所做的不多—它只是呈現指定頁面的視圖。視圖中的內容變得更加有趣,如圖 11 中所示。此文件構建了一個 HTML 表單,但沒有出現 Runat="server"。Url.Action helper 用于生成表單回發的 URL。其中還使用了幾種不同的 HTML 幫助程序(如 TextBox、TextArea 和 SubmitButton)。它們會出色完成您的預期目標:為各種輸入字段生成 HTML。 ?Figure?11?EditPage.aspx 復制代碼 <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="EditPage.aspx.cs" Inherits="MiniWiki.Views.WikiPage.EditPage" %> <%@ Import Namespace="System.Web.Mvc" %> <%@ Import Namespace="MiniWiki.Controllers" %> <asp:Content ID="Content1" ContentPlaceHolderID="MainContentPlaceHolder" runat="server"> <form action="<%= Url.Action( new { controller = "WikiPage", action = "NewVersion", pageName = ViewData.Name })%>" method=post> <% if (ViewContext.TempData.ContainsKey("errors")) { %> <div id="errorlist"> <ul> <% foreach (string error in (string[])ViewContext.TempData["errors"]) { %> <li><%= error%></li> <% } %> </ul> </div> <% } %> Your name: <%= Html.TextBox("Creator", ViewContext.TempData.ContainsKey("creator") ? (string)ViewContext.TempData["creator"] : ViewData.Creator)%> <br /> Please enter your updates here:<br /> <%= Html.TextArea("Body", ViewContext.TempData.ContainsKey("body") ? (string)ViewContext.TempData["body"] : ViewData.Body, 30, 65)%> <br /> Tags: <%= Html.TextBox( "Tags", ViewContext.TempData.ContainsKey("tags") ? (string)ViewContext.TempData["tags"] : ViewData.Tags)%> <br /> <%= Html.SubmitButton("SubmitAction", "OK")%> <%= Html.SubmitButton("SubmitAction", "Cancel")%> </form> </asp:Content> 處理 Web 編程最頭疼的事情之一就是表單中的錯誤。更確切地說,您想要顯示錯誤信息,但同時想要保留原來輸入的數據。我們都有過那種經歷,在填寫一張有 35 個字段的表單時出現一個錯誤,程序卻只是提供一堆錯誤信息和一張新的空白表單。MVC Framework 使用 TempData 存儲以前輸入信息,以便可以重新填入表單。這是 ViewState 實際上在 Web 窗體中變得非常簡單的原因,因為保存控件的內容幾乎是自動的。 我想在 MVC 中如法炮制,因此引入了 TempData。TempData 是一種詞典,與非類型化的 ViewData 很相似。不過,TempData 的內容僅針對單一請求存在,隨后就會被刪除。要了解如何使用此方法,請參閱圖 12,NewVersion 操作。 ?Figure?12?NewVersion Action 復制代碼 [ControllerAction] public void NewVersion(string pageName) { NewVersionPostData postData = new NewVersionPostData(); postData.UpdateFrom(Request.Form); if (postData.SubmitAction == "OK") { if (postData.Errors.Length == 0) { WikiSpace space = new WikiSpace(Repository); WikiPage page = space.GetPage(pageName); WikiPageVersion newVersion = new WikiPageVersion( postData.Body, postData.Creator, postData.TagList); page.Add(newVersion); } else { TempData["creator"] = postData.Creator; TempData["body"] = postData.Body; TempData["tags"] = postData.Tags; TempData["errors"] = postData.Errors; RedirectToAction(new { controller = "WikiPage", action = "EditPage", pageName = pageName }); return; } } RedirectToAction(new { controller = "WikiPage", action = "ShowPage", pageName = pageName }); } 首先,它創建一個 NewVersionPostData 對象。這是另一個幫助程序對象,具有存儲記入的內容和進行某些驗證的屬性和方法。為加載 postData 對象,我將使用 MVC 工具包的幫助程序。UpdateFrom 實際上是工具包提供的擴展方法,它使用反射將表單字段的名稱與我的對象中屬性的名稱相對映。最終結果是,所有字段值均載入到我的 postData 對象中。不過,UpdateFrom 使用起來確實有缺點,由于它直接從 HttpRequest 獲取表單數據,使單元測試變得更為困難。 NewVersion 檢查的第一項是 SubmitAction。如果用戶單擊“確定”按鈕并確實想要發布編輯的頁面,則此項檢查將通過。如果此處有任何其它值,操作會重定向回 ShowPage,只是重新顯示原來的頁面。 如果用戶確實單擊了“確定”,則檢查 postData.Errors 屬性。這將在記入內容上運行一些簡單的驗證。如果沒有任何錯誤,我會將新版本的頁面重新寫入 wiki。不過,如果出現錯誤,情況會變得饒有趣味。 如果出現錯誤,我會設置 TempData 詞典的各個字段,以便其包含 PostData 的內容。然后,我會重定向回“編輯”頁面。現在,由于已設置 TempData,頁面將重新顯示以用戶上次記入的值初始化的表單。 處理記入、驗證和 TempData 的這個過程現在變得有些煩瑣,并且需要多做一些手動工作。將來發行的版本應包括至少會將一些 TempData 檢查自動化的幫助程序方法。關于 TempData 的最后一個注意事項是:TempData 的內容存儲在用戶的服務器端會話中。如果您關閉會話,TempData 將無法正常工作。
創建控制器 現在,wiki 的基礎已在發揮功效,但繼續進行之前,我想要明確實現中的以下幾個要點。例如,Repository 屬性用于分離 wiki 的邏輯與物理存儲。您可以提供在文件系統(正如我所做的那樣)、數據庫或您想要的任何位置中存儲內容的存儲庫。遺憾的是,我需要解決兩個問題。 首先,我的控制器類與具體的 FileBasedSpaceRepository 類緊密地連在一起。我需要一個默認值,以便在屬性沒有設置時,也能合理使用。更糟的是,磁盤上文件的路徑在這里也是硬編碼的。最起碼,它應取自配置。 其次,我的對象必須依賴存儲庫,否則無法運行。對于良好的設計,存儲庫實際應為構造函數 參數,而不是屬性。但我無法將其添加到構造函數中,因為 MVC Framework 要求控制器上的構造函數不能有參數。 幸運的是,我可以通過一個擴展性掛接擺脫此限制:即控制器工廠。控制器工廠的功能正如其 名稱所指:它創建 Controller 實例。您只需要創建一個類實現 IControllerFactory 接口并向 MVC 系統注冊即可。您可以為所有控制器或僅為指定的類型注冊控制器工廠。圖 13 所示為 WikiPageController 的控制器工廠,其現在將存儲庫作為構造函數參數傳遞。在這種情況下,實現非常煩瑣,但它可以創建能使用更強大工具(特別是依賴關系注入容器)的控制器。 無論如何,現在我擁有了將控制器依賴關系分離到對象中(易于管理和維護)的所有詳細信息。 ?Figure?13?Controller Factory 復制代碼 public class WikiPageControllerFactory : IControllerFactory { public IController CreateController(RequestContext context, Type controllerType) { return new WikiPageController( GetConfiguredRepository(context.HttpContext.Request)); } private ISpaceRepository GetConfiguredRepository(IHttpRequest request) { return new FileBasedSpaceRepository(request.MapPath("~/WikiPages")); } } 此工作的最后一步是向框架注冊工廠。通過 ControllerBuilder 類可進行此操作,方法是將以下行添加到 Application_Start 方法中的 Global.asax.cs(路由前后均可): 復制代碼 ControllerBuilder.Current.SetControllerFactory(
typeof(WikiPageController), typeof(WiliPageControllerFactory));
這將注冊 WikiPageController 的工廠。如果此項目中有其他控制器,它們不會使用此工廠,因為此工廠僅針對 WikiPageController 類型進行了注冊。如果您想要將工廠設置為供所有控制器使用,還可以調用 SetDefaultControllerFactory。
其他擴展點 控制器工廠只是框架擴展性的起點。本文中無法詳述所有的細節,因此我將僅僅說明要點。首 先,如果您想要輸出的內容不是 HTML,或想要使用其他模板引擎而不是 Web 窗體,可將控制器的 ViewFactory 設為其他項。您可以實現 IviewFactory 界面,然后即可完全控制如何生成輸出。這對于生成 RSS、XML 或圖形非常有用。 正如您所見到的,路由系統非常靈活。但路由系統中沒有任何內容是 MVC 專用的。每個路由均有一個 RouteHandler 屬性;目前為止,我始終將其設為 MvcRouteHandler。但可以實現 IRouteHandler 界面并將路由系統與其他 Web 技術掛接。將來推出的框架將附帶 WebFormsRouteHandler,并且其他技術也會在將來利用通用路由系統的優勢。 控制器并非必須從 System.Web.Mvc.Controller 衍生。控制器需要做的僅僅是實現 IController 界面,該界面只有稱為 Execute 的一種方法。您可以從中進行任何操作。另一方面,如果您想將 Controller 基類的幾種行為組合在一起,您可以覆蓋 Controller 的許多虛擬函數:
  • OnPreAction、OnPostAction 和 OnError 可讓您將每個已執行操作上的預處理和后處理連接起來。OnError 為您提供在控制器內處理錯誤的機制。
  • 當 URL 路由到控制器但控制器沒有實現路由中請求的操作時,會調用 HandleUnknownAction。默認情況下,此方法會拋出一個異常,但您可以用所需的操作覆蓋默認值。
  • InvokeAction 是一種方法,它負責解決調用何種操作方法并進行調用。如果您想要自定義過程(例如,除去 [ControllerAction] 屬性的要求),應使用該方法。
還有其他幾種針對 Controller 的虛擬方法,但這些方法主要是測試掛接而不是作為擴展點。例如,RedirectToAction 是虛擬的,因此您可以創建實際并不進行重定向的衍生類。這樣,您不需要完全運行 Web 服務器就能測試重定向操作。
要告別 Web 窗體嗎? 現在您可能在想:“Web 窗體會面臨怎樣的命運?MVC 會取代它嗎?”答案是否定的!Web 窗體是一種普及技術,Microsoft 將繼續支持并改進它。它在許多應用程序中發揮著重要的作用;例如,可使用 Web 窗體創建典型的 Intranet 數據庫報表應用程序,所花的時間比使用 MVC 編寫短得多。此外,Web 窗體支持大量的控件,許多控件均具備非常先進的功能,可以大大提高效率。 那么,什么時候應該選擇 MVC 呢?這主要取決于您的要求和喜好。您是否正在為獲得想要的 URL 格式而煩惱?您是否想要對 UI 進行單元測試?以上情況均需要依靠 MVC。反之,如果您要顯示許多數據,提供可編輯的網格和優良的樹形視圖控件?那么,您暫時最好還是使用 Web 窗體。 今后,MVC Framework 很可能在 UI 控制部分有所改進,但在便利性上,它可能始終不及 Web 窗體,因為后者具備大量拖曳功能。同時,ASP.NET MVC Framework 為 Web 開發人員提供了一種在 Microsoft .NET Framework 中構建 Web 應用程序的新方法。Framework 針對可測試性設計、推倡使用 HTTP 并且幾乎在每個點均可擴展。對于那些想要完全控制其 Web 應用程序的開發人員來說,這是一個對 Web 窗體的誘人補充。
Chris Tavares 是 Microsoft 模式和實施方案小組的一名開發人員,他致力于幫助開發社區了解在 Microsoft 平臺上構建系統的最佳實踐。他還是 ASP.NET MVC 小組的虛擬成員,幫助您設計新的框架。可以通過 cct@tavaresstudios.com 與 Chris 取得聯系。

轉載于:https://www.cnblogs.com/bndy/archive/2010/07/19/1780491.html

總結

以上是生活随笔為你收集整理的ASP.NET MVC: 构建不带 Web 窗体的 Web 应用程序(转载)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

激情婷婷久久 | 色悠悠久久综合 | 99精品免费在线 | 免费成人av在线 | 免费91在线 | 六月色婷婷 | 91私密视频 | 欧美婷婷综合 | 亚洲欧美日韩精品久久久 | 91九色porn在线资源 | 黄色精品国产 | 奇米网在线观看 | www.日日日.com | 五月天综合色 | 日本午夜在线亚洲.国产 | 国产福利精品视频 | 91片黄在线观看 | 五月天久久久久久 | 精品国产自在精品国产精野外直播 | 久久久久国产精品厨房 | 91中文字幕在线 | 色综合久久88色综合天天免费 | 国产精品伦一区二区三区视频 | 四虎5151久久欧美毛片 | 视频一区在线免费观看 | 久久精品视频在线观看免费 | 天堂久久电影网 | 久草综合视频 | 欧美久久久久久久久久 | 亚洲 综合 国产 精品 | 国产福利久久 | 91亚·色 | 中文视频一区二区 | 亚洲精品99久久久久久 | 美女久久视频 | 国产又粗又猛又色又黄视频 | 午夜精品一区二区国产 | 免费国产在线精品 | 99久久精品国产一区二区成人 | 国语精品久久 | 婷婷综合成人 | 国产专区精品 | 999男人的天堂 | 国产不卡视频在线播放 | 99久久久久久久 | 夜夜躁天天躁很躁波 | 91av在线免费视频 | 天天干天天想 | 久久国产精品免费视频 | 精品字幕在线 | 91精品国产乱码在线观看 | 亚洲欧洲一区二区在线观看 | 一级黄色a视频 | 久久国产精品99国产 | 国产不卡视频在线 | 婷婷色 亚洲 | 午夜久久电影网 | 欧美大片aaa | 日韩精品久久久久久 | 精品久久久久久亚洲综合网站 | 九九三级毛片 | 国产资源在线播放 | 日韩欧美在线观看一区二区 | 精品一二三区 | 精品国产电影一区二区 | 五月天.com | 免费成人在线观看 | 97视频总站 | 在线观看91av | 中文字幕一区二区在线观看 | 国产精品永久久久久久久久久 | 久久久亚洲成人 | 91视频 - 114av | 国产一级在线观看 | 成人av在线影院 | 国产精品毛片一区二区在线 | 国产精品成人自产拍在线观看 | 一区二区三区四区不卡 | 一区二区精品 | 精品在线一区二区三区 | 久久久香蕉视频 | 在线中文日韩 | 91福利视频网站 | 久久人人爽人人片av | 激情影音先锋 | 日韩精品一区二区三区视频播放 | 蜜臀一区二区三区精品免费视频 | 国产99在线免费 | 久艹在线观看视频 | 中文字幕久久网 | 中文字幕在线观看三区 | 伊人亚洲综合网 | 黄色亚洲大片免费在线观看 | 亚洲精品国产综合久久 | 日韩av一区二区在线影视 | 在线va网站 | 伊人六月 | 国产一区二区三区午夜 | 婷婷色狠狠 | 久久综合中文色婷婷 | 国产精品人成电影在线观看 | 免费a网址 | 四虎国产精品成人免费影视 | 97人人看| 国产正在播放 | 一级片黄色片网站 | 精品999国产 | 久久精品99国产精品亚洲最刺激 | 美女视频久久久 | 亚洲最新av在线网站 | 国产精品2020 | 国产黄免费在线观看 | 成年人在线| 免费看v片 | 婷婷在线视频观看 | 久草免费在线视频观看 | 日本久久久久久 | 中文字幕在线高清 | 高清视频一区二区三区 | 久久综合五月婷婷 | 日韩精品一区电影 | 欧美,日韩 | 91精品国产自产在线观看永久 | 亚洲欧美精品一区 | 午夜精品久久久久久久爽 | 西西大胆免费视频 | 久久久久久久精 | 国产资源 | 国产日产精品一区二区三区四区 | 日韩av在线免费看 | 免费91在线观看 | 亚洲欧美久久 | 成人网在线免费视频 | 亚洲日本一区二区在线 | 日韩a级黄色片 | 久久久精品日本 | 欧美精品免费在线观看 | 国产黄| 国产亚洲精品久久久久久移动网络 | 国产91电影在线观看 | 色偷偷88888欧美精品久久 | 婷婷色网站 | 日韩精品免费专区 | 久久国产精品99久久久久久进口 | www欧美xxxx| 免费av在线网站 | 摸bbb搡bbb搡bbbb | 午夜av剧场| 97电影网手机版 | 超碰在线9| 国产精品视频在线观看 | 亚洲综合在线五月天 | 91成人黄色 | 97国产大学生情侣白嫩酒店 | 久久精品国产亚洲a | 久久热首页 | 亚洲精品理论片 | 欧美激情精品久久久久 | 欧美黄色高清 | 久草视频在线观 | 天天天天干 | 亚洲国产无| 亚洲在线网址 | 婷婷99 | 久久艹欧美 | 激情电影影院 | www色网站 | av成人黄色 | 久久久九色精品国产一区二区三区 | 91人人澡人人爽人人精品 | 999久久久久久久久 69av视频在线观看 | 91久久国产自产拍夜夜嗨 | 超碰在线9 | 久久精品影片 | 日本中文一级片 | 成年人视频在线观看免费 | 国产黄在线观看 | 国产成人在线播放 | 国产亚洲午夜高清国产拍精品 | av在线进入| 国产成人av一区二区三区在线观看 | 国产91成人在在线播放 | 免费一级片在线 | 国产美女网 | 免费亚洲精品视频 | 欧美人牲 | 日韩网站在线 | 色狠狠一区二区 | 91九色综合 | 色综合久久网 | 久久日韩精品 | 美女黄濒 | 欧美专区日韩专区 | 久草在线观 | 午夜免费福利视频 | 国产精品岛国久久久久久久久红粉 | 日韩欧美精品一区二区 | 国产精品一区二区三区免费视频 | 国产一区国产二区在线观看 | 91成人免费电影 | 手机看片1042| 天天干天天干天天 | 在线免费观看不卡av | 国产 日韩 欧美 自拍 | 成人免费毛片aaaaaa片 | 一级免费片 | 丁香六月婷婷开心 | 国产免费高清 | 在线观看视频黄 | 久久免费国产电影 | 国内精品久久久久久久久久久久 | 三级黄色免费片 | 夜夜狠狠 | 日本中文在线播放 | 中文字幕观看视频 | 久久精品免费看 | 91理论电影| 2023av在线| 日日夜夜操av | 夜夜操天天干, | 丁香六月婷婷开心 | 成人少妇影院yyyy | 日韩视频一区二区在线 | 国产一二区免费视频 | www.色午夜 | 日日麻批40分钟视频免费观看 | 日韩高清在线看 | 国产69精品久久久久9999apgf | 国内精品视频一区二区三区八戒 | av超碰在线| 九九免费精品 | 久久久久久久久久伊人 | 国产一区在线观看免费 | 91色欧美 | 国产视频每日更新 | 午夜在线免费观看视频 | 国产一区欧美二区 | 黄色特一级 | 亚洲日本精品视频 | 伊人电影天堂 | 久久成人在线视频 | 人人舔人人爱 | av成人免费| 91精品高清| 欧美日韩精品在线免费观看 | www91在线| 欧美午夜激情网 | 免费观看久久 | 久久精品这里热有精品 | 亚洲成av人片在线观看香蕉 | 911在线| 81精品国产乱码久久久久久 | 日日摸日日添夜夜爽97 | 欧美精品资源 | 91丨精品丨蝌蚪丨白丝jk | 最新国产在线视频 | 国产一区影院 | 狠狠色丁香久久婷婷综合丁香 | 成人国产精品久久久久久亚洲 | 色噜噜日韩精品欧美一区二区 | 国产精品18久久久久vr手机版特色 | 久操综合| 国产精品一区电影 | 久久精品一区 | 九九热精品视频在线观看 | 国产伦精品一区二区三区高清 | 懂色av懂色av粉嫩av分享吧 | 亚洲午夜大片 | 国产一级高清 | 免费福利在线观看 | 天天做天天看 | 婷婷色五 | 欧美在线久久 | 国产一级久久 | www.日日日.com | 免费网站观看www在线观看 | 91色视频 | 国产这里只有精品 | 五月婷婷一区二区三区 | 91c网站色版视频 | 日韩久久久久久久久久久久 | 天天干夜夜爱 | 精品一区二区亚洲 | 国产成人精品一区二区三区福利 | 成人羞羞视频在线观看免费 | 一区二区三区在线视频111 | 国产精品毛片一区二区三区 | 午夜av大片 | 国产在线精品视频 | 欧美日韩一区二区三区视频 | 免费亚洲视频在线观看 | 国产成人亚洲在线观看 | 成人在线视频你懂的 | 色在线网| 91丨九色丨首页 | 午夜国产一区 | 国产一级二级在线播放 | 免费黄a | 国产精品美女毛片真酒店 | 又黄又爽的免费高潮视频 | 中文字幕一区二区三区四区 | 久久综合久久88 | 91精品成人 | 日韩精品免费一区二区三区 | 国产一区视频导航 | 国产精品久久久久久一二三四五 | 国内精品久久久久久久 | 免费看黄色小说的网站 | 五月婷婷影院 | 国产精品激情在线观看 | 久久久亚洲精品 | 日韩中文字幕一区 | 欧美另类美少妇69xxxx | 成人一区二区三区中文字幕 | 热久久影视 | 国产精品黑丝在线观看 | 国产精品专区一 | 97超碰在线资源 | 久久在现视频 | 六月丁香婷 | 天天操 夜夜操 | 少妇精品久久久一区二区免费 | 99久久久久久国产精品 | 爱情影院aqdy鲁丝片二区 | 久久免费视频7 | 国产五月色婷婷六月丁香视频 | 欧美男同网站 | 69精品视频 | 日韩欧美在线一区二区 | 国产精品第一视频 | 四虎永久视频 | 国产精品v欧美精品 | 欧美热久久 | www.com.日本一级 | 青春草视频在线播放 | 国产成人一区二区三区在线观看 | 一级淫片a| 亚洲精品成人av在线 | 毛片网站在线观看 | 成年人免费观看在线视频 | 激情五月婷婷综合网 | 成年人免费av | 一区二区三区四区免费视频 | 一区二区三区国产欧美 | 亚洲年轻女教师毛茸茸 | 久久亚洲二区 | 国产精品九九九九九 | 国产不卡精品 | 在线观看亚洲a | 国产亚洲在线 | 免费精品国产va自在自线 | 久久综合久久伊人 | 91九色蝌蚪国产 | 天天操天天操天天操天天操天天操 | 一级性视频 | 五月天精品视频 | 婷五月天激情 | 久久av观看 | 成人91在线观看 | 中文字幕av在线免费 | 麻豆传媒电影在线观看 | 国产色视频 | 国产高清不卡在线 | 激情丁香 | 精品久久久久久国产偷窥 | 一区二区视频在线看 | 男女精品久久 | 四虎视频| 日韩免费网址 | 91麻豆产精品久久久久久 | www黄色 | 99久久99久久精品国产片果冰 | 国产五月 | 十八岁以下禁止观看的1000个网站 | 国产91免费在线观看 | 亚洲国产成人在线播放 | 国内精品视频久久 | 久久精品视频在线看 | 国产免费久久久久 | 国产精品区二区三区日本 | 在线观看中文字幕网站 | 免费av免费观看 | 亚洲成人av在线播放 | 天天射狠狠干 | 美女一区网站 | 国产最顶级的黄色片在线免费观看 | 少妇精69xxtheporn| 99国产视频 | 日韩精品欧美专区 | 亚洲精品啊啊啊 | 99re8这里有精品热视频免费 | 丝袜美女在线 | h视频日本 | 激情伊人五月天 | 成人免费观看电影 | 欧美久久成人 | 最近日韩免费视频 | 亚洲国产精品久久久 | 91香蕉视频色版 | 欧美日韩一级久久久久久免费看 | 中文欧美字幕免费 | 亚洲国产婷婷 | 一区二区精品久久 | 日韩免费高清 | 免费网址在线播放 | 91手机视频 | 久久国产精品免费观看 | 久久精品视频免费播放 | 亚洲国产综合在线 | 日韩欧美一区二区在线观看 | 久久精品国产成人精品 | 久久精品成人欧美大片古装 | 国模一区二区三区四区 | 在线免费观看国产黄色 | 丝袜av一区 | 久久久国产网站 | 久久综合婷婷国产二区高清 | 黄色软件在线观看 | 国产精品不卡在线 | 国产精品第7页 | 日韩在线高清免费视频 | 国产精品精品久久久久久 | 成人欧美一区二区三区在线观看 | 久久国产女人 | 亚洲国产中文在线观看 | 日韩一区二区三区观看 | 欧美日韩一级在线 | 国产小视频你懂的在线 | 久久a v电影 | 天天干 夜夜操 | 天天草天天干天天 | 国产精品久久久久婷婷二区次 | 精品在线观看一区二区 | 亚洲国产成人精品电影在线观看 | av丝袜天堂 | 亚洲精品国产日韩 | 国产亚洲人成网站在线观看 | 亚洲91av| 免费的黄色的网站 | 91精品国 | 国产精品久久久久久麻豆一区 | 久久久午夜影院 | 高清视频一区 | 日韩精品一卡 | 久久夜色精品国产亚洲aⅴ 91chinesexxx | 天天草天天色 | 亚洲成av人片在线观看 | 91福利小视频 | 久久一视频 | 亚洲欧洲美洲av | a亚洲视频 | 一级特黄av | 五月婷婷丁香色 | 欧洲精品视频一区 | 婷婷激情网站 | 成人理论电影 | 亚洲天天看 | 日韩免费av在线 | ,午夜性刺激免费看视频 | 18性欧美xxxⅹ性满足 | 亚洲区精品| 波多野结依在线观看 | 欧美老女人xx | 欧美成人区 | 99综合电影在线视频 | 人人操日日干 | 亚洲一区免费在线 | 色婷婷在线视频 | 色婷婷激情电影 | 国产91小视频| 91干干干| 青青河边草手机免费 | 6699私人影院 | 99在线观看视频网站 | 亚洲精品视频 | 色综合天天色综合 | av免费网页| 色国产在线 | 国产精品99蜜臀久久不卡二区 | 深爱激情五月婷婷 | 日韩欧美视频在线免费观看 | 亚洲精品视频在线观看免费 | 国产精品久久久久久超碰 | 亚洲最新视频在线播放 | 亚洲精品国产自产拍在线观看 | 精品在线看 | 国内久久精品 | 久久精品视频国产 | 91麻豆网站 | 天天色天天操综合 | 日日操天天射 | 国产精品毛片一区二区 | 一级a性色生活片久久毛片波多野 | 欧美精品在线观看免费 | 不卡电影一区二区三区 | 在线小视频你懂得 | 2019天天干天天色 | 中文字幕在线播放日韩 | 91在线播放国产 | 日本中文在线观看 | 欧美性一级观看 | 国产一级片一区二区三区 | 色a在线观看 | 欧美精品做受xxx性少妇 | 精品视频成人 | 久久精品直播 | 中文字幕一区二区三区在线视频 | 亚洲一级片免费观看 | 狠狠色丁香婷婷综合欧美 | 色偷偷男人的天堂av | 久久久久久久久网站 | 日韩最新在线视频 | 国内三级在线观看 | 夜又临在线观看 | 日本中文字幕高清 | 在线观看日韩精品 | 亚洲黄色av一区 | 国产精品乱码久久久 | 91在线看黄 | 一级黄色大片在线观看 | 欧美国产日韩一区二区三区 | 日韩精品一区二区三区在线视频 | 91九色蝌蚪 | 亚洲1区在线 | 国产精品入口66mio女同 | 国产精华国产精品 | 中文字幕亚洲欧美日韩 | 国产精品一区二区av日韩在线 | 五月天视频网站 | 国产91精品高清一区二区三区 | 久久99精品久久久久久久久久久久 | 天天干天天干天天射 | 99热在线免费观看 | 黄色成人免费电影 | 在线观看视频一区二区三区 | 色人久久 | 日韩超碰在线 | 制服丝袜在线91 | www久久99 | 99久久99久久精品免费 | 成人一级 | 国产字幕av | 日本公妇在线观看高清 | 天天射天天爽 | 日日综合网 | 91精品国产三级a在线观看 | 成av人电影 | 99精品在线免费视频 | 午夜 在线| 亚洲免费婷婷 | 欧美激情视频一区 | 免费午夜视频在线观看 | 少妇bbw撒尿 | 国产精品久免费的黄网站 | 日韩中文字幕免费在线观看 | av官网 | 亚洲欧美日韩国产精品一区午夜 | 青青河边草免费观看 | 免费中文字幕在线观看 | 91成人免费观看视频 | 国产精品国内免费一区二区三区 | 91欧美国产 | 蜜臀av夜夜澡人人爽人人 | a午夜在线 | 91福利社区在线观看 | 天天综合网久久综合网 | 97人人射 | 香蕉视频在线免费看 | 色视频在线看 | 久久国产视屏 | 日韩视频在线不卡 | 久久婷婷开心 | 五月婷香 | 国产精品av在线 | 成人a毛片| 毛片在线网 | 白丝av在线 | 久久久污| 香蕉免费| 久久综合色影院 | 成人国产精品久久久春色 | 国产香蕉av| 欧美激情视频一二区 | 午夜久久久久久久久久久 | 天堂在线一区 | 免费情趣视频 | 亚洲国产片色 | 久久久这里有精品 | 在线观看成人小视频 | 精品一区精品二区高清 | 有码中文字幕 | 性色av免费在线观看 | 成人中文字幕在线观看 | 黄色福利网站 | 中文字幕高清在线 | 婷婷六月丁香激情 | 天堂在线一区二区三区 | 91在线在线观看 | 国产欧美日韩精品一区二区免费 | 五月婷久 | 黄网站a | 99精品国产高清在线观看 | 中文字幕字幕中文 | 国产精品久久久久久一区二区 | 天天干天天干天天射 | 麻豆手机在线 | 久久人人爽爽人人爽人人片av | 国产在线国偷精品产拍免费yy | av中文天堂 | 在线观看激情av | 狠狠色丁香婷婷综合最新地址 | 亚洲精品在线播放视频 | 国产中文视频 | 成人高清在线 | 韩国在线一区二区 | 你操综合| 欧美久久久一区二区三区 | 九九免费观看视频 | 日韩欧美综合在线视频 | 在线免费观看国产黄色 | 国产91小视频 | 成年人视频免费在线 | 国产成人在线免费观看 | 午夜在线观看一区 | av网站在线观看免费 | 五月天精品视频 | 欧美性色19p| 国产精品入口麻豆 | 制服丝袜一区二区 | 欧美一区免费观看 | 日韩精品久久一区二区三区 | 99精品视频在线 | 欧美天天综合 | 免费福利视频网站 | 久久99精品久久久久久久久久久久 | 国产精品私拍 | 久久久午夜视频 | 国产永久免费高清在线观看视频 | 国产日韩在线播放 | 五月天久久婷婷 | 片网站 | 午夜视频导航 | 黄色成人毛片 | 国产免费资源 | 国产精品免费视频网站 | 最近2019好看的中文字幕免费 | 欧美日韩精品在线免费观看 | 中文字幕在线视频第一页 | 成人av一区二区兰花在线播放 | 久久国产精品视频观看 | 免费福利片 | 色老板在线视频 | 欧美日韩国产精品一区二区三区 | 97国产精品| 青草视频在线免费 | 日韩精品一区二区三区三炮视频 | 国产婷婷久久 | 久久久av电影 | 一区二区三区久久精品 | 美女很黄免费网站 | 欧美性黄网官网 | 国产精品亚洲片夜色在线 | 久久国产精品免费观看 | 天天操天天添天天吹 | 久久综合久久综合九色 | 国产亚洲精品久久久久久电影 | 少妇搡bbbb搡bbb搡忠贞 | 免费日韩高清 | www.com久久久 | 亚洲精品一区二区在线观看 | 午夜成人免费影院 | 麻花豆传媒mv在线观看网站 | 国际精品久久 | 男女拍拍免费视频 | 激情五月播播久久久精品 | av成人在线电影 | 91中文在线视频 | 国产成人福利在线观看 | 91精品视频网站 | 久久电影网站中文字幕 | 999久久久久久久久6666 | 97在线观看免费高清完整版在线观看 | 国产成人精品久久亚洲高清不卡 | 亚洲成人av片在线观看 | 免费在线精品视频 | www.人人草 | 操久久免费视频 | 欧美精品乱码99久久影院 | 日韩高清免费电影 | 三级大片网站 | 国产成人精品网站 | 国产精品白浆视频 | 中文字幕丝袜一区二区 | 国产精品免费久久 | 日韩精品不卡 | 日韩在线精品 | 97香蕉超级碰碰久久免费软件 | 国产精品一区二区三区免费看 | 国产精品一区在线 | 成人毛片100免费观看 | av先锋影音少妇 | 粉嫩av一区二区三区四区在线观看 | 久久久久久国产精品免费 | 国产精品免费久久久 | 在线久草视频 | 久久99国产精品二区护士 | 91久久爱热色涩涩 | 91免费视频网站在线观看 | 91精品国产高清自在线观看 | 国产黄色在线看 | 深夜激情影院 | 国产黄色电影 | 成人在线观看免费 | 又黄又刺激的视频 | 成年人免费看片网站 | 不卡av免费在线观看 | 午夜av在线播放 | 亚洲精品tv久久久久久久久久 | 亚洲精品美女在线观看播放 | 精品专区| 在线免费观看视频一区二区三区 | 又黄又刺激视频 | 日本久久高清视频 | 中文视频在线 | 丁香婷婷射 | 精品国产一二三四区 | 日韩欧美国产成人 | 黄色软件视频网站 | 国产成人福利片 | 日韩欧美视频免费观看 | 中文字幕一区二区三区在线观看 | 婷婷在线五月 | 一本一本久久a久久精品综合小说 | 超碰97人人干 | 波多野结衣在线播放一区 | 激情五月亚洲 | 不卡av在线免费观看 | 精品久久久免费 | 日韩欧美69 | 久久99精品国产99久久6尤 | 爱射综合 | 久久国产电影院 | 这里只有精品视频在线 | 亚洲欧美激情精品一区二区 | 中文乱幕日产无线码1区 | 五月婷婷在线播放 | 日韩高清免费在线观看 | 久久精品一区二区三区四区 | 久久国产精彩视频 | 99色免费 | 最近久乱中文字幕 | 依人成人综合网 | 国产一区二区高清 | 国产精品久久久久久久久久久久午夜 | 久久精品高清 | 天天色婷婷 | av网址最新 | 日韩中文字幕免费在线观看 | 九九热在线观看视频 | 在线蜜桃视频 | 久久经典视频 | 亚洲精品国产精品国自 | 最新中文字幕 | 久草在线手机观看 | 狠狠插天天干 | www最近高清中文国语在线观看 | av中文字幕第一页 | 99亚洲视频 | 黄色免费高清视频 | 久久久精品在线观看 | 国产亚洲精品无 | 伊人中文在线 | 九九九热视频 | 天堂av网址 | 欧美极品少妇xxxxⅹ欧美极品少妇xxxx亚洲精品 | 成人av电影免费 | 国产精品3区| 日韩欧美一区二区三区视频 | 国产一级在线 | 婷婷久久综合九色综合 | 亚洲欧美视频在线观看 | 国产99久 | 成年人视频免费在线播放 | 一区二区三区在线免费观看 | 天堂av高清| 一区二区三区精品久久久 | 国产中文字幕一区二区 | 九九99视频 | 91丨九色丨国产在线观看 | 99热在线国产精品 | 亚洲精品在线电影 | 免费a网址 | 日韩激情小视频 | www.com久久久 | 在线国产高清 | 久久综合久久久 | 亚洲欧美乱综合图片区小说区 | 一级黄色片在线免费观看 | 播五月婷婷| 黄色大片视频网站 | 亚洲欧美va| 成人在线视频一区 | 日韩欧美在线播放 | 玖玖爱在线观看 | 久久好看| 成人wwwxxx视频 | 91av99| 综合天堂av久久久久久久 | 婷婷亚洲五月色综合 | 免费看成人a| 亚洲激情一区二区三区 | 成人av电影免费观看 | 伊人成人精品 | 欧美一级免费高清 | 午夜精品一区二区三区在线视频 | 中文一二区 | 免费一级片在线 | 亚洲特级片 | 精品欧美一区二区在线观看 | 亚洲精品乱码久久久久久按摩 | 日韩精品一区二区三区中文字幕 | av在线电影网站 | 国产精品高清在线观看 | 正在播放日韩 | 黄色影院在线免费观看 | 日韩电影一区二区三区在线观看 | 精品欧美日韩 | 国产亚洲精品久久久久久网站 | 右手影院亚洲欧美 | 五月天中文字幕mv在线 | 日韩三级一区 | 免费中文字幕 | 精品字幕| 91成人免费观看视频 | 黄色成品视频 | 久久99婷婷| 狠狠躁日日躁夜夜躁av | 亚洲精品在线视频 | 日b视频国产 | 又黄又爽的视频在线观看网站 | 四虎影视国产精品免费久久 | 天天摸夜夜添 | 国产九九九视频 | 欧美在线观看小视频 | 黄色电影网站在线观看 | 国产三级国产精品国产专区50 | 色99久久| 日韩精品免费 | 最近中文字幕在线中文高清版 | 日精品在线观看 | 成人免费看片网址 | 黄色三级免费观看 | 国产丝袜制服在线 | 青草视频在线 | 中文字幕888| 欧美日韩在线视频一区 | 亚洲免费成人 | 最近中文字幕免费观看 | 国产分类视频 | 白丝av免费观看 | 国产 在线观看 | 中文字幕超清在线免费 | 波多野结衣精品在线 | 毛片.com| 亚洲国产日韩欧美在线 | 欧美色综合天天久久综合精品 | 欧美狠狠色 | 激情在线网址 | .国产精品成人自产拍在线观看6 | 欧美一进一出抽搐大尺度视频 | 美女视频又黄又免费 | 国产999精品久久久影片官网 | 中文在线免费观看 | 精品久久久免费 | 嫩模bbw搡bbbb搡bbbb | 日本在线观看黄色 | 国产资源在线视频 | 久久精久久精 | 狂野欧美激情性xxxx | 国产中文字幕第一页 | 91在线精品观看 | 欧美激情视频一二区 | 超碰人人舔 | 天天操操操操操操 | 91精品国产91久久久久久三级 | 午夜久久影视 | 国产九九热视频 | 操综合 | 国产美女无遮挡永久免费 | 蜜臀av性久久久久蜜臀aⅴ涩爱 | 久久综合久色欧美综合狠狠 | 久久久一本精品99久久精品66 | 午夜12点 | 亚洲在线视频观看 | 国产色拍| 婷婷综合网 | 国产免费观看高清完整版 | av片在线观看 | 国产在线视频导航 | 亚洲欧美999| 国产精品99久久久久久武松影视 | 97国产一区二区 | 91喷水| 精品免费视频 | 日韩av一区在线观看 | 久久精品人人做人人综合老师 | 免费h在线观看 | 国产一级在线视频 | 免费一级日韩欧美性大片 | 日韩一级成人av | 狠狠干干 | 国产日韩在线播放 | 精品综合久久 | 日韩3区 | 久久综合久久综合这里只有精品 | 91久久精品一区二区三区 | 五月婷婷视频在线 | 日日摸日日添夜夜爽97 | 在线观看福利网站 | 亚洲网久久 | 婷婷久久网站 | 超碰在线网 | 国产精品日韩欧美一区二区 | 亚洲午夜av电影 | 一区二区免费不卡在线 | 麻豆精品在线 | 天天综合中文 | 91精品久久香蕉国产线看观看 | 欧美成人999| 久久久免费看视频 | 韩日视频在线 | 久久精品国产免费看久久精品 | 成年人免费观看在线视频 | 日本在线观看中文字幕 | 91久久国产自产拍夜夜嗨 | 天天干天天看 | 成人免费在线播放视频 | av三区在线 | av在线在线 | 69热国产视频 | av电影久久 | 久久亚洲综合色 | av在线播放亚洲 | 91九色视频在线观看 | 天天综合色天天综合 | 久插视频 | 中文字幕在线影院 | 全黄色一级片 | 狠狠干美女 | 在线视频欧美亚洲 | 亚洲黄色网络 | 成年人国产视频 | 国产精品成人一区二区三区 | 日韩另类在线 | 天堂网一区二区三区 | 青青草国产在线 | 亚洲精品视频第一页 | 天天插天天色 | 久久视频一区二区 | 亚洲精品视频 | 国产成人av网址 | 51久久夜色精品国产麻豆 | 超碰在线99 | 国产一区在线免费观看 | 一区二区不卡高清 | 婷婷精品进入 | 日韩av免费一区二区 | 成人免费视频播放 | 色妞色视频一区二区三区四区 | 成人中文字幕在线 | 国产精品久久久久av | 婷婷播播网 | 精品国模一区二区三区 | 久久国产精品一区二区三区 | 国产精品久久久网站 | 亚洲高清在线精品 | 国产欧美久久久精品影院 | 91插插影库 | 色综合天天视频在线观看 | 成人在线网站观看 | 久久精品欧美 | av网站免费看 | 深爱婷婷 | 手机在线看永久av片免费 | 久久不射网站 | 日韩欧美大片免费观看 | 黄色av网站在线免费观看 | www免费视频com━ | a级黄色片视频 | 国产91探花 | www黄色com| wwwww.国产 | 久久久精品 一区二区三区 国产99视频在线观看 | 成人在线视频免费观看 | 六月丁香社区 | 久草五月| 91成人免费 | 人人操日日干 |