精通ASP.NET MVC ——视图

文章非常长,仅仅用于记录自己学习。 

创建自定义视图引擎

创建自定义视图引擎的价值是,演示请求处理管道如何工作,并完善关于MVC架构如何操作的知识,视图引擎实现IViewEngine接口,如下图所示:

    public interface IViewEngine
    {
        ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache);

        ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache);

        void ReleaseView(ControllerContext controllerContext, IView view);
    }

视图引擎的作用是将对视图的请求转化成ViewEngineResult对象。 这个接口中两个方法是FindView  FindPartialView,给它们传递的是描述请求的参数:处理该请求的控制器(ControllerContext对象)、视图名及其布局,以及是否允许视图引擎重用其缓存结果。当框架对ViewResult进行处理时,会调用这个两个方法。最后一个方法是ReleaseView(释放视图),当视图不在需要时被调用(从其名称不难看出,该方法的作用是释放视图所占用的资源)。

当请求一个视图时,ViewEngineResult类使视图引擎能够对MVC框架做出响应。代码如下图所示:

using System.Collections.Generic;

namespace System.Web.Mvc
{
    public class ViewEngineResult
    {
        public IEnumerable<string> SearchedLoactions{get;private set;}
        public IView View {get;private set;}
        public IViewEngine ViewEngine{get;private set;}

        public ViewEngineResutlt(IEnumerable<string> searchedLoactions)
        {
            if(searchedLoactions == null)
            {
                throw new ArgumentNullException("searchedLocations");
            }
            SearchedLoactions = searchedLoactions
        }

        public ViewEngineResutlt(IView view,IViewEngine viewEngine)
        {
            if(view == null){throw new ArgumentNullException("view");}
            if(viewEngine == null){throw new ArgumentNullException("viewEngine");}
            View = view;
            ViewEngine = viewEngine;
        }
    }
}

可以通过两个构造器中的其中一个来表示一个结果,如果视图引擎能够对请求提供视图,那么可以用以下构造器创建一个ViewEngineResult: 

public ViewEngineResutlt(IView view,IViewEngine viewEngine)

如果视图引擎不能对请求提供视图,那么可以使用如下构造器: 这一版本是查找视图位置的一个枚举。如果找不到视图,就会将枚举的信息显示给用户。

public ViewEngineResutlt(IEnumerable<string> searchedLoactions)

视图引擎的最后一个构造块是IView接口,如下图所示: 


    public interface IView
    {
        void Render(ViewContext viewContext, TextWriter writer);
    }

把一个IView实现传递给ViewEngineResult对象的构造器,然后它会被视图引擎方法所返回。MVC框架会调用Render方法(把IView实现传递给ViewEngineResult对象构造器时,自然便会调用IView接口中的这个Render方法)。 到目前为止,ViewContext对象定义了一些属性,这些属性给你提供请求信息以及MVC框架如何处理它的细节。

有用的ViewContext属性
名称描述
Controller返回处理当前请求的IController实现
RequestContext返回当前请求的细节
RouteData为当前请求返回的路有数据
TempData返回和请求相关的临时数据
View返回将要处理请求的IView接口的实现。很明显,如果你正在创建一个自定义视图实现,它将是当前类。
ViewBag返回一个表示视图的object
ViewData返回一个包含模式视图包和元数据的视图模型数据字典。

这些属性中最有趣的是ViewData,它返回一个ViewDataDictionary对象。定义了一些有用的属性,如下图所示:

有用的ViewDataDictionary
名称描述
keys为字典中的数据返回键值集合,他们可用来访问视图包属性
Model为请求返回视图模型对象
ModelMetadata返回一个可以用来反映模型类型的ModelMetadata对象
ModelState返回有关模型的状态信息

根据以上知识,创建一个自定义视图引擎, 首先新建一个空白项目,创建一个Home控制器,如下图所示:

    public class HomeController : Controller
    {
        // GET: Home
        public ActionResult Index()
        {
            ViewBag.Messge = "Hello,World";
            ViewBag.Time = DateTime.Now.ToShortTimeString();
            return View("DebugData");
        }

        public ActionResult List()
        {
            return View();
        }
    }

创建一个自定义IView,在项目中新建一个Infrastructure的文件夹,创建一个DebugDataView.cs的新的类文件,如下图所示: 

