7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

目錄

0. 前言

今天是開心的一天造烁。因為我們終于來到了系列學(xué)習(xí)的最后一節(jié)。我相信你喜歡之前的課程躏精,并從中學(xué)到了許多。

1. Lab 32 — 讓項目有組織性

這個實驗確切地講無關(guān)任何新的功能鹦肿。它只是使項目更有結(jié)構(gòu)性和系統(tǒng)化矗烛。

第一步:創(chuàng)建解決方案文件夾

右擊解決方案,然后選擇 Add -> New Solution Folder箩溃。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

將文件夾的名稱改為「View And Controller」〔t吃,F(xiàn)在重復(fù)這個步驟,創(chuàng)建多個相似的文件夾涣旨,分別命名為「Model」歪架,「ViewModel」,「Data Access Layer」霹陡。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

第二步:創(chuàng)建數(shù)據(jù)訪問層項目

右擊 Data Access Layer 文件夾和蚪,然后創(chuàng)建一個新的類庫項目,命名為「DataAccessLayer」烹棉。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

第三步:創(chuàng)建業(yè)務(wù)層和業(yè)務(wù)實體層項目

在 Model 文件夾下創(chuàng)建兩個類庫項目攒霹,分別命名為「BusinessLayer」 和 「BusinessEntities」。

第四步:創(chuàng)建 ViewModel 項目

在 ViewModel 文件夾下創(chuàng)建一個新的類庫項目浆洗,命名為「ViewModel」催束。

第五步:添加引用

首先右擊每一個項目,然后選擇 Add -> Reference伏社,選擇如下引用抠刺。

  1. 對于 DataAccessLayer,選擇 BusinessEntities洛口。

  2. 對于 BusinessLayer矫付,選擇 DataAccessLayer 和 BusinessEntities凯沪。

  3. 對于 MVC Web Application第焰,選擇 BusinessLayer,BusinessEntities 和 ViewModel妨马。

  4. 對于 BusinessEntities挺举,選擇 System.ComponentModel.DataAnnotations杀赢。

第六步:設(shè)置項目

  1. 從 MVC 項目中的 DataAccessLayer 文件夾中復(fù)制 SalesERPDAL.cs 文件到新創(chuàng)建的 Data Access Layer 類庫項目中。
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
  1. 從 MVC 項目中移除 DataAccessLayer 文件夾湘纵。

  2. 從 MVC 項目的 Model 文件夾下復(fù)制 Employee.cs脂崔,UserDetails.cs 和 UserStatus.cs 文件到新創(chuàng)建的 BusinessEntities 類庫項目中。

  3. 從 MVC 項目的 Model 文件下復(fù)制 EmployeeBusinessLayer.cs 文件到新創(chuàng)建的 BusinessLayer 類庫項目中梧喷。

  4. 從 MVC 項目中移除 Model 文件夾砌左。

  5. 從 MVC 項目的 ViewModels 文件夾下復(fù)制所有類到新創(chuàng)建的 View Model 類庫項目中。

  6. 從 MVC 項目中移除 ViewModels 文件夾铺敌。

  7. 將 MVC 項目汇歹,即 WebApplication1 移到「View And Controller」解決方案文件夾。

第七步:Build 項目

選擇菜單欄的 Build -> Build Solution偿凭。你將會得到如下的錯誤信息产弹。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

第八步:解決錯誤

  1. 向 ViewModel 項目中添加 System.Web 引用。

  2. 在 DataAccessLayer 和 BusinessLayer 項目中運用 Nuget Manager弯囊,安裝 Entity Framework痰哨。(如果你對 Nuget Manager 感到困惑,建議你看一下第 3 天的課程)

注:業(yè)務(wù)層需要引用 Entity Framework 是因為 BusinessLayer 直接與 DataAccessLayer 相關(guān)聯(lián)匾嘱。在一個正確的架構(gòu)中斤斧,業(yè)務(wù)層不應(yīng)該與數(shù)據(jù)訪問層直接關(guān)聯(lián)。我們可以通過 Repository 來完成這個目的霎烙。

  1. 從 MVC 項目中移除 EntityFramework折欠。

步驟如下。

  • 右擊 MVC 項目吼过,選擇「Manage Nuge Packages」選項锐秦。

  • 在左側(cè)區(qū)域的「Manage Nuget Packages」對話框下選擇「Installed Packages」。

  • 右擊區(qū)域?qū)@示之前下載過的所有包盗忱。選擇 EntityFramework酱床,然后點擊 Uninstall。

第九步: Build 解決方案

你將會看到如下錯誤趟佃。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

第十步:解決錯誤

現(xiàn)在扇谣,我們在 MVC 項目中既沒有 SalesERPDAL 引用,也沒有 Entity Framework 引用闲昭。添加這些引用不是一個最佳實踐罐寨。作為最佳實踐,控制器不應(yīng)該與數(shù)據(jù)訪問層直接相關(guān)聯(lián)序矩。

  • 在 DataAccessLayer 項目中創(chuàng)建一個新的類鸯绿,命名為 DatabaseSettings,它有一個靜態(tài)方法,命名為 SetDatabase瓶蝴。
    using System.Data.Entity;
    using WebApplication1.DataAccessLayer;
    namespace DataAccessLayer
    {
        public class DatabaseSettings
        {
            public static void SetDatabase()
            {
                Database.SetInitializer(new    DropCreateDatabaseIfModelChanges<SalesERPDAL>());<saleserpdal>
            }
        }   
    }
  • 在 BusinessLayer 項目中創(chuàng)建一個新的類毒返,命名為 BusinessSettings,它有一個靜態(tài)方法舷手,命名為 Setbusiness拧簸。
