夜鹰教程网-程序员的加油站
 当前位置:文章中心 >> vs2022_vs2019_vs2017_vs2014_vs2012
ASP.NET MVC开发实践教程【View/Model 全解 】
夜鹰教程网 来源:www.yyjcw.com 日期:2016-11-13 16:12:27
本文讲解在Action中向View传递Model的几种方式.以及View获取Model以后如何编写显示逻辑.还详细的介绍了

一.摘要

本文讲解在Action中向View传递Model的几种方式.以及View获取Model以后如何编写显示逻辑.还详细的介绍了

ASP.NET MVC框架提供的Html Helper类的使用及如何为Html Helper类添加自定义扩展方法.

二.承上启下

上一篇文章中我们学习了Controller处理一次请求的全过程.在Controller的Action中, 会传递数据给View,

还会通知View对象开始显示.所以Model是在Action中获取的, 并由Action传递给View. View对象接到Action通
知后会使用自己的显示逻辑展示页面.

 

下面首先让我们学习如何将Model传递给View对象.

三.传递数据给View

在MVC中,Model对象是指包含了数据的模型. Controller将Model传递给View以后, View对象中不应该做任何
的业务逻辑处理, 仅仅根据Model对象做一些显示逻辑的处理.

传递Model对象时, 我们有两种选择:

1.传递一个弱类型的集合, 即成员为object类型的集合,  在View中需要将每个成员转换成我们需要的类型,比如
int, string,自定义类型等.

2.传递强类型对象, 这些类型是我们自定义的. 在View中直接使用我们传递的强类型对象, 不需要再转换类型.

如果让我们自己设计一个MVC框架, 我们也会想到上面两种实现方式,接下来看看在ASP.NET MVC中的实现.

1.传递弱类型的集合

(1) 如何传递

ASP.NET MVC框架定义了ViewContext类, 直译后是"View上下文", 其中保存和View有关的所有数据, 其中
Model对象也封装在了此类型中.

ViewContext对象包含三个属性:

  • IView View
  • ViewDataDictionary ViewData
  • TempDataDictionary TempData

其中ViewData集合和TempData集合都是用来保存Model对象的.在一个Controller的Action中, 我们可以用
如下方式为这两个集合赋值:
///<summary>      
/// 传递弱类型Model的Action示例
/// </summary>
/// <returns>ViewResult</returns>
        public ActionResult WeakTypedDemo()
        {
            ViewData["model"] = "Weak Type Data in ViewData";
            TempData["model"] = "Weak Type Data in TempData";
            return View("WeakTypedDemo");
        }
    
在页面中, 是用如下方式使用这两个集合:

    <div>
        <% = ViewData["model"] %><br />
        <% = TempData["model"] %><br />
    </div>

(2) 传递过程

请注意Action中的赋值语句实际上操作的是Controller类的ViewData和TempData属性, 此时并没有任
何的数据传递.上一篇文章中我们已经学到, return View语句会返回一个ViewResult对象, 并且接下来要
执行ViewResult的Executeresult方法. Controller的View方法会将Controller类的ViewData和
TempData属性值传递给ViewResult,代码如下:

protected internal virtual ViewResult View(IView view, object model) {
            if (model != null) {
                ViewData.Model = model;
            }

            return new ViewResult {
                View = view,
                ViewData = ViewData,
                TempData = TempData
            };
        }





然后在ViewResult中根据ViewData和TempData构建ViewContext对象:
 public override void ExecuteResult(ControllerContext context) {
          //...
            ViewContext viewContext = new ViewContext(context, View, ViewData, TempData);
            View.Render(viewContext, context.HttpContext.Response.Output);
          //...
        }


ViewContext对象最终会传递给ViewPage, 也就是说ViewData和TempData集合传递到了ViewPage.
我这里简化了最后的传递流程, 实际上ViewData对象并不是通过ViewContext传递到ViewPage中的,
ViewPage上的ViewData是一个单独的属性, 并没有像TempData一样其实访问的是
ViewContext.TempData. 这么做容易产生奇异, 本类ViewContext是一个很好理解职责十分清晰的类.
作为使用者的我们暂时可以忽略这点不足, 因为如此实现ViewData完全是为了下面使用强类型对象.

(3)ViewData和TempData的区别