namespace WebApplication1.Infrastructure
{
    public class DebugDataView : IView
    {
        public void Render(ViewContext viewContext, TextWriter writer)
        {
            Write(writer, "---Routing Data---");
            foreach (string key in viewContext.RouteData.Values.Keys)
            {
                Write(writer, "key:{0},value:{1}", key, viewContext.RouteData.Values[key]);
            }
            Write(writer, "---View Data---");
            foreach (string key in viewContext.ViewData.Keys)
            {
                Write(writer,"key:{0},value:{1}",key,viewContext.ViewData[key]);
            }
        }

        private void Write(TextWriter writer, string template, params object[] values)
        {
            writer.Write(string.Format(template,values) + "<p>");
        }
    }
}

 

视图引擎的目的是产生一个ViewEngineResult对象,它或者包含一个IView,或者是一个用于搜索适当视图的位置列表。现在已经有了IView的实现,于是可以创建视图引擎,在Infrastructure的文件夹下新建一个DebugDataViewEngine.cs的类文件,代码如下图所示:

namespace WebApplication1.Infrastructure
{
    public class DebugDataViewEngine : IViewEngine
    {
        public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
        {
            return new ViewEngineResult(new string[] { "No View (Debug Data View Engine)" });
        }

        public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
        {
            if (viewName == "DebugData")
            {
                return new ViewEngineResult(new DebugDataView(), this);
            }
            else
            {
                return new ViewEngineResult(new string[] { "No view (Debug Data View Engine)" });
            }
        }

        public void ReleaseView(ControllerContext controllerContext, IView view)
        {
            
        }
    }
}

注册自定义视图引擎 

视图引擎需要在 Global.asax 的 Application_Start 方法中进行注册,代码如下图所示:

ViewEngines.Engines.Add(new DebugDataViewEngine());

静态的ViewEngine.Engines集合含有一组在应用程序中安装的视图引擎。MVC框架支持在一个单一的应用程序中安装多个引擎。当处理一个ViewResult时,动作调用器获取这组已经安装的视图引擎,并依次调用它们的FindView方法。

一旦动作调用接收到一个含有IView方法的ViewEngineResult对象。便会停止调用FindView方法。如果有两个或者多个引擎能够对同视图的请求进行服务,这意味着在ViewEngines.Engines集合中添加引擎的顺序是很重要的,代码如下图所示:

ViewEngines.Engines.Insert(0,new DebugDataViewEngine());

测试视图引擎 

运行程序,效果如下图所示:

           

如果导航到/Home/List,该动作方法调用View方法请求其默认视图,这是一个我们不支持的视图,就会报错,如下图所示: 

            

从上述报错来看,Razor和ASPX视图也在列表中,可以清除其他类型的视图引擎, 代码如下:

ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new DebugDataViewEngine());

理解Razor视图渲染

Razor视图引擎会编译应用程序中的视图,以改善性能。视图会被转化成C#类,然后被编译。这是在视图中能够如此方便地包含C#代码片段的原因。

程序启动前,视图不会被编译。只有程序启动后才能触发视图编译过程。出于方便,会将视图文件生成的类写成磁盘上的C#代码,然后进行编译,这意味着能够看到一个视图的C#语句。这些语句可以在C盘的某些隐蔽的目录下被找到,并且文件名和它们所包含的类名不对应。


配置视图搜索位置

在查找视图时,Razor视图引擎遵循MVC框架早期版本建立起来的约定。例如,如果请求与Home控制器先关的Index视图时,Razor会审查一下视图列表:

/Views/Home/Index.cshtml

/Views/Home/Index.vbhtml

/Views/Shared/Index.cshtml

/Views/Shared/Index.vbhtml

Razor实际上不会在磁盘上查找这些视图文件,因为它们还没有被编译成C#文件。Razor查找的是表示这些视图的编译类。.cshtml文件是含有C#语句的模板,而.vbhtml文件含有Visual Basic语句。

Razor视图引擎搜索属性
属性描述默认值

ViewLoactionFormats

MasterLocationFormatsPartiaView

LocationFormats

查找视图、分部视图、以及布局的位置

~/ Views / {1} / {0}.cshtml,

~/ Views/ {1} / {0}.vbhtml,

~/ Views/ Shared / {0}.cshtml,

~/ Views/ Shared/ {0}.vbhtml,

AreaViewLocationFormats