using DataAccessLayer;

namespace BusinessLayer
{
    public class BusinessSettings
    {
        public static void SetBusiness()
        {
            DatabaseSettings.SetDatabase();
        }
    }
}
  • 在 Global.asax 中運用語句來解決錯誤,并且移除 Database.SetInitializer 語句男窟。觸發(fā) BusinessSettings.SetBusiness 函數(shù)盆赤。
using BusinessLayer;
...
...
BundleConfig.RegisterBundles(BundleTable.Bundles);
BusinessSettings.SetBusiness();

再次 Bulid 應(yīng)用,這一次將會成功歉眷。

Lab 32 的 Q&A

什么是解決方案文件夾弟劲?

Solution 文件夾只是邏輯文件夾。實際上姥芥,它不會在物理硬盤上被創(chuàng)建兔乞。它的目的只是為了讓解決方案更加系統(tǒng)化。

2. Lab 33 — 創(chuàng)建單頁應(yīng)用 — Part 1 — 設(shè)置

現(xiàn)在凉唐,我們將不會對已存在的控制器和視圖做出改變庸追。我們將會為此實驗創(chuàng)建一個全新的控制器和視圖。做這些步驟的理由是:

  1. 使已存在的項目不受影響台囱,因此你可以將之前的版本和單一頁版本進行對比淡溯,更好地學(xué)習(xí)。

  2. 實現(xiàn)并理解 ASP.NET MVC 中的另一個概念簿训,即 Areas咱娶。

正如我所說的,我們將會創(chuàng)建新的控制器强品,視圖和視圖模型膘侮。

只有以下組件會被重復(fù)利用。

  • 已存在的 Business Layer的榛。

  • 已存在的 Data Access Layer琼了。

  • 已存在的 Business Entities。

  • Authentication 和異常過濾器夫晌。

  • FooterViewModel雕薪。

  • Footer.cshtml。

第一步:創(chuàng)建一個新的 Area

右擊項目晓淀,然后選擇 Add -> Area所袁。一個對話框?qū)棾觯斎朊Q為 SPA凶掰,然后點擊 Add燥爷。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

它將在項目中創(chuàng)建一個新的文件夾架構(gòu)蜈亩,如下所示。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

很明顯局劲,我們并不需要 Area 下的 Model 文件夾勺拣,刪除即可奶赠。

什么是 Areas鱼填?

Areas 是實現(xiàn) ASP.NET MVC 項目模塊化的一種簡單方式。

每一個項目都由多個模塊組成毅戈。例如:賬戶模塊苹丸,顧客關(guān)系模塊,付款模塊苇经,等等赘理。

在傳統(tǒng)的應(yīng)用開發(fā)風(fēng)格中,我們經(jīng)常使用「Folders」來達到這個目的扇单。我們在一個單獨項目中創(chuàng)建多個文件夾商模。每一個文件夾代表一個模塊。我們會把各自模塊的文件放在各自的文件夾中蜘澜。

當使用 ASP.NET MVC 時施流,這種自定義文件夾將會遇到大問題。

讓我們來討論下在 ASP.NET MVC 中鄙信,運用簡單的文件夾來實現(xiàn)模塊瞪醋。

  • DataAccessLayer,BusinessLayer装诡,BusinessEntities 和 ViewModels 不會產(chǎn)生任何問題银受。他們僅僅是簡單的類,所以可以被放置到任意地方鸦采。

  • 我們不能將控制器隨意放置宾巍。它必須放置在 Controller 文件夾下。但是這不會成為一個大問題渔伯,因為從 MVC 4 開始蜀漆,控制器的位置限制就已經(jīng)被舍棄了。現(xiàn)在我們可以將其放置到任意想要放置的地方咱旱。

  • 不幸的是确丢,對于視圖是不可行的。所有的視圖都必須放置在「/Views/ControllerName」或者「/Views/Shared」文件夾下吐限。

第二步:創(chuàng)建所需的 ViewModels

在 ViewModel 類庫項目中創(chuàng)建一個新的文件夾鲜侥,命名為 SPA,然后創(chuàng)建一個 ViewModel诸典,命名為 MainViewModel描函。

using WebApplication1.ViewModels;
namespace WebApplication1.ViewModels.SPA
{
    public class MainViewModel
    {
        public string UserName { get; set; }
        public FooterViewModel FooterData { get; set; }//New Property
    }
}

第三步:創(chuàng)建 Index 行為方法

在 MainController 中引用如下語句。

using WebApplication1.ViewModels.SPA;
using OldViewModel=WebApplication1.ViewModels;

在 MainController 中創(chuàng)建一個新的行為方法,命名為 Index舀寓。

public ActionResult Index()
{
    MainViewModel v = new MainViewModel();
    v.UserName = User.Identity.Name;
    v.FooterData = new OldViewModel.FooterViewModel();
    v.FooterData.CompanyName = "StepByStepSchools";//Can be set to dynamic value
    v.FooterData.Year = DateTime.Now.Year.ToString();
    return View("Index", v);
}

正如你所見胆数,為 WebApplication1.ViewModels 命名空間增加了一個 OldViewModels 別名。現(xiàn)在互墓,我們可以使用 OldViewModel.ClassName必尼,而不是 WebApplication1.ViewModels.ClassName。

不指定別名會導(dǎo)致歧義錯誤篡撵。在命名空間 WebApplication1.ViewModels.SPA 和 WebApplication1.ViewModels 中判莉,存在相似的類。

第四步:創(chuàng)建 Index 視圖

創(chuàng)建一個與上述 Index 方法相聯(lián)系的視圖育谬。