虽然ViewData和TempData都可以传递弱类型数据,但是两者的使用是有区别的:

  • ViewData的生命周期和View相同, 只对当前View有效.
  • TempData保存在Session中, Controller每次执行请求的时候会从Session中获取TempData并
    删除Session, 获取完TempData数据后虽然保存在内部的字典对象中,但是TempData集合的每
    个条目访问一次后就从字典表中删除. 也就是说TempData的数据至多只能经过一次Controller传递.

(4) TempData的实现

TempData的类型是TempDataDictionary, 和一般的字典表没有明显的不同, TempData的生命周期
是由Controll决定的.

在所有Controll的基类ControllerBase中, 创建了类型为TempDataDictionary的TempData属性.

在ControllerBase的派生类Controller中, 重写了ExecuteCore()方法:

  protected override void ExecuteCore() {
            TempData.Load(ControllerContext, TempDataProvider);

            try {
                string actionName = RouteData.GetRequiredString("action");
                if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) {
                    HandleUnknownAction(actionName);
                }
            }
            finally {
                TempData.Save(ControllerContext, TempDataProvider);
            }
        }


注意其中的TempData.Load和TempData.Save语句.

TempDataDictionary.Load用来从ControllerContext总读取TempData的数据.

TempDataDictionary.Save方法将发生了变化的TempData数据保存到ControllerContext中.

这两个方法都需要传递ITempDataProvider实例负责具体的读取和保存操作. 在Controll中默认的
TempDataProvider是SessionStateTempDataProvider, 也就是说读取和保存都使用Session. 我
们也可以扩展自己的TempDataProvider, 可以将临时数据保存在任何地方.比如制作
AspNetCacheTempDataProvider使用机器本地缓存来保存TempData.

为何TempData只能够在Controll中传递一次? 因为SessionStateTempDataProvider.LoadTempData
方法(在TempDataDictionary.Load中调用)在从ControllerContext的Session中读取了TempData
数据后, 会清空Session:

 

  public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext) {   

         HttpContextBase httpContext = controllerContext.HttpContext;                   

     if (httpContext.Session == null) {               

throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled);

            }           

Dictionary<string, object> tempDataDictionary =

httpContext.Session[TempDataSessionStateKey] as Dictionary<string, object>;        

    if (tempDataDictionary != null) {           

     // If we got it from Session, remove it so that no other request gets it      

          httpContext.Session.Remove(TempDataSessionStateKey);             

  return tempDataDictionary;            }       

     else {                return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);    

        }        }




注意上面加粗的部分. 一旦读取成功, 就会删除TempData的Session.

再回忆一下Controll的ExecuteCore方法,  是在Controll的Execute方法中调用的, 是每一次
Controll一定会执行的方法, 所以我们得出了"只能在Controll之间传递一次"的结论.

2.传递强类型对象

我在系统中建立了一个模型类:StrongTypedDemoDTO

从名字可以看出, 这个模型类是数据传输时使用的(Data Transfer Object).而且是我为一个View单独创建的.

添加一个传递强类型Model的Action,使用如下代码:

 public ActionResult StrongTypedDemo()
        {
            StrongTypedDemoDTO model = new StrongTypedDemoDTO() { UserName="ziqiu.zhang",
UserPassword="123456" };
            return View(model);
        }


使用了Controller.View()方法的一个重载, 将model对象传递给View对象.下面省略此对象的传输过程,
先让我们来
看看如何在View中使用此对象.

在创建一个View时, 会弹出下面的弹出框:

 

勾选"Create a strongly-typed view"即表示要创建一个强类型的View,
在"View data class"中选择我们的数据模型类.

在"view content"中如下选项:

 

这里是选择我们的View的"模板", 不同的模板会生成不同的View页面代码. 虽然这些代码不一定满足我们需要,
但是有时候的确能节省一些时间,尤其是在写文章做Demo的时候. 比如我们的View是添加数据使用的,那就选
择"Create".如果是显示一条数据用的, 就选择"Detail".

以选择Detail为例, 自动生成了下列代码:



<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage
<DemoRC.Models.DTO.TransferModelController.StrongTypedDemoDTO>" %>
...
<body>
    <fieldset>
        <legend>Fields</legend>
        <p>
            UserName:
            <%= Html.Encode(Model.UserName) %>
        </p>
        <p>
            UserPassword:
            <%= Html.Encode(Model.UserPassword) %>
        </p>
    </fieldset>
    <p>
        <%=Html.ActionLink("Edit", "Edit", new { /* id=Model.PrimaryKey */ }) %> |
        <%=Html.ActionLink("Back to List", "Index") %>
    </p>
</body>
...
页面的Model属性就是一个强类型对象, 在这里就是StrongTypedDemoDTO类实例.page页面指令可以看出,
这里的页面继承自ViewPage<T>类, 而不是ViewPage, 用T已经确定为StrongTypedDemoDTO类型,
所以Model的类型就是StrongTypedDemoDTO.

3.传递数据的思考

使用强类型数据要优于弱类型数据, 老赵也曾提出过. 强类型有太多的好处, 智能提示, 语意明确,
提高性能,编译时发现错误等等.

所以在实际应用中, 我们应该传递强类型的数据.

目前ASP.NET MVC还存在一些不足. 将页面类型化,  导致了只能传递一种数据类型实例到页面上. 而且内
部代码的实现上并不十分完美.尤其是虽然我们已经知道传递的是StrongTypedDemoDTO类型, 页面的Model
属性也是StrongTypedDemoDTO类型, 但是仍然需要进行强制类型转换, 原因是Controller的View(object model)
方法重载接收的是一个object类型.

还有, 为每个View建立一个模型类, 也是一件繁琐的工作. 也许我们的业务模型中的两个类组合在一起就是View页
面需要的数据, 但是却不得不建立一个类将其封装起来.模型类的膨胀也是需要控制一个事情. 尤其是对于互谅网
应用而非企业内部的系统, 页面往往会有成百上千个.而且复用较少.

当然目前来说既然要使用强类型的Model, 我提出一些组织Model类型的实践方法.下面是我项目中的Model
类型组织结构:

 

这里Model是一个文件夹, 稍大一些的系统可以建立一个Model项目. 另外我们需要建立一个DTO文件夹,
用来区分Model的类型.
MVC中的Model应该对应DTO文件夹中的类.在DTO中按照Controller再建立文件夹,
因为Action和View大部分都是按照Controller组织的, 所以Model也应该按照Controller组织.

在Controller文件夹里放置具体的Model类. 其实两个Controller文件夹中可以同相同的类名称, 我们
通过命名空间区分同名的Model类:




使用时也要通过带有Controller的命名空间使用比如DTO.TransferModelController.StrongTypedDemoDTO,
或者建立一些自己的约定.

四.使用Model输出页面

View对象获取了Model以后, 我们可以通过两种方式使用数据:

1.使用内嵌代码

熟悉ASP,PHP等页面语言的人都会很熟悉这种直接在页面上书写代码的方式.但是在View中应该只书写和显示
逻辑有关的代码,而不要增加任何的业务逻辑代码.


假设我们创建了下面这个Action,为ViewData添加了三条记录:

        /// <summary>
        /// Action示例:使用内嵌代码输出ViewData
        /// </summary>
        /// <returns></returns>
        public ActionResult ShowModelWithInsideCodeDemo()
        {
            ViewData["k1"] = @"<script type=""text/javascript"">";
            ViewData["k2"] = @"alert(""Hello ASP.NET MVC !"");";
            ViewData["k3"] = @"</script>";
            return View("ShowModelWithInsideCode");
        }
在ShowModelWithInsideCode中, 我们可以通过内嵌代码的方式, 遍历ViewData集合:

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>使用内嵌代码输出ViewData</title>
    <% foreach(KeyValuePair<string, object> item in ViewData )
       {%>
            <% = item.Value %>
    <% } %>   
</head>
<body>
    <div>
   
        <div>此页面运行的脚本代码为:</div>
        <fieldset>      
        <% foreach(KeyValuePair<string, object> item in ViewData )
           {%>
               <% = Html.Encode(item.Value) %> <br />
        <%  } %>
        </fieldset>
    </div>
</body>
</html>
页面上遍历了两遍ViewData,第一次是作为脚本输出的, 第二次由于进行了HTML编码,所以将作为文字显示在页面上.

使用这种方式, 我们可以美工做好的HTML页面的动态部分, 使用<% %>的形式转化为编码区域, 通过程序控制输出.
由于只剩下显示逻辑要处理,所以这种开发方式往往要比CodeBehind的编码方式更有效率, 维护起来一目了然.