AreaMasterLocationFormats

AreaPartialViewLocationForms

为一个区域查找视图、分部视图,以及布局位置

~/ Areas / {2} / Views / {1} / {0}.cshtml,

~/ Areas / {2} / Views / {1} / {0}.vbhtml,

~/ Areas / {2} / Views / Shared / {0}.cshtml,

~/ Areas / {2} / Views / Shared / {0}.vbhtml

这些属性是在Razor之前引入的,这是每组三个属性具有相同值的原因。每个属性都是一个字符串数组,它们是用合成字符串格式化符号来表示的。以下是与占位符对应的参数值:

{0} 表示视图名。

{1} 表示控制器名。

{2} 表示区域名。

通过创建一个RazorViewEngine子类,可以改变Razor搜索的视图文件。 在Infrastructrue文件夹中,创建了一个名称为CustomerLocationViewEngine的视图引擎,代码如下图所示:

    public class CustomLocationViewEngine:RazorViewEngine
    {
        public CustomLocationViewEngine()
        {
            ViewLocationFormats = new string[] { "~/Views/{1}/{0}.cshtml","~/Views/Common/{0}.cshtml"};
        }
    }

以上代码对 ViewLoactionFormats设置了一个新值。新数组只包含用于.cshtml文件的条目。此外,已经将查找共享视图的位置修改为了View/Common,而不是Views/Shared。并且在Global.asax的Application_Start方法中,注册此引擎:

ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new CustomLocationViewEngine());

最后创建/View/Common文件夹,并添加一个名称为List.cshtml的视图文件。 代码如下图所示:

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>List</title>
</head>
<body>
    <div> 
        <h3>This is the /Views/Common/List.cshtml View</h3>
    </div>
</body>
</html>

启动程序,并导航到/Home/List时,执行效果如下图所示:

             

 


 使用分段

Razor引擎支持分段的概念,Razor分段能够灵活的控制将视图的哪一个部分插入到布局之中,以及把他们插入到哪里。

下面对/Views/Home/Index.cshtml文件进行编辑,如下图所示:


@model string[]
@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@section Header{
    <div class="view">
        @foreach (string str in new[] {"Home","List","Edit" })
        {
            @Html.ActionLink(str,str,null,new { style = "margin:5px"})
        }
    </div>
}

<div class="view">
    This is a lists of fruit names

    @foreach (string name in Model)
    {
        <span><b>@name</b></span>
    }
</div>

@section Footer
{
    <div class="view">
        This is the footer
    </div>
}

在上图代码中,创建了名称为“Header” 和 “Footer”的分段。分段的内容可以混用HTML标记和Razor标签。你可以在布局中使用@RenderSection 辅助器方法来指定分段要插入的位置。修改Views/Shared/_Layout.cshtml文件,代码如下图所示:

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <style type="text/css">
        div.layout {
            background-color:lightgray;
        }

        div.view {
            border:thin solid black;margin:10px 0;
        }
    </style>
    <title>@ViewBag.Title</title>
</head>
<body>
    @RenderSection("Header")

    <div class="layout">This is part of the layout</div>

    @RenderBody()

    <div class="layout">
        This is part of Layout
    </div>

    @RenderSection("Footer")
    <div class="layout">
        This is part of Layout
    </div>
</body>
</html>

在Razor对布局进行解析时,RenderSection辅助器方法会显示视图中指定名称的分段内容。视图中未包含在分段中的内容会插入布局中使用RenderBody辅助器方法。 运行程序,结果如下图所示:

                     

注意:一个视图只能定义在布局中被引用的分段。如果视图在视图布局中午没有对应的@RendSection辅助器调用分段,MVC框架会报错。 

一般情况下,不要分段与视图的其余的部分混杂在一起。其约定是,在视图的开始或者结尾部分定义分段,以便更容易看到哪些内容区域被处理成分段,对于那些需要RenderBody辅助器捕捉的内容,可以把这些定义成一个独立的分段,如下图所示:

@model string[]
@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@section Header{
    <div class="view">
        @foreach (string str in new[] {"Home","List","Edit" })
        {
            @Html.ActionLink(str,str,null,new { style = "margin:5px"})
        }
    </div>
}

@section Body
{
    <div class="view">
        This is a lists of fruit names

        @foreach (string name in Model)
        {
            <span><b>@name</b></span>
        }
    </div>
}