@using WebApplication1.ViewModels.SPA
@model MainViewModel
<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
<title>Employee Single Page Application</title>

第五步:執(zhí)行并測試應(yīng)用

按下 F5券盅,然后執(zhí)行應(yīng)用。完成登錄操作膛檀,然后導(dǎo)航到 MainController 中的 Index 行為锰镀。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

Lab 33 的 Q&A

為什么在控制器名稱之前需要 SPA 關(guān)鍵字?

當我們向 ASP.NET MVC 中添加 Area 時咖刃,Visual Studio 就會創(chuàng)建一個文件泳炉,命名為 [AreaName]AreaRegistration.cs,它包含一個類僵缺,這個類定義了 AreaName 屬性和 RegisterArea 方法胡桃,該方法用于為 Area 注冊路由信息。

在我們的例子中磕潮,你可以發(fā)現(xiàn)名稱為 SpaAreaRegistration.cs 文件翠胰,它被放置在「~/Areas/Spa」文件夾下。SpaAreaRegistration 類的 RegisterArea 方法包含如下代碼自脯。

context.MapRoute(
                "SPA_default",
                "SPA/{controller}/{action}/{id}",
                new { action = "Index", id = UrlParameter.Optional }
);

這就解釋了為什么我們需要在控制器名稱前添加 SPA 關(guān)鍵字之景。

SpaAreaRegistration 類中的 RegisterArea 方法如何被觸發(fā)?

打開 Global.asax 文件膏潮,Application_Start 的第一行如下锻狗。

AreaRegistration.RegisterAllAreas();

RegisterAllAreas 方法查找到應(yīng)用中所有來自于 AreaRegistration 的類型,并調(diào)用它們的每一個 RegisterArea 方法焕参。

我們可以不使用 SPA 來觸發(fā) MainController 動作嗎轻纪?

讓我們簡化一下問題:URL 為「localhost:8870/Main/Index」還會起作用嗎?

答案是肯定的叠纷。AreaRegistration 類創(chuàng)建一個新的路由刻帚,但是不會刪除其它路由。路由在 RouteConfig 類中定義涩嚣,仍然起作用崇众。正如我之前所說的那樣掂僵,控制器的位置沒有限制。因此它仍能起作用顷歌,但是輸出不會被正確地呈現(xiàn)锰蓬,因為它將不能查找到視圖。我建議你執(zhí)行一下應(yīng)用眯漩,試一試芹扭。

3. Lab 34 — 創(chuàng)建單頁應(yīng)用 — Part 2 — 展示 Employees

第一步:為展示已存在的 Employees 創(chuàng)建 ViewModel

在 ViewModel 類庫的 SPA 文件夾下創(chuàng)建兩個新的 ViewModel 類,命名為 EmployeeViewModel 和 EmployeeListViewModel坤塞。

namespace WebApplication1.ViewModels.SPA
{
    public class EmployeeViewModel
    {
        public string EmployeeName { get; set; }
        public string Salary { get; set; }
        public string SalaryColor { get; set; }
    }
}
namespace WebApplication1.ViewModels.SPA
{
    public class EmployeeListViewModel
    {
        public List<employeeviewmodel> Employees { get; set; }
    }
}

注:兩個 ViewModel 實際上都是 Non-Spa 應(yīng)用的 ViewModel 復(fù)制品冯勉。唯一的區(qū)別是不需要 BaseViewModel 了澈蚌。

第二步:創(chuàng)建 EmployeeList Index

在 MainController 下創(chuàng)建一個新的行為方法摹芙,命名為 EmployeeList。

public ActionResult EmployeeList()
{
    EmployeeListViewModel employeeListViewModel = new EmployeeListViewModel();
    EmployeeBusinessLayer empBal = new EmployeeBusinessLayer();
    List<employee> employees = empBal.GetEmployees();

    List<employeeviewmodel> empViewModels = new List<employeeviewmodel>();

    foreach (Employee emp in employees)
    {
        EmployeeViewModel empViewModel = new EmployeeViewModel();
        empViewModel.EmployeeName = emp.FirstName + " " + emp.LastName;
        empViewModel.Salary = emp.Salary.Value.ToString("C");
        if (emp.Salary > 15000)
        {
            empViewModel.SalaryColor = "yellow";
        }
        else
        {
            empViewModel.SalaryColor = "green";
        }
        empViewModels.Add(empViewModel);
    }
    employeeListViewModel.Employees = empViewModels;
    return View("EmployeeList", employeeListViewModel);
}

注:HeaderFooterFilter 不再需要宛瞄。

第三步:創(chuàng)建 AddNewLink 分部視圖

這次我們不能運用之前的 AddNewLink 分部視圖浮禾,因為之前的標簽設(shè)定會導(dǎo)致全部的刷新。我們的目標是創(chuàng)建一個「Single Page Application」份汗,因此我們不應(yīng)該有全部的刷新盈电。

在「~/Areas/Spa/Views/Main」文件夾下創(chuàng)建一個新的分部視圖,命名為 AddNewLink.cshtml杯活。

<a href="#" onclick="OpenAddNew();">Add New</a>

第四步:創(chuàng)建 AddNewLink 行為方法

在 MainController 下創(chuàng)建一個新的行為方法匆帚,命名為 GetAddNewLink。

public ActionResult GetAddNewLink()
{
if (Convert.ToBoolean(Session["IsAdmin"]))
{
return PartialView("AddNewLink");
}
else
{
return new EmptyResult();
}
}

第五步:創(chuàng)建 EmployeeList 視圖

在「~/Areas/Spa/Views/Main」文件夾下創(chuàng)建一個新的分部視圖旁钧,命名為 EmployeeList吸重。