最让我高兴是使用这种方式后,我们终于可以只使用HTML控件了.虽然ASP.NET WebFrom编程模型中自带了很多服
务器控件, 功能很好很强大, 但是那终究是别人开发的控件, 这些控件是可以随意变化的, 而且实现原理也对使用
者封闭. 使用原始的页面模型和HTML控件将使我们真正的做程序的主人.而且不会因为明天服务器控件换了个
用法就要更新知识, 要知道几乎
所有的HTML控件几乎是被所有浏览器支持且不会轻易改变的.

2.使用服务器控件
注意虽然我们同样可以在ASP.NET MVC中使用服务器端控件, 但是在MVC中这并不是一个好的使用方式.建议不要使用.

要使用服务器端控件, 我们就需要在后台代码中为控件绑定数据. ASP.NET MVC框架提供的添加一个View对象的方法已
经不能创建后台代码, 也就是说已经摒弃了这种方式.但是我们仍然可以自己添加.

首先创建一个带有后台代码的(.cs文件),一般的Web Form页面(aspx页面),然后修改页面的继承关系,
改为继承自ViewPage:

public partial class ShowModelWithControl : System.Web.Mvc.ViewPage
在页面上放置一个Repeater控件用来显示数据:

<body>
    <form id="form1" runat="server">
    <div>
        <asp:Repeater ID="rptView" runat="server">
            <ItemTemplate>
                <%# ((KeyValuePair<string, object>)Container.DataItem).Value  %><br />
            </ItemTemplate>
        </asp:Repeater>
    </div>
    </form>
</body>
在Page_Load方法中, 为Repeater绑定数据:

    public partial class ShowModelWithControl : System.Web.Mvc.ViewPage
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            rptView.DataSource = ViewData;
            rptView.DataBind();
        }
    }
在Controller中创建Action, 为ViewData赋值:

        /// <summary>
        /// Action示例:使用服务器控件输出ViewData
        /// </summary>
        /// <returns></returns>
        public ActionResult ShowModelWithControlDemo()
        {
            ViewData["k1"] = @"This";
            ViewData["k2"] = @"is";
            ViewData["k3"] = @"a";
            ViewData["k4"] = @"page";
            return View("ShowModelWithControl");
        }
运行结果:

 







 

再次强调,  在ASP.NET MVC中我们应该尽量避免使用这种方式.

3.使用 HTML Helper 类生成HTML控件

HTML Helper类是ASP.NET MVC框架中提供的生成HTML控件代码的类. 本质上与第一种方式一样, 只是我们
可以不必手工书写HTML代码,而是借助HTML Helper类中的方法帮助我们生成HTML控件代码.

同时,我们也可以使用扩展方法为HTML Helper类添加自定义的生成控件的方法.

HTML Helper类的大部分方法都是返回一个HTML控件的完整字符串, 所以可以直接在需要调用的地方使用
<% =Html.ActionLink() %>的形式输出字符串.

(1)ASP.NET MVC中的HtmlHelper类

在ViewPage中提供了Html属性, 这就是一个HtmlHelper类的实例. ASP.NET MVC框架自带了下面这些方法:

  • Html.ActionLink()
  • Html.BeginForm()
  • Html.CheckBox()
  • Html.DropDownList()
  • Html.EndForm()
  • Html.Hidden()
  • Html.ListBox()
  • Html.Password()
  • Html.RadioButton()
  • Html.TextArea()
  • Html.TextBox()

上面列举的常用的HtmlHelper类的方法,并不是完整列表.


下面的例子演示如何使用HtmlHelper类:

    <div>
        <% using (Html.BeginForm())
           { %>
           <label style="width:60px;display:inline-block;">用户名:</label>
           <% =Html.TextBox("UserName", "ziqiu.zhang", new { style="width:200px;" })%>
           <br /><br />
           <label style="width:60px;display:inline-block;">密码:</label>
           <% =Html.Password("Psssword", "123456", new { style = "width:200px;" })%>                    
        <% }%>
    </div>
上面的代码使用Html.BeginForm输出一个表单对象, 并在表单对象中添加了两个Input, 一个使用
Html.TextBox输出, 另一个使用Html.Password输出,区别是Html.Password输出的input控件的type类型
为password.效果如图:

 




 