@section Footer
{
    <div class="view">
        This is the footer
    </div>
}

以上这种办法有利于建立更清晰的视图,并减少了RenderBody捕捉无关内容的情况。为了使用这种方法,我们得用Rend而Section("Body")替换对RenderBody辅助器的调用。 代码如下图所示:

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <style type="text/css">
        div.layout {
            background-color:lightgray;
        }

        div.view {
            border:thin solid black;margin:10px 0;
        }
    </style>
    <title>@ViewBag.Title</title>
</head>
<body>
    @RenderSection("Header")

    <div class="layout">This is part of the layout</div>

    @RenderSection("Body")

    <div class="layout">
        This is part of Layout
    </div>

    @RenderSection("Footer")
    <div class="layout">
        This is part of Layout
    </div>
</body>
</html>

对分段进行测试

检查一个视图是否已经定义了布局中的一个特定的分段,可以使用如下代码,修改了_layout.cshtml:

@if (IsSectionDefined("Footer"))
{
    @RenderSection("Footer")
}
else
{
    <h4> This is the deafault footer </ht>
}

渲染可选分段 

默认情况下,视图中必须含有布局中的RendSection的所有分段。如果缺少分段,MVC框架将会报错,修改_layout.cshtml代码并运行,结果如下图所示:

 

定义可选分段,只需加上一个false值即可:如果视图定义了它,其内容将被插入到结果中,否则也不会抛出异常,代码如下图所示:

 @RenderSection("scripts",false)

 


使用分部视图

通常需要在应用程序中多个不同的地方,使用同样的Razor标签 和 HTML 标记片段。采取的办法不是重复这些标记,而是采用分部视图(Partial View).

新建一个名称为MyPartial.cshtml分部视图,代码如下图所示:

<div>
    This is the massage from the partial view.
    @Html.ActionLink("This is a link to the Index action", "Index");
</div>

修改List.cshtml页面,代码如下图所示: 


@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>List</title>
</head>
<body>
    <div> 
        <h3>This is the /Views/Common/List.cshtml View</h3>
    </div>
</body>
</html>
@Html.Partial("MyPartial")

提示:Razor视图引擎对分部视图的查找方式,于规则视图相同 (即在~/Views/<controller> 和 ~/Views/Shared文件夹中进行查找)。这意味着,可以创建控制器专用的的特殊版本的分部视图,它会覆盖Shared文件夹下进行查找。

运行程序,效果如下: 

           


使用强类型分部视图 

可以创建强类型分部视图,然后在渲染这个分部视图时,传递要使用的视图模型对象。

新建一个名为MyStrongTypedPartial.cshtml的分部视图,代码如下图所示:

@model IEnumerable<string>

<div>
    This is message from the partial view.
    <ul>
        @foreach (string str in Model)
        {
            <li>@str</li>
        }
    </ul>
</div>

并修改/View/Common/List.cshtml文件,代码如下所示: 


@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>List</title>
</head>
<body>
    <div> 
        <h3>This is the /Views/Common/List.cshtml View</h3>
    </div>
</body>
</html>
@Html.Partial("MyStronglyTypedPartial",new[] {"Apple","Orange","Pear" })

运行效果如下图所示:

             

 


使用子动作 

子动作是通过视同调用的动作方法。当你希望将某种控制器逻辑用于应用程序的多个地方时,子动作可以让你避免重复的控制器逻辑。子动作和动作之间的关系,如同分部视图和视图的关系一样。无论何时,当希望显示某些数据驱动的“小部件”,这些“小部件”要出现在多个页面上,而且含有与主动作无关的数据时,你可能就会希望使用子动作。

创建一子动作,代码如下图所示:

        [ChildActionOnly]
        public ActionResult Time()
        {
            return PartialView(DateTime.Now);
        }

新增Time.cshtml页面,代码如下图所示: 

@model DateTime

<p> The time is @Model.ToShortDateString()</p>

启动程序,再次导航到/Home/List,效果如下图所示:

           

通过提供一个匿名类型对象,其属性对应于子动作方法的参数名,可以将参数传递给动作方法,代码如下图所示:

        [ChildActionOnly]
        public ActionResult Time(DateTime time)
        {
            return PartialView(DateTime.Now);
        }
@Html.Action("Time",new { time = DateTime.Now})

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值