@using WebApplication1.ViewModels.SPA
@model EmployeeListViewModel
<div>
    @{
        Html.RenderAction("GetAddNewLink");
    }

    <table border="1" id="EmployeeTable">
        <tr>
<th>Employee Name</th>

第六步:設(shè)置 EmployeeList 為初始頁

在「~/Areas/Spa/Views/Main」文件夾下打開 Index.cshtml 文件,在 DivOptions div 中包含 EmployeeList 行為結(jié)果歪今。

第七步:執(zhí)行并測試

按下 F5嚎幸,并執(zhí)行應(yīng)用。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

4. Lab 35 — 創(chuàng)建單頁應(yīng)用 — Part 3 — 創(chuàng)建 Employee

第一步:創(chuàng)建 AddNew 視圖模型

在 ViewModel 類庫項目的 SPA 文件夾下創(chuàng)建一個新的視圖模型寄猩,命名為 CreateEmployeeViewModel嫉晶。

namespace WebApplication1.ViewModels.SPA
{
    public class CreateEmployeeViewModel
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Salary { get; set; }
    }
}

第二步:創(chuàng)建 AddNew 行為方法

在 MainController 中引用如下聲明。

using WebApplication1.Filters;

在 MainController 下創(chuàng)建 AddNew 行為方法如下田篇。

[AdminFilter]
public ActionResult AddNew()
{
    CreateEmployeeViewModel v = new CreateEmployeeViewModel();
    return PartialView("CreateEmployee", v);
}

第三步:創(chuàng)建 CreateEmployee 分部視圖

在「~/Areas/Spa/Views/Main」文件夾下創(chuàng)建一個新的分部視圖替废,命名為 CreateEmployee。

@using WebApplication1.ViewModels.SPA
@model CreateEmployeeViewModel
<div>
    <table>
        <tr>
            <td>
                First Name:
</td>

第四步:引入 JQuery UI

右擊項目泊柬,然后選擇「Manage Nuget Manager」椎镣。搜索「JQuery UI」。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

安裝 JQuery UI彬呻。它將會向項目中增加幾個 JavaScript.js 和 Stylesheet.css 文件衣陶。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

第五步:包含 JQuery UI

打開「~/Areas/Spa/Views/Main/Index.cshtml」文件柄瑰,然后包含 JQuery.js,JQueryUI.js 和 All.css 文件剪况。這些文件作為 JQuery UI 的一部分被 Nuget Manager 所添加教沾。

<head>
<meta name="viewport" content="width=device-width" />
<script src="~/Scripts/jquery-1.8.0.js"></script>
<script src="~/Scripts/jquery-ui-1.11.4.js"></script>
<title>Employee Single Page Application</title>
<link href="~/Content/themes/base/all.css" rel="stylesheet" />
...

第六步:實現(xiàn) OpenAddNew 函數(shù)

在「~/Areas/Spa/Views/Main/Index.cshtml」下創(chuàng)建一個新的 JavaScript 函數(shù),命名為 OpenAddNew译断。

<script>
    function OpenAddNew() {
        $.get("/SPA/Main/AddNew").then
            (
                function (r) {
                    $("<div id='DivCreateEmployee'></div>").html(r).
                        dialog({
                            width: 'auto', height: 'auto', modal: true, title: "Create New Employee",
                            close: function () {
                                $('#DivCreateEmployee').remove();
                            }
                        });
                }
            );
    }
</script>

第七步:執(zhí)行并測試

按下 F5授翻,執(zhí)行應(yīng)用。

完成登錄操作孙咪,然后導(dǎo)航到 MainController 的 Index 動作堪唐。然后點擊 Add New 超鏈接。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

第八步:創(chuàng)建 ResetForm 函數(shù)

打開 CreateEmployee.cshtml 視圖翎蹈。在頂部創(chuàng)建 ResetForm 函數(shù)淮菠。

@model CreateEmployeeViewModel
<script>
    function ResetForm() {
        document.getElementById('TxtFName').value = "";
        document.getElementById('TxtLName').value = "";
        document.getElementById('TxtSalary').value = "";
    }
</script>

第九步:創(chuàng)建 CancelSave 函數(shù)

打開 CreateEmployee.cshtml 視圖。在頂部創(chuàng)建 CancelSave 函數(shù)荤堪。

document.getElementById('TxtSalary').value = "";
    }
    function CancelSave() {
        $('#DivCreateEmployee').dialog('close');
}

接下來是什么合陵?

在我們繼續(xù)第十步之前,我們先要了解接下來應(yīng)該做什么澄阳。

  • 終端用戶點擊 Save Employee 按鈕拥知。

  • 在客戶端對控件的值進行合法性驗證。

  • 如果所有的值都是合法的碎赢,就被傳輸?shù)椒?wù)器端低剔。

  • 在數(shù)據(jù)庫中存儲一個新的 Employee 記錄。

  • CreateEmployee 對話框關(guān)閉肮塞。

  • Grid 更新 Employee 記錄襟齿。

開始計劃

  • 認證

對于認證,我們可以運用在 Non-Spa 項目中使用過的認證 JavaScript 代碼峦嗤。

  • 保存 Employee

我們將會創(chuàng)建一個 MVC 行為方法蕊唐,用于保存 Employee,然后通過 JQuery Ajax 來觸發(fā)它烁设。

  • 將數(shù)據(jù)從客戶端傳輸?shù)椒?wù)器端

之前通過 Form 標簽和 Submit 按鈕能輕松并自動的處理這件事√胬妫現(xiàn)在我們不能使用該方法,因為它將會帶來全部的刷新装黑。取而代之的是副瀑,我們利用 JQuery Ajax 的方式,這種方式允許我們觸發(fā)服務(wù)器端的 MVC 行為方法恋谭,并且不會全部刷新頁面糠睡。