(2)扩展Html Helper类

我们可以自己扩展HtmlHelper类, 为HtmlHelper类添加新的扩展方法, 从而实现更多的功能.

在项目中建立Extensions文件夹, 在其中创建SpanExtensions.cs文件.源代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;


namespace System.Web.Mvc
{
    public static class SpanExtensions
    {
        public static string Span(this HtmlHelper helper,string id, string text)
        {
            return String.Format(@"<span id=""{0}"">{1}</span>", id, text);
        }
    }
}



上面的代码我们为HtmlHelper类添加了一个Span()方法, 能够返回一个Span的完整HTML字符串.

因为命名空间是System.Web.Mvc,所以页面使用的时候不需要再做修改,Visual Studio会自动识别出来:

 

请大家一定要注意命名空间, 如果不使用System.Web.Mvc命名空间, 那么一定要在页面上引用你的扩展方法所在
的命名空间, 否则我们的扩展方法将不会被识别.

接下来在页面上可以使用我们的扩展方法:

    <div>
        <!-- 使用自定义的Span方法扩展HTML Helper -->
        <% =Html.Span("textSpan", "使用自定义的Span方法扩展HtmlHelper类生成的Span") %>
    </div>

(3) 使用TagBuilder类创建扩展方法

上面自定义的Span()方法十分简单, 但是有时候我们要构造具有复杂结构的Html元素, 如果用字符串拼接的方法就有些笨拙.

ASP.NET MVC框架提供了一个帮助我们构造Html元素的类:TagBuilder

TagBuilder类有如下方法帮助我们构建Html控件字符串:

方法名称 用途
AddCssClass() 添加class=””属性
GenerateId() 添加Id,  会将Id名称中的"."替换为IdAttributeDotReplacement 属性值的字符.默认替换成"_"
MergeAttribute() 添加一个属性,有多种重载方法.
SetInnerText() 设置标签内容, 如果标签中没有再嵌套标签,则与设置InnerHTML 属性获得的效果相同.
ToString() 输出Html标签的字符串, 带有一个参数的重载可以设置标签的输出形式为以下枚举值:
  • TagRenderMode.Normal -- 有开始和结束标签
  • TagRenderMode.StartTag -- 只有开始标签
  • TagRenderMode.EndTag -- 只有结尾标签
  • TagRenderMode.SelfClosing -- 单标签形式,如<br/>

同时一个TagBuilder还有下列关键属性:

属性名称 用途
Attributes Tag的所有属性
IdAttributeDotReplacement 添加Id时替换"."的目标字符
InnerHTML Tag的内部HTML内容
TagName Html标签名, TagBuilder只有带一个参数-TagName的构造函数.所以TagName是必填属性

 

下面在添加一个自定义的HtmlHelper类扩展方法,同样是输出一个<Span>标签:

        public static string Span(this HtmlHelper helper, string id, string text, string css, object htmlAttributes)
        {
            //创意某一个Tag的TagBuilder
            var builder = new TagBuilder("span");

            //创建Id,注意要先设置IdAttributeDotReplacement属性后再执行GenerateId方法.
            builder.IdAttributeDotReplacement = "-";
            builder.GenerateId(id);
           

            //添加属性           
            builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));

            //添加样式
            builder.AddCssClass(css);
            //或者用下面这句的形式也可以: builder.MergeAttribute("class", css);

            //添加内容,以下两种方式均可
            //builder.InnerHtml = text;
            builder.SetInnerText(text);

            //输出控件
            return builder.ToString(TagRenderMode.Normal);

        }
在页面上,调用这个方法:

<% =Html.Span("span.test", "使用TagBuilder帮助构建扩展方法", "ColorRed", new { style="font-size:15px;" })%>

生成的Html代码为:

<span id="span-test" class="ColorRed" style="font-size: 15px;">使用TagBuilder帮助构建扩展方法</span>

注意已经将id中的"."替换为了"-"字符.

 




五.总结

本来打算在本文中直接将ViewEngine的使用也加进来, 但是感觉本文已经写了很长的内容, (虽然不多,但是很长......)
所以将ViewEngine作为下一章单独介绍.

