• 《ASP.NET MVC 5 破境之道》:第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造


    第一境 ASP.Net MVC5项目初探 — 第三节:View层简单改造

    看到这里,您一定已经迫不及待的想要动手了。下面,我们通过一些对视图的修改,来深入了解Razor能给我们带来什么惊喜。

    一、修改页面标题

    有过HTML开发经验的读者,都知道页面标题,是在HTML页面的<title>...</title>标签中指定的。

    默认情况下,MVC视图的标题为[xxx] - My ASP.NET Application,我们想把“ - My ASP.NET Application”替换为“ - Honor Shop”。

    查找顺序是由内而外,因为内层配置会覆盖外层,所以我们先看Home目录中的视图文件,有没有指定标题。默认是没有的。

    接下来看Home目录中,有没有建立新的_ViewStart.cshtml,因为它也可以覆盖全局配置。默认也是没有的。

    接下来再找上级目录,也就是/Views目录中的_ViewStart.cshtml,默认是可以找到的,这个文件里默认指定的Layout是"~/Views/Shared/_Layout.cshtml",其中“~/”代表虚拟根目录,是相对路径的表示方法,它指向了/Views/Shared/_Layout.cshtml文件。

    打开_Layout.cshtml文件,我们可以看到<title>@ViewBag.Title - My ASP.NET Application</title>,可能现在还不太明白@ViewBag.Title是什么意思,不过先不用管它,总之,格式看起来很像“[xxx] - My ASP.NET Application”。

    我们把<title>@ViewBag.Title - My ASP.NET Application</title>修改为<title>@ViewBag.Title - Honor Shop</title>。

    运行一下,可以看到标题栏已经变成“[xxx] - Honor Shop”的格式了。

    接下来,我们来看看[xxx]是从哪儿来的。还是按照刚才的顺序,这次比较幸运,在控制器视图文件里就发现了一些端倪。每个视图文件的开头都有如此一段代码:

    @{ ViewBag.Title = "xxx"; }

    有的同学会疑惑,这是个什么鬼,HTML里面,没见过啊。这里,结合刚才我们用到的<title>@ViewBag.Title - My ASP.NET Application</title>一起来说明一下,首先,我们使用的是Razor视图引擎,'@'符号,在Razor中有特殊的含义,它标明一段服务器端代码的开始。

    C# 的主要 Razor 语法规则

    Razor 代码封装于 @{ ... } 中

    行内表达式(变量和函数)以 @ 开头

    代码语句以分号结尾

    字符串由引号包围

    C# 代码对大小写敏感

    C# 文件的扩展名是 .cshtml

    C# 实例

    <!-- 单行代码块 -->
    @{    var myMessage =    "Hello World"; }
    ​
    <!-- 行内表达式或变量 -->
    <p>The value of myMessage is: @myMessage</p><!-- 多行语句代码块 -->
    @{
        var greeting = "Welcome to our site!";
        var weekDay = DateTime.Now.DayOfWeek;
        var greetingMessage = greeting + " Here in Huston it is: " + weekDay;
    }
    <p>The greeting is: @greetingMessage</p>

    至于ViewBag.Title = "xxx";,代码中并没有ViewBag的定义,首先想到它会不会是Razor内置的东东,要么,就是MVC提供的东东,但要解释清楚它,目前还有点复杂,这里讲解会牵扯的东西比较多,为了保持对视图的改造的流畅性,我们后面再介绍它。但现在可以猜测的出它是一个服务器端对象,并且它有一个Title属性。虽然这个猜测有点偏激,姑且先这么理解它吧。

    那么,书归正传,就尝试替换一下吧,看看效果,正如所料,[xxx],被替换掉了。

    fdb5e9744b0e46afb452ce6b609897ed

    也有细心的同学,会发现我的标题栏里的图标怎么于自己的不一样,下面我们就来更改标题栏的图标吧。

    二、修改标题栏图标

    学习过HTML的读者,都知道这是一个比较基础知识,我们不难发现,在项目的根目录下,“躺着”一个名叫favicon.ico的文件。对了,就是它,图标文件的扩展名为.ico,读者可以从网上随意下载一个ico文件,来替换它。我为Honor Shop精心设计了一个,简洁又富有科技感的图标:)自我陶醉一下。

    honorshop_logo

    也可以通过在html的<head>...</head>标签中加入如下代码来改变favicon的路径及名称。

    <head>
        <title>@ViewBag.Title - Honor Shop</title>
        <link rel="icon" href="~/Content/Images/honorshop.ico" type="image/x-icon" />
        <link rel="shortcut icon" href="~/Content/Images/honorshop.ico" type="image/x-icon" />
    </head>

    三、修改页脚

    先修改页脚,不为别的,代码少,改起来简单:P,在页面底部,很轻松就找到了:

    <footer>
        <p>© @DateTime.Now.Year - My ASP.NET Application</p>
    </footer>

    简单修改为:

    <footer>
        <p>© @DateTime.Now.AddYears(-3).Year - Honor Shop</p>
    </footer>

    运行看看,效果实现,也简单使用了一下在Razor中进行服务端编码的快感。

     

    不过这时发现,好像很多地方都用到了“Honor Shop”这段字符串,这是我的网站的名字,可以预料,之后,还会有很多地方会用到。当然,方法有很多了,比如写入配置文件,比如写入数据库,等等。这里为了折腾,暂时不考虑这些方法,既然我们现在是对视图的修改,本着学习的目的,先在视图层面想办法,比如在布局视图中加一个变量,然后,视图里就可以引用这个变量了,也比四处硬编码来得爽快。

    我们现在_Layout.cshtml的顶端加入如下代码:

    @{ var SiteName = "My Honor Shop"; }

    接着,我们对页面标题进行更新:

    <title>@ViewBag.Title - @SiteName</title>

    再更新页脚

    <p>© @DateTime.Now.AddYears(-3).Year - @SiteName</p>

    运行一下看看,哎哟,不错哟。

    不过,这里有个问题,如果我们的项目中,使用了多个布局文件,那不是要在每个布局文件中都定义一遍SiteName?而且,经过试验,在控制器视图中,也没有办法直接使用@SiteName,因为控制器视图与_Layout视图并没有继承关系。

    这里提出第一个方案,还记得_ViewStart先于其他视图运行,它的代码里只是指定了一个Layout属性,别的什么都没做。但是这个Layout却可以在控制器视图中进行重写覆盖,由此灵感而发,这个Layout属性到底是谁的属性?在Layout上点击鼠标右键选择[Go To Definition]项或者使用快捷键[F12]跳转到Layout的声明处。

    0158084ae29b4bcca9c94bef1a2dbfbb

    可以看到,Layout是一个抽象类StartPage的属性,既然这个类是一个抽象类,那么它就不能被实例化,但不管是哪个类继承自它,也就同样继承Layout属性,既然Layout能够在其他视图中使用,那么与Layout平起平坐的其他属性,肯定也可以。所以,第一眼就瞄到了PageData这个属性,它是一个字典,Key是object类型,Value是dynamic类型,看起来,都挺合适的。于是乎,在_ViewStart.cshtml中做些手脚:

    @{
        /* 在PageData中添加站点名称键值对 */
        PageData.Add("SiteName", "My First Solution 4 Page Title - Honor Shop");
    ​
        Layout = "~/Views/Shared/_Layout.cshtml";
    }

    以页面标题为例,对视图中所有使用@SiteName的地方进行更新:

    <title>@ViewBag.Title - @PageData["SiteName"]</title>

    运行一下,效果如同期望一样,在布局视图和控制器视图中,都可以使用:

    clipboard

    第二个方案就是我们之前还很朦胧的ViewBag,与查看Layout属性的方法一样,故技重施,跳转到ViewBag的声明处。

    93454756b349460eb7eea3ba795af0cb

    可以看到,ViewBag是一个抽象类WebViewPage的属性,而且是动态属性,那么如法炮制,再对_ViewStart.cshtml中做些手脚:

    clipboard

    但是很遗憾,在_ViewStart中不能操作ViewBag属性。这是为什么呢?再仔细看看上面两个抽象类,原来他们分别在不同的命名空间,也就是说,_ViewStart和控制器视图,处于不同命名空间。第二个方案宣告失败,放弃,就是这么随性:D

    不过于此同时,我们还是有所收获的,在返回头来看看第二个抽象类WebViewPage,尤其是它内部声明的一系列属性,我可以很负责任的告诉你,你的整个.NET MVC生涯都是在与它们打交道,不信?你等着瞧……

    关于页脚的修改,暂时告一段落,下面来修改导航栏吧。

    四、修改导航栏

    知己知彼方能百战不殆,我们先看看导航栏原来长得什么样子。

    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li>@Html.ActionLink("Home", "Index", "Home")</li>
                    <li>@Html.ActionLink("About", "About", "Home")</li>
                    <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
                </ul>
            </div>
        </div>
    </div>

    忽略掉繁杂的HTML标签和各种样式,标重点,第9、13、14、15行。第13~15行很相似,只是参数的内容不同,第9行与其它几行,都是以@Html.ActionLink开头,参数个数不同。看来都是使用了Html的ActionLink的重载方法。

    等等,'@'符我们说过了,表明后面要接服务端代码了,那么,Html又是什么鬼?我们还是跳转到声明处看看吧

    0bb98e91908544018c662b252299592f

    首先,它是一个抽象类WebViewPage<TModel>的属性,WebViewPage<TModel>类又继承自WebViewPage类,WebViewPage类就是刚才我们看到的声明了ViewBag的类,兜兜转转啊,不过,WebViewPage类中也声明了一个Html,只不过是HtmlHelper<object>类型,这里的Html是HtmlHelper<TModel>类型,其实它们两个都是同根同源的,都是HtmlHelper<TModel>类的实例。同时,也可以看出,两个WebViewPage,一个是强类型的,一个是弱类型的。继续追踪HtmlHelper<TModel>类,它继承自HtmlHelper类,具有两个构造函数,还有两个只读属性,没有方法声明,那么,方法应该是都在HtmlHelper基类中声明的了,跟进去一看,大跌眼镜,虽然有声明了一堆方法,但是居然没有我们期待已久的ActionLink方法,此刻,必须要想到扩展方法,如果没有想到,那么请恶补C#的相关细节。返回到_Layout.cshtml,直接跳转到ActionLink方法的声明,眼前一亮,原来都在这里:

    e32f5ac0bc2f49209fee3852b8f8321a

    大家可以展开类及方法上的Summary信息来了解一些信息。从类名上可以看出,这是一个针对链接的扩展类。从方法名可以看出,链接可以分为两种,一种是ActionLink,另一种是RouteLink。RouteLink可以简单理解为通过路由(不是通常所说的路由器阿)跳转,用到时再详细介绍。我们先来看ActionLink。

    ActionLink有10个重载,每个都讲,也是比较辛苦的,毕竟我也很懒……我们就拿导航栏中第9行代码使用的重载来做一个说明吧。

    //
    // 根据指定的链接文字,操作名称,控制器名称,路由参数对象和html属性对象,
    // 返回一个锚点元素(也就是html中的a标签),
    // MvcHtmlString是一个特殊的字符串,是经过Html-encoding的html字符串,这点很重要。
    //
    public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper
        // 锚点标签中显示的文字
        , string linkText
        // 控制器中操作的名称
        , string actionName
        // 控制器名称
        , string controllerName
        // 包含路由参数的对象,通过反射检测routeValues对象的属性获取路由参数。
        // 通常使用对象初始化器语法创建routeValues对象。
        , object routeValues
        // 一个包含html属性列表的对象
        , object htmlAttributes);

    1546035326(1)

    目前,我们也只能是修改第一个参数,动手:

    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink(@PageData["SiteName"] as string // @PageData["SiteName"]的值是dynamic类型
                                , "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li>@Html.ActionLink("首页", "Index", "Home")</li>
                    <li>@Html.ActionLink("关于我们", "About", "Home")</li>
                    <li>@Html.ActionLink("联系我们", "Contact", "Home")</li>
                </ul>
            </div>
        </div>
    </div>

    92abba08fccc4bb8bdd85f644e65651b

    这样就完成了对导航信息的基本修改。

    现在我们可以初步体会到,@Html.ActionLink可以帮助我们生成一个锚点元素,它是.NET MVC视图引擎提供的一个辅助方法。

    Html就是对HtmlHelper的封装,它能帮助我们生成页面上所需要的各种元素。

    但我现在又想把我辛辛苦苦设计的Logo放上去,替换干巴巴的“Honor Shop"文字。

    五、添加带链接的图片

    首先,将制作好的Logo文件(logo.png)拷贝到/Content/Images/下。将原来的锚点代码注释掉。

    @*@Html.ActionLink(@PageData["SiteName"] as string // @PageData["SiteName"]的值是dynamic类型
                       , "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })*@

    Razor中使用@*....*@来注释,看起来,还挺有喜感的。

    接下来,我们先立个目标,最终要生成一个什么样的html,能够满足我们添加的Logo的需求。

    <a href="/Home/Index" class="navbar-brand" style="padding-top: 0px; padding-bottom:0px;">
        <img src="/Content/Images/logo.png" alt="Honor Shop" title="Honor Shop" style="height: 100%; background-color:white;">
    </a>

    这里没有什么特别的,都是一些Html和CSS的基础的东西,不是本书的重点,不多介绍,看看效果。

    07720172f9174d5c86f860b9bd6a5157

    目前,它可以很好的工作,但它是脆弱的。思考一个问题,如果,我们的应用,并没有部署在网站的根目录,或者修改了路由的定义,那么,锚点的href和图片的src的值,都有可能把浏览器导航到一个网站上并不存在的资源处。再做一个假设,我们的应用里有很多这样的锚点加图片的元素,他们在编译时,并不会报告错误,应用将变得非常难以维护。

    更好的办法就是可以通过路由来计算路径,这样就可以有效的解决上面提出的部署位置和修改路由的问题了。

    .NET Web应用为我们提供了一套路由机制,可以为我们计算路由路径,其核心就是RouteTable,我们可以通过

    RouteTable.Routes.GetVirtualPath(RequestContext requestContext, RouteValueDictionary values).VirtualPath;
    RouteTable.Routes.GetVirtualPath(RequestContext requestContext, string routeName, RouteValueDictionary values).VirtualPath;

    这两个重载方法来计算路径,是不是很开心,那么来看看参数列表,我们是不是都具备:

    第一个参数,requestContext,不用操心,视图引擎已经为我们提供了,可以通过this.ViewContext.RequestContext来获取;

    第二个参数,values,他的类型是RouteValueDictionary,是一个字典,我们可以通过这个字典,提交路由所需的参数;

    第三个参数,routeName,可以用来指定我们需要使用哪条路由;

    路由是MVC的一个重要机制,更是ASP.NET核心框架的一部分,还记得我们在本境第二节中介绍App_Start目录时,提及到RouteConfig,这个类就是用来管理配置路由的,并且在应用启动时,Global中的Application_Start方法中会调用它的RegisterRoutes方法来注册路由。

    ASP.NET MVC框架中的路由主要有两个用途:

    1. 匹配传入的请求(该请求不匹配服务器文件系统中的文件或资源),并把这些请求映射到控制器操作。

    2. 构造传出的URL,用来响应控制器操作。

    现在我们打开RouteConfig,看看默认提供的路由是什么样的。

    using System.Web.Mvc;
    using System.Web.Routing;
    ​
    namespace HonorShop.Web
    {
        public class RouteConfig
        {
            public static void RegisterRoutes(RouteCollection routes)
            {
                routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    ​
                // 注册一条路由配置
                routes.MapRoute(
                    // 定义路由的名称,名称可以自定义,但在路由表中不可重复。
                    name: "Default",
                    // 定义一条url访问的模式
                    url: "{controller}/{action}/{id}",
                    // 指定路由的默认值
                    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
                );
            }
        }
    }

    通过上面的代码,可以看出,注册一条路由所需要的参数,以及他们的含义。

    对于构造首页的url,我们知道controller是Home,action是Index,id是可选的,可以不提供。

    但是对于图片的url,就没有那么幸运了,因为图片是静态资源,并没有明确的controller和action。很显然,默认提供的路由,不适合我们。不如,我们动手来添加一条路由吧:

    // 注册一条路由指向图片资源
    routes.MapRoute(
        // 定义本条路由的名称为Images
        name: "Images",
        // 定义url匹配的模式
        url: "Content/Images/{imgName}",
        // 定义本条路由的默认参数值,这里配置为可选;
        // 其实更好的建议是配置为一张“图片未找到”“图片已损坏”等含义的图片名称;
        defaults: new { imgName = UrlParameter.Optional }
    );

    好了,准备工作都已经就绪了,让我们返回_Layout.cshtml,在它顶部开始撸代码:

    @{
        var context = this.ViewContext.RequestContext;
        var href_values = new RouteValueDictionary { { "controller", "home" }, { "action", "index" } };
        var href = RouteTable.Routes.GetVirtualPath(context, href_values).VirtualPath;
        var src_values = new RouteValueDictionary { { "id", "logo.png" } };
        var src = RouteTable.Routes.GetVirtualPath(context, "Images", src_values).VirtualPath;
    }

    方法都很简单,前面也对细节都解释过了,下面接着修改我们的锚点和图片元素:

    <a href="@href" class="navbar-brand" style="padding-top: 0px; padding-bottom:0px;">
        <img src="@src" alt="Honor Shop" title="Honor Shop" style="height: 100%; background-color:white;" />
    </a>

    运行效果与图14一样,完美。完美是完美,不过实现的过程,未免复杂了些,而且这里只是解决了路径计算的问题,如果有很多路径需要计算,参数也是各种各样,可以想象计算路径给我们带来的如此的繁重的代码量,对于偷奸耍滑成性的MVC团队来说,这也太不能忍了。

    值得庆幸的是,MVC给我们提供了很多与链接相关的辅助方法:

      • Html.ActionLink:我们前面刚介绍过;
      • Url.Action:根据给定的Controller,Action 生成链接,但是Html.ActionLink返回的是MvcHtmlString的一个带<a>标签的超链接,而Url.Action返回的是string,一个根据Controller,Action生成的URL地址,比Html.ActionLink少了<a>标签;
      • Html.RouteLink 与 Url.RouteUrl:两者都是可以指定由哪一个路由来生成Url,其它与上面的ActionLInk,Action一样;
      • Url.Content:将虚拟(相对)路径转换为应用程序绝对路径。

    这次,我们来使用两个Url属性提供的方法,毕竟我们是用来计算路径,Url看起来比Html更贴切一些。将之前直接使用路由所做的更改,全都删除或者注释掉,对的,就是这么随性:

    <a href="@Url.Action("Index", "Home")" class="navbar-brand" style="padding-top: 0px; padding-bottom:0px;">
        <img src="@Url.Content("~/Content/Images/logo.png")"
        alt="Honor Shop" title="Honor Shop" style="height: 100%; background-color:white;" />
    </a>

    运行看看:

    使用辅助方法计算资源路径效果

    没有意外,一切按计划行事。但为了早日冲入第二境,我决定再用@Html提供的辅助方法折腾一遍,但是悲剧的是在@Html中并没有找到与图片相关的扩展方法。这就有点悲剧了。

    如果只用@Html.ActionLink能不能实现呢,能,可以把Logo图片加到样式表的背景图里,ActionLink的htmlAttributes应用样式,也是可以的。也比较简单,但对SEO不够友好,特殊场景还是可以使用的,这里就不实操了。

    不过这时,想起了之前提到的@Html.ActionLink是一系列扩展的重载方法,这些方法都可以辅助生成锚点元素。那么,我们是不是也可以通过扩展方法,来生成符合我们要求的自定义图片链接元素呢?说干就干。

    在项目中创建一个Html目录,用来放置对HtmlHelper扩展方法的类文件;

    在Html目录中新建类LinkExtensions,修改为静态类(必须,可以参考C#扩展方法的实现);

    using System.Collections.Generic;
    using System.Web.Mvc;
    using System.Web.Routing;
    ​
    namespace HonorShop.Web.Html
    {
        public static class LinkExtensions
        {
            /// <summary>
            /// Summary:
            ///     扩展ActionLink方法,用来创建带锚点的图片元素;
            /// </summary>
            /// <param name="htmlHelper">扩展类</param>
            /// <param name="actionName">操作名</param>
            /// <param name="controllerName">控制器名</param>
            /// <param name="routeValues">路由参数列表</param>
            /// <param name="linkAttributes">锚点htmlAttributes</param>
            /// <param name="imagePath">图片路径</param>
            /// <param name="imageAttributes">图片htmlAttributes</param>
            /// <returns>An image element (img element) within an anchor element (a element).</returns>
            public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper
                , string actionName
                , string controllerName
                , RouteValueDictionary routeValues
                , IDictionary<string, string> linkAttributes
                , string imagePath
                , IDictionary<string, string> imageAttributes)
            {
                var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
    ​
                string imgUrl = urlHelper.Content(imagePath);
                TagBuilder imgTagBuilder = new TagBuilder("img");
                imgTagBuilder.MergeAttribute("src", imgUrl);
                if (null != imageAttributes && 0 < imageAttributes.Count)
                    imgTagBuilder.MergeAttributes(imageAttributes, true);
                string img = imgTagBuilder.ToString(TagRenderMode.SelfClosing);
    ​
                string url = urlHelper.Action(actionName, controllerName, routeValues);
    ​
                TagBuilder tagBuilder = new TagBuilder("a")
                {
                    InnerHtml = img
                };
                tagBuilder.MergeAttribute("href", url);
                if (null != linkAttributes && 0 < linkAttributes.Count)
                    tagBuilder.MergeAttributes(linkAttributes, true);
    ​
                return new MvcHtmlString(tagBuilder.ToString(TagRenderMode.Normal));
            }
        }
    }

    在_Layout.cshtml的顶部添加引用

    @using HonorShop.Web.Html;

    修改导航栏Logo位置代码

    @Html.ActionLink("Index",
            "Home",
            new RouteValueDictionary { { "area", string.Empty } },
            new Dictionary<string, string> {
                { "alt", "Honor Shop" },
                { "title", "Honor Shop" },
                { "class", "navbar-brand" },
                { "style", "padding-top: 0px; padding-bottom:0px;" } },
            "~/Content/Images/logo.png",
            new Dictionary<string, string> {
                { "alt", "Honor Shop" },
                { "title", "Honor Shop" },
                { "style", "height: 100%; background-color:white;" } })

    两个字典拼装的有点多,代码显得长了点,不过,一个方法搞定,还是简洁了很多,而且使用了扩展方法,在重用和灵活性上都得到了大幅度的提升。

    回想一下,我们在第二部分说过如何修改标题栏图标时,使用了如下代码:

    <link rel="icon" href="~/Content/Images/honorshop.ico" type="image/x-icon" />
    <link rel="shortcut icon" href="~/Content/Images/honorshop.ico" type="image/x-icon" />

    其中,也涉及到了路径问题,那么,我们应用学到的知识,来更新一下它们吧:

    <link rel="icon" href="@Url.Content("~/Content/Images/honorshop.ico")" type="image/x-icon" />
    <link rel="shortcut icon" href="@Url.Content("~/Content/Images/honorshop.ico")" type="image/x-icon" />

    运行一下,效果如图15所示一样。大功告成。

    到这里,我们也基本了解了HtmlHelper和UrlHelper两个辅助类的使用方法以及如何为其添加扩展方法。这将在我们后面的开发过程中,打下良好的基础,读者朋友需要细细品味,最好能够跟着动手实际操作一番。

    下一节开始,我们就要使用这些知识,动手打造我们的第一个页面了。

    喜欢本系列丛书的朋友,可以点击链接加入QQ交流群(994761602)【C# 破境之道】
    方便各位在有疑问的时候可以及时给我个反馈。同时,也算是给各位志同道合的朋友提供一个交流的平台。
    需要源码的童鞋,也可以在群文件中获取最新源代码。

  • 相关阅读:
    oracle over 函数几个例子
    securecrt 方向键乱码解决
    windows 8.1 启用hyper-v导致vmware 无法使用的问题解决方案(兼顾WP8.1模拟器和vmware)
    oracle 查询所有约束
    home条 防止误碰
    导航栏相关知识
    报错Domain=NSCocoaErrorDomain Code=3840 "Garbage at end."
    H5混合开发问题总结
    适配iOS11
    网络
  • 原文地址:https://www.cnblogs.com/mikecheers/p/10288364.html
Copyright © 2020-2023  润新知