現(xiàn)在一個重要的問題是,如果調(diào)用是手動的疚颊,那么數(shù)據(jù)是如何通過 JavaScript 傳輸?shù)?MVC 行為方法中的狈孔。

找尋解決方案

理解問題

當你聽到數(shù)據(jù)這個詞時信认,首先映入你腦海的是 JavaScript,.NET均抽,還是其它技術(shù)呢嫁赏?

答案是變量。我們可以利用變量來承載臨時數(shù)據(jù)油挥,然后將其轉(zhuǎn)儲在持久性的倉庫中潦蝇,例如數(shù)據(jù)庫。

在我們的例子中深寥,我們用到了兩個技術(shù)攘乒,即 JavaScript 和 ASP.NET MVC。JavaScript 是一個技術(shù)惋鹅,ASP.NET MVC 是另一項技術(shù)则酝。

它們不能交換彼此的數(shù)據(jù),換句話說负饲,它們不能直接與彼此交換變量堤魁。你也許會想知道喂链,為什么它們不能返十?

它們都有變量。它們都支持數(shù)據(jù)格式椭微,例如 Float洞坑,Int,Char蝇率,那么它們?yōu)槭裁床荒軐⒆兞勘舜藗鬏斈兀?/p>

答案是迟杂,它們擁有變量,但是它們不同本慕。.NET 中的整型數(shù)據(jù)類型沒有要求和其它技術(shù)的整型數(shù)據(jù)類型相同排拷。它們也許在大小上不同,或者可能是其它屬性锅尘。

舉一個現(xiàn)實中的有趣例子监氢。每一個人都擁有腿,手藤违,眼睛等浪腐。同樣,小狗也共同的東西顿乒。它們相同嗎议街?顯而易見的是,它們不同璧榄。人類的眼睛不能被小狗的眼睛所替代特漩,反之亦然吧雹。

在所有的技術(shù)中,變量的概念是相似的涂身,但是它們卻不相同吮炕。再一次重復(fù)這句話,「.NET 中的整型和 Java 中的整型是有所區(qū)別的」访得。

解決方案 — 一個統(tǒng)一的數(shù)據(jù)類型

行業(yè)已經(jīng)意識到了這個問題龙亲,因此考慮使用在所有技術(shù)中相同的數(shù)據(jù)類型。

String 數(shù)據(jù)類型正是如此悍抑,并且可以承載任何數(shù)據(jù)鳄炉。

  • 我們可以將整型數(shù)據(jù)轉(zhuǎn)換為字符串類型,然后將其存儲在字符串變量中搜骡。

  • 我們可以將浮點型數(shù)據(jù)轉(zhuǎn)換為字符串類型拂盯,然后將其存儲在字符串變量中。

  • 任何數(shù)據(jù)類型都可以存儲在字符串變量中记靡。

終極的解決方案是「每一次從技術(shù)1傳輸數(shù)據(jù)到技術(shù)2谈竿,技術(shù)1需要將數(shù)據(jù)轉(zhuǎn)換為字符串類型,然后傳輸給技術(shù)2摸吠,因為這樣可以100%確保技術(shù)2能夠理解該字符串」空凸。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

目前在行業(yè)中已經(jīng)形成標準。

問題 — 如何處理復(fù)雜的數(shù)據(jù)寸痢?

如果這種字符串傳輸方式成為標準呀洲,那么復(fù)雜的數(shù)據(jù)如何傳輸呢?如果我們想將 Employee 的信息從一個技術(shù)傳輸給另一個呢啼止?

在 .NET 中道逗,「類和對象」被用于呈現(xiàn)復(fù)雜數(shù)據(jù)∠追常看一下如下例子滓窍。

Employee e=new Employee();
e.EmpName= "Sukesh";
e.Address= "Mumbai";

在 JavaScript 中,「JavaScript 對象」用于呈現(xiàn)復(fù)雜數(shù)據(jù)巩那±艉唬看一下如下例子。

var e={
EmpName= "Sukesh",
Address= "Mumbai"
};

將復(fù)雜的數(shù)據(jù)從 .NET 傳輸給其它技術(shù)拢操,意味著類對象從 .NET 傳輸給其它技術(shù)迁杨,將復(fù)雜數(shù)據(jù)從 JavaScript 傳輸給其它技術(shù)短荐,意味著 JavaScript 對象從 JavaScript 傳輸給其它技術(shù)。

直接傳輸是不可能的。按照之前的標準蚓炬,我們先要將 .NET 對象或者 JavaScript 對象轉(zhuǎn)換為字符串類型彻况,然后再發(fā)送。

解決方案:一個統(tǒng)一的數(shù)據(jù)格式標準

就像之前一樣,行業(yè)也指出一個統(tǒng)一的數(shù)據(jù)格式標準陈醒。說它是統(tǒng)一的,意味著所有人要發(fā)送數(shù)據(jù)之前都需要呈現(xiàn)出他們的數(shù)據(jù)瞧甩。因此 XML 應(yīng)運而生钉跷。

所有技術(shù)都將數(shù)據(jù)轉(zhuǎn)換為 XML 格式,然后將其發(fā)送給其它技術(shù)肚逸。就像字符串一樣爷辙,XML 也被當做一種標準格式,因此每一個技術(shù)都知道它朦促。

C# 代碼創(chuàng)建的 Employee 對象也能通過 XML 格式這樣呈現(xiàn)膝晾。

<employee></employee>
<Employee>
      <EmpName>Sukesh</EmpName>
      <Address>Mumbai</Address>
</Employee>