前些天 Scott Guthrie's的博客上放出了"ASP.NET MVC免费教程", 里面介绍了创建一名为"NerdDinner"项目的全过程,
使用LINQ+ASP.NET MVC, 但是其中对于技术细节没有详细介绍(和本系列文章比较一下就能明显感觉出来), 下面提供本书的
pdf文件下载地址以及源代码下载地址:

 

  • 免费下载PDF版本
  • 下载应用源码 + 单元测试

    源代码是英文版本,  其实我最近有做一个中文的"Nerd Dinner"的想法, 但是因为要写文章而且还要工作已经让我焦头烂额,
    写文章真的是一件体力活.如果有人同样有兴趣翻译代码, 我可以提供域名和服务器空间.

    差点提供忘记本篇文章的示例源代码下载:

    http://files.cnblogs.com/zhangziqiu/AspNet-Mvc-4-Demo.rar

    作者:张子秋
  • 复制链接 网友评论 收藏本文 关闭此页
    上一条: ASP.NET MVC开发实践教程【ViewEn…  下一条: ASP.NET MVC开发实践教程Controller/Ac…
    夜鹰教程网成立于2008年,目前已经运营了将近 13 年,发布了大量关于 html5/css3/C#/asp.net/java/python/nodejs/mongodb/sql server/android/javascript/mysql/mvc/easyui/vue/echarts原创教程。 我们一直都在坚持的是:认证负责、一丝不苟、以工匠的精神来打磨每一套教程,让读者感受到作者的用心。我们默默投入的时间,确保每一套教程都是一件作品,而不是呆板的文字和视频! 目前我们推出在线辅导班试运营,模式为一对一辅导,教学工具为QQ。我们的辅导学科包括 java 、android原生开发、webapp开发、商城开发、C#和asp.net开发,winform和物联网开发、web前端开发,但不仅限于此。 普通班针对的是国内学员,例如想打好基础的大学生、想转行的有志青年、想深入学习的程序员、想开发软件的初学者或者业余爱好者等。 就业办针对即将毕业上岗的大四学生,或者打算转行的初级开发工程师。 留学生班针对的是在欧美、加拿大、澳洲、日本、韩国、新加坡等地留学的中国学子,目的是让大家熟练地掌握编程技能,按时完成老师布置的作业,并能顺利地通过考试。 详细咨询QQ:1416759661   夜鹰教程网  基于角色的权限管理系统(c-s/b-s)。
      夜鹰教程网  基于nodejs的聊天室开发视频教程
      夜鹰教程网  Git分布式版本管理视频教程
      夜鹰教程网  MVC+EasyUI视频教程
      夜鹰教程网  在线考试系统视频教程
      夜鹰教程网  MongoDB视频教程。
      夜鹰教程网 Canvas视频教程
      夜鹰教程网 报表开发视频教程
      热点推荐
    一个关于天气预报的WebService【C…
    VS2010最大的新特点是并行编程的进…
    TextBox控件:asp.net中如何为密码…
    Web服务调用实例:实现天气预报的…
    ASP.NET程序员面试试题(130道题)
    ASP.NET教程:调用WebService的源码…
    网站开发全程设计
    据说这套.net面试题很多网络公司都…
    考考你:C#常见题型及部分答案
    原创:.net读取数据库sql2000
    伪静态URL重写配置
    配置web.config代码asp.net3.5个性…
    使用线程池提高性能 Socket网络编…
    ASP.NET(C#)GridView表头的增加…
    如何找到正确的学习方向【.NET版】…
      最近更新
    C#修改注册表demo
    一个获取内容中的图片地址的方法
    ASP.NET 4.0尚未在 Web 服务器上注…
    四大作用域:application,session…
    ConfigurationManager不存在的解决…
    vs2012_vs2013_vs2015没有Web Dep…
    vs2015禁用解决方案中单击打开文件…
    微软为Visual Studio 2015新增安卓…
    C#如何实现搜索引擎网络爬虫程序
    C#中正则表达式的用法
    用C#抓取需要登录的页面数据
    VS2015新功能
    VS2015安装图解教程
    vs2015新功能介绍
    vs2015安装图解

    关于我们 | 网站建设 | 技术辅导 | 常见问题 | 联系我们 | 友情链接

    夜鹰教程网 版权所有 www.yyjcw.com All rights reserved 备案号:蜀ICP备08011740号3