因此解決方案是,「技術(shù)1把復(fù)雜的數(shù)據(jù)轉(zhuǎn)換為 XML 格式的字符串數(shù)據(jù)务冕,然后將其傳送給技術(shù)2」血当。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

問題 — XML 格式的問題

XML 格式有如下的問題。

  1. XML 格式增加了需要發(fā)送的字符串大小禀忆。大小越大臊旭,意味著轉(zhuǎn)化需要更多的時間,即意味著更差的性能箩退。

  2. 第二個原因离熏,也是最主要的原因:XML 很難創(chuàng)建和解析。

讓我們來探討一下乏德。

  • 正如我們之前所說撤奸,每一個技術(shù)都需要基于數(shù)據(jù)創(chuàng)建 XML 字符串,然后傳輸這個 XML 字符串『袄ǎ現(xiàn)在利用 XML Serializers,用 C# 基于 .NET 對象來創(chuàng)建 XML 字符串是容易的矢棚。但是對于 JavaScript 而言呢郑什?實際上, JavaScript 既沒有序列化的概念蒲肋,也沒有 XML 操作庫用于使用蘑拯。因此當 JavaScript 傳輸數(shù)據(jù)給其它技術(shù)時,我們需要在 JavaScript 對象中手動創(chuàng)建 XML 字符串兜粘。這是一個很艱難的任務(wù)申窘。

  • 當一個技術(shù)從另一個技術(shù)那里接收到數(shù)據(jù)時,通常都是 XML 格式的字符串】字幔現(xiàn)在通過 XML Deserializers剃法,用 C# 將 XML 字符串解析并創(chuàng)建 .NET 對象是容易的。但是對于 JavaScript 而言呢路鹰?實際上贷洲, JavaScript 既沒有反序列化的概念收厨,也沒有 XML 操作庫用于使用。因此當 JavaScript 解析 XML 字符串時优构,這是一個很艱難的任務(wù)诵叁。

解決方案 — JSON

為了解決 XML 格式帶來的問題,行業(yè)想出一個新的格式钦椭,稱為 JSON拧额。它是 「JavaScript Object Notation」的縮寫。

利用 C# 來創(chuàng)建 Employee 對象彪腔,可以通過如下代碼來呈現(xiàn) JSON 格式势腮。

{
  EmpName: "Sukesh",
  Address: "Mumbai"
}

JSON 格式展現(xiàn)的數(shù)據(jù)和 JavaScript 對象相像,因此這種格式被命名為 JSON(JavaScript Object Notation)漫仆。

  • 正如你所看見的捎拯,這比之前更輕量級。

  • 有一些完備的函數(shù)可用于 JavaScript盲厌,即能將 JavaScript 對象轉(zhuǎn)換為 JSON 格式字符串署照,也能將 JSON 格式字符串解析為 JavaScript 對象。

如下代碼展示了如何創(chuàng)建和解析 JSON 字符串吗浩。

var e={
EmpName= &ldquo;Sukesh&rdquo;,
Address= &ldquo;Mumbai&rdquo;
};
var EmployeeJsonString = JSON.stringify(e);//This EmployeeJsonString will be send to other technologies.

var EmployeeJsonString=GetFromOtherTechnology();
var e=JSON.parse(EmployeeJsonString);
alert(e.EmpName);
alert(e.Address);

關(guān)閉對話框

我們可以運用 JQuery API 來關(guān)閉 CreateEmployee 對話框建芙。

更新 Grid

可以通過如下方式來更新 Grid。

  • 通過分部視圖

a. 像 CreateEmployee 設(shè)計函數(shù)一樣懂扼,創(chuàng)建一個 Grid 的分部視圖禁荸。

b. 在 EmployeeListView 中創(chuàng)建一個含有 Id 的 Div,然后在里面展示 Grid 的分部視圖阀湿。

c. 當 Save Employee 按鈕被點擊時赶熟,以分部視圖結(jié)果的格式更新 Grid,然后用新的 PartialViewResult 來替換內(nèi)部的 Grid HTML陷嘴。

我相信迄今為止我們完成的任何一個實驗都能給你一個很好的思路來實現(xiàn)這樣的目的映砖,因此我們將把其作為一個任務(wù)。需要你來親自完成它灾挨。

  • 通過手動代碼

在這個方法中邑退,MVC 行為方法將會返回 EmployeeViewModel,而不是 EmloyeeListViewModel劳澄,這將會被 JavaScript 接收并運用 JavaScript 來創(chuàng)建一個新的行地技,然后手動插入到 Grid 中。EmployeeViewModel 將會以 JSON 字符串形式來從 MVC 行為方法中傳輸?shù)?JavaScript秒拔。

回到實驗

第十步:創(chuàng)建 SaveEmployee Action

在 MainController 中創(chuàng)建一個新的行為方法莫矗,命名為 SaveEmployee。

[AdminFilter]
public ActionResult SaveEmployee(Employee emp)
{
    EmployeeBusinessLayer empBal = new EmployeeBusinessLayer();
    empBal.SaveEmployee(emp);

EmployeeViewModel empViewModel = new EmployeeViewModel();
empViewModel.EmployeeName = emp.FirstName + " " + emp.LastName;
empViewModel.Salary = emp.Salary.Value.ToString("C");
if (emp.Salary > 15000)
{
empViewModel.SalaryColor = "yellow";
}
else
{
empViewModel.SalaryColor = "green";
    }
return Json(empViewModel);
}

現(xiàn)在使用 JSON 的方式來講字符串從 MVC 行為方法傳輸?shù)?JavaScript。

第十一步:包含 Validation.js

在上述實驗中包含 Validation.js 文件趣苏。

@using WebApplication1.ViewModels.SPA
@model CreateEmployeeViewModel
<script src="~/Scripts/Validations.js"></script>

第十二步:創(chuàng)建 SaveEmployee 函數(shù)

打開 CreateEmployee.cshtml 視圖狡相,在頂部創(chuàng)建 SaveEmployee 函數(shù)。

...
...
    function SaveEmployee() {
        if (IsValid()) {
            var e =
                {
                    FirstName: $('#TxtFName').val(),
                    LastName: $('#TxtLName').val(),
                    Salary: $('#TxtSalary').val()
                };
            $.post("/SPA/Main/SaveEmployee",e).then(
                function (r) {
                    var newTr = $('<tr></tr>');
                    var nameTD = $('<td></td>');
                    var salaryTD = $('<td></td>');

                    nameTD.text(r.EmployeeName);
                    salaryTD.text(r.Salary); 

                    salaryTD.css("background-color", r.SalaryColor);

                    newTr.append(nameTD);
                    newTr.append(salaryTD);

                    $('#EmployeeTable').append(newTr);
                    $('#DivCreateEmployee').dialog('close'); 
                }
                );
        }
    }
</script>

第十三步:執(zhí)行并測試

按下 F5食磕,執(zhí)行應(yīng)用尽棕。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

Lab 35 的 Q&A

JSON 方法是用于做什么的?

JSONResult 是 ActionResult 的一個子類彬伦。在第六天的學(xué)習(xí)中滔悉,我們談到了 MVC 請求周期。現(xiàn)在我們再來回顧一下单绑。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

在 ActionResult 類中回官, ExecuteResult 被聲明為抽象的。ActionResult 類的所有子類都以自己的方式來定義它搂橙。在第一天的學(xué)習(xí)中歉提,我們談?wù)撨^ ViewResult。在 ViewResult 類中区转,ExecuteResult 方法將會做如下事情苔巨。

  • 它將會創(chuàng)建 ViewPageActivator 類的對象。

  • 它將會選擇正確的 ViewEngine废离,將 ViewPageActivator 對象作為參數(shù)傳輸給 ViewEngine 的構(gòu)造器侄泽。ViewEngine 將會創(chuàng)建 View 類的對象。

  • 它將會觸發(fā)視圖的 RenderView 方法蜻韭,用于渲染最終 HTML 輸出的響應(yīng)悼尾。

當它來自于 JsonResult,ExecuteResult 方法將會:

  • 設(shè)置響應(yīng)內(nèi)容的類型為「Application/Json」肖方。

  • 運用 JavaScript Serializer闺魏,它將會把傳輸?shù)臄?shù)據(jù)轉(zhuǎn)換為 JSON 格式的字符串。

  • 為響應(yīng)流書寫最終的 JSON 格式字符串窥妇。

5. Lab 36 — 創(chuàng)建單頁應(yīng)用 — Part 4 — 批量上傳

第一步:創(chuàng)建 SpaBulkUploadController

創(chuàng)建一個新的 AsyncController舷胜,稱作 SpaBulkUploadController。

namespace WebApplication1.Areas.SPA.Controllers
{
    public class SpaBulkUploadController : AsyncController
    {
    }
}

第二步:創(chuàng)建 Index 方法

在上述的控制器中創(chuàng)建一個新的行為方法活翩,稱為 Index。

[AdminFilter]
public ActionResult Index()
{
    return PartialView("Index");
}

第三步:創(chuàng)建 Index 分部視圖

在「~/Areas/Spa/Views/SpaBulkUpload」中創(chuàng)建一個新的分部視圖翻伺,稱為 Index材泄。

<div>
    Select File : <input type="file" name="fileUpload" id="MyFileUploader" value="" />
    <input type="submit" name="name" value="Upload" onclick="Upload();" />
</div>

第四步:創(chuàng)建 OpenBulkUpload 方法

在「~/Areas/Spa/Views/Main」文件夾中打開 Index.cshtml,然后創(chuàng)建一個 JavaScript 方法吨岭,稱為 Index.cshtml拉宗。

function OpenBulkUpload() {
            $.get("/SPA/SpaBulkUpload/Index").then
                (
                    function (r) {
                        $("<div id='DivBulkUpload'></div>").html(r).dialog({ width: 'auto', height: 'auto', modal: true, title: "Create New Employee",
                            close: function () {
                                $('#DivBulkUpload').remove();
                            } });
                    }
                );
        }
    </script>
</head>
<body>
<div style="text-align:right">

第五步:執(zhí)行并測試

按下 F5,并執(zhí)行應(yīng)用。完成登錄操作旦事。導(dǎo)航到 Main 控制器下的 Index 行為魁巩,然后點擊 BulkUpload 鏈接。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

第六步:創(chuàng)建 FileUploadViewModel

在 ViewModel 類庫項目的 SPA 文件夾下創(chuàng)建一個新的視圖模型類姐浮,稱為 FileUploadViewModel谷遂。

namespace WebApplication1.ViewModels.SPA
{
    public class FileUploadViewModel
    {
        public HttpPostedFileBase fileUpload { get; set; }
    }
}

第七步:創(chuàng)建 Upload 行為

在 SpaBulkUploadController 下創(chuàng)建一個新的行為方法,稱為 Upload卖鲤。

[AdminFilter]
public async Task<actionresult> Upload(FileUploadViewModel model)
{
    int t1 = Thread.CurrentThread.ManagedThreadId;
    List<employee> employees = await Task.Factory.StartNew<list<employee>>
        (() => GetEmployees(model));
    int t2 = Thread.CurrentThread.ManagedThreadId;
    EmployeeBusinessLayer bal = new EmployeeBusinessLayer();
    bal.UploadEmployees(employees);
    EmployeeListViewModel vm = new EmployeeListViewModel();
    vm.Employees = new List<employeeviewmodel>();
    foreach (Employee item in employees)
    {
        EmployeeViewModel evm = new EmployeeViewModel();
        evm.EmployeeName = item.FirstName + " " + item.LastName;
        evm.Salary = item.Salary.Value.ToString("C");
        if (item.Salary > 15000)
        {
            evm.SalaryColor = "yellow";
        }
        else
        {
            evm.SalaryColor = "green";
        }
        vm.Employees.Add(evm);
    }
    return Json(vm);
}

private List<employee> GetEmployees(FileUploadViewModel model)
{
    List<employee> employees = new List<employee>();
    StreamReader csvreader = new StreamReader(model.fileUpload.InputStream);
    csvreader.ReadLine();// Assuming first line is header
    while (!csvreader.EndOfStream)
    {
        var line = csvreader.ReadLine();
        var values = line.Split(',');//Values are comma separated
        Employee e = new Employee();
        e.FirstName = values[0];
        e.LastName = values[1];
        e.Salary = int.Parse(values[2]);
        employees.Add(e);
    }
    return employees;
}

正如你所看見的肾扰,這次我們返回的是 JsonResult,而不是重定向蛋逾。

第八步:創(chuàng)建 Upload 函數(shù)

在「~/Areas/Spa/Views/SpaBulkUpload」文件夾下打開 Index 視圖集晚。然后創(chuàng)建一個 JavaScript 函數(shù),稱為 Upload区匣。

<script>
    function Upload() {
        debugger;
        var fd = new FormData();
        var file = $('#MyFileUploader')[0];
        fd.append("fileUpload", file.files[0]);
        $.ajax({
            url: "/Spa/SpaBulkUpload/Upload",
            type: 'POST',
            contentType: false,
            processData: false,
            data: fd
        }).then(function (e) {
            debugger;
            for (i = 0; i < e.Employees.length; i++)
            {
                var newTr = $('<tr></tr>');
                var nameTD = $('<td></td>');
                var salaryTD = $('<td></td>');

                nameTD.text(e.Employees[i].EmployeeName);
                salaryTD.text(e.Employees[i].Salary);

                salaryTD.css("background-color", e.Employees[i].SalaryColor);

                newTr.append(nameTD);
                newTr.append(salaryTD);

                $('#EmployeeTable').append(newTr);
            }
            $('#DivBulkUpload').dialog('close');
        });
    }
</script>

第九步:執(zhí)行并測試

創(chuàng)建一個文本文件偷拔,如下所示。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

按下 F5亏钩,并執(zhí)行應(yīng)用莲绰。

7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天
7 天玩轉(zhuǎn) ASP.NET MVC — 第 7 天

6. 總結(jié)

這里,我們完成了 7 天玩轉(zhuǎn) ASP.NET MVC 的系列學(xué)習(xí)铸屉。我們已經(jīng)運用 ASP.NET MVC 的功能完成了一個簡單的項目钉蒲。我們也在其中穿插討論了許多詳細的理論概念。

學(xué)無止境彻坛,雖然 ASP.NET MVC 的系列學(xué)習(xí)已經(jīng)告終顷啼,但是后續(xù)的深入學(xué)習(xí),還需要靠腳踏實地昌屉,不斷地實踐和反思钙蒙。

希望每一個人都享受其中,樂于學(xué)習(xí)间驮,不斷成長躬厌。

原文地址:Learn MVC Project in 7 days

OneAPM for .NET 能夠深入到所有 .NET 應(yīng)用內(nèi)部完成應(yīng)用性能管理和監(jiān)控,包括代碼級別性能問題的可見性竞帽、性能瓶頸的快速識別與追溯扛施、真實用戶體驗監(jiān)控、服務(wù)器監(jiān)控和端到端的應(yīng)用性能管理屹篓。想閱讀更多技術(shù)文章疙渣,請訪問 OneAPM 官方博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末堆巧,一起剝皮案震驚了整個濱河市妄荔,隨后出現(xiàn)的幾起案子泼菌,更是在濱河造成了極大的恐慌,老刑警劉巖啦租,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哗伯,死亡現(xiàn)場離奇詭異,居然都是意外死亡篷角,警方通過查閱死者的電腦和手機焊刹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來内地,“玉大人伴澄,你說我怎么就攤上這事≮寤海” “怎么了非凌?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長荆针。 經(jīng)常有香客問我敞嗡,道長,這世上最難降的妖魔是什么航背? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任喉悴,我火速辦了婚禮,結(jié)果婚禮上玖媚,老公的妹妹穿的比我還像新娘箕肃。我一直安慰自己,他們只是感情好今魔,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布勺像。 她就那樣靜靜地躺著,像睡著了一般错森。 火紅的嫁衣襯著肌膚如雪吟宦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天涩维,我揣著相機與錄音殃姓,去河邊找鬼。 笑死瓦阐,一個胖子當著我的面吹牛蜗侈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播睡蟋,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼宛篇,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了薄湿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎豺瘤,沒想到半個月后吆倦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡坐求,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年蚕泽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桥嗤。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡须妻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泛领,到底是詐尸還是另有隱情荒吏,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布渊鞋,位于F島的核電站绰更,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏锡宋。R本人自食惡果不足惜儡湾,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望执俩。 院中可真熱鬧徐钠,春花似錦、人聲如沸役首。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宋税。三九已至摊崭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間杰赛,已是汗流浹背呢簸。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留乏屯,地道東北人根时。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像辰晕,于是被迫代替她去往敵國和親蛤迎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容