[MVC]ASP.NET MVC Contact Manager Sample Applicatio

mikel阅读(825)

In this series of tutorials, Stephen Walther demonstartes how to use the ASP.NET MVC framework to build an entire Contact Manager application using unit tests, test-driven development, Ajax, and software design principles and patterns.
Read all 7 tutorials in C# or VB.NET.

中文翻译地址:http://www.cnblogs.com/024hi/archive/2009/03/17/asp_net_mvc_sample_contact_manager_index.html

[C#]从零开始学习ASP.NET MVC(四) View/Model 全解

mikel阅读(947)

一.摘要

本文讲解在Action中向View传递Model的几种方式.以及View获取Model以后如何编写显示逻辑.还详细的介绍了ASP.NET MVC框架提供的Html Helper类的使用及如何为Html Helper类添加自定义扩展方法.

二.承上启下

上 一篇文章中我们学习了Controller处理一次请求的全过程.在Controller的Action中, 会传递数据给View,还会通知View对象开始显示.所以Model是在Action中获取的, 并由Action传递给View. View对象接到Action通知后会使用自己的显示逻辑展示页面.

image

下面首先让我们学习如何将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传递, 并且每个元素至多只能访问一次.

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时, 会弹出下面的弹出框:

image

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

在"view content"中如下选项:

image

这里是选择我们的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类型组织结构:

image

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

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

namespace DemoRC.Models.DTO.TransferModelController
{
/// <summary>
/// Action为StrongTypedDemo的数据传输模型
/// </summary>
public class StrongTypedDemoDTO
{
/// <summary>
/// 用户名
/// </summary>
public string UserName
{
get;
set;
}
/// <summary>
/// 用户密码
/// </summary>
public string UserPassword
{
get;
set;
}
}
}

使用时也要通过带有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");
}

运行结果:

image

再次强调,  在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.效果如图:

image

(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会自动识别出来:

image

请大家一定要注意命名空间, 如果不使用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文件下载地址以及源代码下载地址:

image

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

     

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

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

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

  • [JQuery]非常简洁的动态加载js和css的jquery plugin

    mikel阅读(823)


     1// plugin author :  chenjinfa@gmail.com
     2// plugin name : $.include
     3//     $.include('file/ajaxa.js');$.include('file/ajaxa.css');
     4//  or $.includePath  = 'file/';$.include(['ajaxa.js','ajaxa.css']);
     5
     6$.extend({
     7    includePath: '',
     8    include: function(file) {
     9        var files = typeof file == "string" ? [file]:file;
    10        for (var i = 0; i < files.length; i++{
    11            var name = files[i].replace(/^\s|\s$/g, "");
    12            var att = name.split('.');
    13            var ext = att[att.length  1].toLowerCase();
    14            var isCSS = ext == "css";
    15            var tag = isCSS ? "link" : "script";
    16            var attr = isCSS ? " type='text/css' rel='stylesheet' " : " language='JavaScript' type='text/JavaScript";
    17            var link = (isCSS ? "href" : "src"+ "='" + $.includePath + name + "'";
    18            if ($(tag + "[" + link + "]").length == 0) document.write("<" + tag + attr + link + "></" + tag + ">");
    19        }

    20    }

    21}
    );

    放在JavaScript文件头部,就像C#(using…)或Java(import …)

    [UI]80 年代以来的操作系统 GUI 设计进化史(下)

    mikel阅读(832)

    70年代,施乐公司 Xerox Palo Alto Research Center (PARC) 的 研究人员开发了第一个 GUI 图形用户界面,开启了计算机图形界面的新纪元,80年代以来,操作系统的界面设计经历了众多变迁,OS/2,Macintosh, Windows, Linux, Symbian OS ,各种操作系统将 GUI设计带进新的时代。本文介绍了80年代以来各种操作系统 GUI 界面设计的进化史。这是本文的第二部分,第一部分参阅80年代以来的操作系统GUI 设计进化史(上)

    Windows 3.1 (1992)
    该版本的 Windows 支持预装的 TrueType 字体,第一次使 Windows 成为可以用于印刷的系统。Windows 3.0 中,只能通过 Adobe 字体管理器(ATM)实现该功能。该版本同时包含一个叫做 Hotdog Stand 的配色主题。
    配色主题可以帮助某些色盲患者更容易看清图形。
    windows_311_workspace
    Source: Wikipedia


    OS/2 2.0 (1992)
    这是第一个获得世界认可并通过可用性与可访问性测试的 GUI,整个 GUI 基于面向对象模式,每个文件和文件夹都是一个对象,可以同别的文件,文件夹与应用程序关联。它同时支持拖放式操作以及模板功能。
    Os 2 2
    IBM OS/2 2.0, Source: toastytech.com


    Os 2 2
    IBM OS/2 2.0, Source: toastytech.com


    Windows 95 (1995)
    Windows 3.x 之后,微软对整个用户界面进行了重新设计,这是第一个在窗口上加上关闭按钮的 Windows 版本。图标被赋予了各种状态(有效,无效,被选中等),那个著名的“开始”按钮也是第一次出现。对操作系统和 GUI 而言,这是微软的一次巨大飞跃。
    Windows 95
    Microsoft Windows 95, Source: guidebookgallery.org


    Windows 95
    Microsoft Windows 95, Source: guidebookgallery.org


    1996 – 2000 OS/2 Warp 4 (1996)
    IBM 终于争气地推出了 OS/2 Warp 4。桌面上可以放置图标,也可以自己创建文件和文件夹,并推出一个类似 Windows 回收站和 Mac 垃圾箱的文件销毁器,不过一旦放进去进不能再恢复。
    Os 2 Warp 4
    IBM OS/2 Warp 4, Source: toastytech.com


    Os 2 Warp 4
    IBM OS/2 Warp 4, Source: toastytech.com


    Mac OS System 8 (1997)
    该版本的 GUI 支持默认的256色图标,Mac OS 8 最早采用了伪3D图标,其灰蓝色彩主题后来成为 Mac OS GUI 的标志。
    Macos 8
    Apple Mac OS 8, Source: guidebookgallery.org


    Windows 98 (1998)
    图标风格和 Windows 95 几无二致,不过整个 GUI 可以使用超过256色进行渲染,Windows 资源管理器改变巨大,第一次出现活动桌面。
    Windows 98
    Microsoft Windows 98, Source: toastytech.com


    KDE 1.0 (1998)
    KDE是 Linux 的一个统一图形用户界面环境。
    Kde 1
    KDE 1.0, Source: ditesh.gathani.org


    GNOME 1.0 (1999)
    GNOME 桌面主要为 Red Hat Linux 开发,后来也被别的 Linux 采用。
    Gnome 1
    Red Hat Linux GNOME 1.0.39, Source: visionfutur.com


    2001 – 2005 Mac OS X (released in 2001)
    2000年初,苹果宣布推出其 Aqua 界面,2001年,推出全新的操作系统 Mac OS X。默认的 32×32, 48×48 被更大的 128×128 平滑半透明图标代替。
    该 GUI 一经推出立即招致大量批评,似乎用户都如此大的变化还不习惯,不过没过多久,他们呢就接受了这种新风格,如今这种风格已经成了 Mac OS 的招牌。
    Mac osx 1
    Apple Mac OS X 10.1 Source: guidebookgallery.org


    Windows XP (released in 2001)
    每一次微软推出重要的操作系统版本,其 GUI 也必定有巨大的改变,Windows XP 也不例外,这个 GUI 支持皮肤,用户可以改变整个 GUI 的外观与风格,默认图标为 48×48,支持上百万颜色。
    Windows xp
    Microsoft Windows XP Professional, Source: guidebookgallery.org
    KDE 3 (released in 2002)
    自1.0版发布以来,KDE 有了长足的改进,对所有图形和图标进行了改进并统一了用户体验。
    Kde 3
    KDE 3.0.1, Source: netbsd.org


    2007 – 2009 (current)
    Windows Vista (released in 2007)
    这是微软向其竞争对手做出的一个挑战,Vista 中同样包含很多 3D 和动画,自 Windows 98 以来,微软一直尝试改进桌面,在 Vista 中,他们使用类似饰件的机制替换了活动桌面。
    Windows Vista
    Microsoft Windows Vista, Source: technology.berkeley.edu
    Mac OS X Leopard (released in 2007)
    对于第六代 Max OS X,苹果又一次对用户界面做出改进。基本的 GUI 仍是 Aqua,但看上去更 3D 一些,也包含了 3D 停靠坞以及很多动画与交互功能。
    Mac osx Leopard
    Apple Mac OS X 10.5 Leopard, Source: skattertech.com
    KDE (v4.0 Jan. 2009, v4.2 Mar. 2009)
    KDE 4 的 GUI 提供了很多新改观,如动画的,平滑的,有效的窗体管理,图标尺寸可以很容易调整,几乎任何设计元素都可以轻松配置。相对前面的版本绝对是一个巨大的改进。
    kde
    Source: Wikipedia

    鸣谢

    延伸阅读
    苹果的设计演化史:1977-2008
    本文国际来源:Operating System Interface Design Between 1981-2009
    中文翻译来源:COMSHARP CMS 官方网站

    [UI]80 年代以来的操作系统 GUI 设计进化史(上)

    mikel阅读(715)

    70年代,施乐公司 Xerox Palo Alto Research Center (PARC) 的 研究人员开发了第一个 GUI 图形用户界面,开启了计算机图形界面的新纪元,80年代以来,操作系统的界面设计经历了众多变迁,OS/2,Macintosh, Windows, Linux, Symbian OS ,各种操作系统将 GUI设计带进新的时代。本文介绍了80年代以来各种操作系统 GUI 界面设计的进化史。

    第一个使用现代图形界面的个人电脑是 Xerox Alto,设计于1973年,该系统并未商用,主要用于研究和大学。
    Source: toastytech.com
    1981-1985
    Xerox 8010 Star (1981年推出)
    这是第一台全集成桌面电脑,包含应用程序和图形用户界面(GUI),一开始叫 The Xerox Star,后改名为 ViewPoint, 最后又改名为 GlobalView。

    Xerox 8010 Star, Source: toastytech.com
    Apple Lisa Office System 1 (1983)
    又 称 Lisa OS,这里的 OS 是 Office System的缩写,苹果开发这款机器的初衷是作为文档处理工作站。不幸的是,这款机器的寿命并不长,很快被更便宜的 Macintosh 操作系统取代。LisaOS 几个升级包括 1983年的 Lisa OS2, 1984年的 Lisa OS 7/7 3.1。

    Apple Lisa OS 1, Source: GUIdebook

    Apple Lisa OS 1, Source: GUIdebook
    VisiCorp Visi On (1984)
    Visi On 是为 IBM PC 开发的第一款桌面 GUI,该系统面向大型企业,价格昂贵,使用基于鼠标的 GUI,它内置安装程序与帮助系统,但尚未使用图标。

    VisiCoprt Visi On, Source: toastytech.com

    VisiCoprt Visi On, Source: toastytech.com
    Mac OS System 1.0 (1984)
    System 1.0 是最早的 Mac 操作系统 GUI,已经拥有现代操作系统的几项特点,基于窗体, 使用图标。窗体可以用鼠标拖动,文件与文件夹可以通过拖放进行拷贝。

    Apple Mac System 1.0, Source: toastytech.com
    Amiga Workbench 1.0 (1985)
    一经发布,Amiga 就领先时代。它的 GUI 包含诸如彩色图形(四色:黑,白,蓝,橙),初级多任务,立体声以及多状态图标(选中状态和未选中状态)

    Amiga Workbench 1.0, Source: GUIdebook

    Amiga Workbench 1.0, Source: GUIdebook
    Windows 1.0x (1985)
    1985年,微软终于在图形用户界面大潮中占据了一席之地,Windows 1.0 是其第一款基于 GUI 的操作系统 。使用了 32×32 像素的图标以及彩色图形,其最有趣的功能是模拟时钟动画图标。

    Microsoft Windows 1.01, Source: makowski-berlin.de

    Microsoft Windows 1.01, Source: makowski-berlin.de
    1986 – 1990 IRIX 3 (released in 1986, first release 1984)
    64为 IRIX 操作系统是为 Unix 设计的,它的一个有趣功能是支持矢量图标,这个功能远在 Max OS X 面世前就出现了。

    Silicon Graphics IRIX 3.0, Source: osnews.com
    Windows 2.0x (1987)
    这个版本的 Windows 操作系统中对 Windows 的管理有了很大改进,Windows 可以交叠,改变大小,最大化或最小化。

    Microsoft Windows 2.03, Source: guidebookgallery.org

    Microsoft Windows 2.03, Source: guidebookgallery.org
    OS/2 1.x (released in 1988)
    OS/2 最早由 IBM 和微软合作开发,然而1991年,随着微软将这些技术放到自己的 Windows 操作系统,两家公司决裂,IBM继续开发 OS/2,OS/2 使用的 GUI 被称为 “Presentation Manager (展示管理)”,这个版本的 GUI只支持单色,以及固定图标。

    Microsoft-IBM OS/2 1.1, Source: pages.prodigy.net

    Microsoft-IBM OS/2 1.1, Source: pages.prodigy.net
    NeXTSTEP / OPENSTEP 1.0 (1989)
    Steve Jobs 心血来潮,想为大学和研究机构设计一款完美的电脑,他的这个设想后来造就了一家叫做 NeXT Computer 的公司。
    第一台 NeXT 计算机于1988年发布,不过到了1989年随着 NeXTSTEP 1.0 GUI 的发布才取得显著进展,该 GUI 后来演变成 OPENSTEP。
    该 GUI 的图标很大,48×48像素,包含更多颜色,一开始是单色的,从1.0开始支持彩色,下图中已经可以看到现代 GUI 的影子。

    NeXTSTEP 1.0, Source: kernelthread.com
    OS/2 1.20 (1989)
    OS/2 的下一个小版本在很多方面都做了改进,图标看上去更好看,窗体也显得更平滑。

    OS/2 1.2, Source pages.prodigy.net
    Windows 3.0 (1990)
    到 Windows 3.0, 微软真正认识到 GUI的威力,并对之进行大幅度改进。该操作系统已经支持标准或386增强模式,在增强模式中,可以使用640K以上的扩展内存,让更高的屏幕分辨率和更 好的图形成为可能,比如可以支持 SVGA 800×600 或 1024×768。
    同时,微软聘请 Susan Kare 设计 Windows 3.0 的图标,为 GUI 注入统一的风格。

    Microsoft Windows 3.0, Source: toastytech.com

    Microsoft Windows 3.0, Source: toastytech.com
    1991 – 1995 Amiga Workbench 2.04 (1991)
    该版 GUI 包含很多改进,桌面可以垂直分割成不同分辨率和颜色深度,在现在看来似乎有些奇怪。默认的分辨率是 640×256,不过硬件支持更高的分辨率。

    Commodore Amiga Workbench 2.04, Source: guidebookgallery.org
    Mac OS System 7 (released in 1991)
    Mac OS 7 是第一款支持彩色的 GUI,图标中加入了微妙的灰,蓝,黄阴影。

    Apple Mac OS System 7.0, Source: guidebookgallery.org

    Apple Mac OS System 7.0, Source: guidebookgallery.org
    鸣谢

    延伸阅读
    苹果的设计演化史:1977-2008
    本文国际来源:Operating System Interface Design Between 1981-2009
    中文翻译来源:COMSHARP CMS 官方网站

    [XNA]XNA基础(03) —— 动画与帧率

    mikel阅读(825)

        我们要做的2D和3D游戏离不开动画,那么在XNA中如何实现动画了?

         首先,我们来看最简单的动画 —— 移动。

         要移动一个Sprite非常简单,我们只需要在Game1.Update()方法中改变Sprite的位置坐标,在下次Game1.Draw()方法被调用时,屏幕上显示的Sprite就被移动了。

         接下来,我们看复杂一点的动画,比如炸弹的爆炸效果,我们可以这样来实现,制作一系列的图片,每张图片都是爆炸过程中某一状态的表现,如下所示:

         

         上面的20个小图片表现了一个爆炸从初始到结束的所有状态,在实际制作时,我们通常将这些图片按顺序制作在一张大图中,并且保证大图中每个小图的尺寸是完全一样的。我们称这样的大图为精灵帧序列图Sprite Sheets

         有了爆炸的Sprite Sheets,我们可以通过在Game1.Update()方法中改变下一个要显示的小图片的索引(比如[2,3]),再根据索引以及小图片的尺寸计算出 该将要显示的小图片在Sprite Sheets中的相对位置,这样我们就可以在Game1.Draw()方法中将Sprite Sheets中的目标小图片的那块区域绘制出来。你应该还记得上一篇文章中讲到的Game1.Draw()方法的第三个参数 sourceRectangle,用它可以指定我们要绘制Sprite Sheets的目标区域。

     

         看来,实现一个动画并非难事,真正困难的地方在于如何控制每个动画可以有自己不同的刷新速度,如何使同一个动画在不同配置的机器上表现相同。这就涉及到帧率问题。

         所谓帧率Frame Rate,指的是一秒钟内重新绘制屏幕的次数。XNA框架为我们设置的默认帧率是60fps。为什么选择60了?因为这是在人的眼睛感觉不到闪烁的情况下显示器最低的刷新频率。我们可以通过基类Microsoft.Xna.Framework.Game的属性TargetElapsedTime来重新设置它。比如:

    base.TargetElapsedTime = new TimeSpan(000010);

         这表示每隔10ms就重绘一次,即帧率为100fps。

         设置帧率为100fps,并不表示就真的会达到100fps的速度,这要看机器的配置如何。当机器的配置不够时,XNA会自动跳过某些次 绘制——即不调用Game1.Draw()方法。我们可以通过GameTime(你还记得Update和Draw方法都有一个该类型的参数)的IsRunningSlowly属性来检测实际的帧率是否比我们设定的要小。

         通过修改TargetElapsedTime属性来设置帧率,会使所有的动画都受到影响,因为它实际修改的是调用Game1.Update()和Game1.Draw()的频率。

         我们如何使一个动画以自己恒定的速度刷新了?包括这个动画的刷新速度不受主帧率(即TargetElapsedTime设定的值)和机器配置的影响,当然,前提条件是我们动画的刷新率不能大于主帧率,也不能超出机器配置允许的最大帧率。

         我们可以用类似下面的代码来控制每个动画以自己的刷新率运行:

              //成员变量          

              int timeSinceLastFrame = 0

              int millisecondsPerFrame = 50; // 20fps          

     

              //在Update方法中

              timeSinceLastFrame += gameTime.ElapsedGameTime.Milliseconds;
              
    if (timeSinceLastFrame > millisecondsPerFrame) //满足了换帧条件
              {
                    timeSinceLastFrame 
    -= millisecondsPerFrame;

                    //…… 此处可以转到动画的下一帧

              }

         通过上述代码,我们就可以控制目标Sprite的动画速率为20fps。     

         在实际的应用中,我们可以将上述控制帧率的代码放到Sprite的基类中,这样就可以控制不同的Sprite以各自的速率运行了。

         今天就讲到这里,下一节我们将讲述如何捕捉用户的输入事件,比如鼠标、键盘的输入。

    [XNA]XNA基础(02)绘制基础

    mikel阅读(827)

    在所有的图形引擎中,绘制都是最基础的部分,本文将介绍在XNA框架中与绘制相关的基础知识。

    在XNA中,我们使用SpriteBatch来进行绘制。首先,我们需要使用SpriteBatch来绘制什么了?是精灵Sprite,对。

    那么Sprite通过什么来表现了?是纹理,比如2D纹理Texture2D。嗯,你可以把纹理想象成Sprite的外表,比如我们制作的一幅精灵图片,就是一个纹理。

    我们要如何才能把一幅图片加载到我们的游戏中来作为一个Sprite的纹理了?这要通过素材管道Content Pipeline。所谓素材,包括我们游戏中要用到的图片、模型、声音等,比如一个纹理图片就是素材的一种。素材管道就很容易理解了,它可以把我们需要的素材导入到我们的游戏中,以XNA能够识别的正确的格式。而且,格式的识别与转换是在编译期完成的。

    在新建的XNA项目中,会有一个默认的Content文件夹,通常,我们会把所有的素材放在这个地方,并且根据素材的种类我们会在其下创建一些子文件夹,比如Image子文件夹用来存放所有的图片素材,Audio文件夹用来存放声音素材等。

    当一个物件别被识别为素材后,就会有一个唯一的资产名称AssetName来标记它,我们可以通过素材的属性页来查看素材的AssetName属性并修改它。

    下面我们可以用素材管理器ContentManager将一个素材导入到游戏中:

    Texture2D texture;
    texture
    = Content.Load<Texture2D>(@”Image\logo); //在上文提到的LoadContent方法中调用

    很多时候,我们的图片需要是透明的,那么如何在SpriteBatch绘制的时候将图片绘制为透明了?有两种方法:要么图片具有透明的背景,要么在制作图片时将需要透明的部分设置为纯正的洋红色(255,0,255)。除此之外,还需要注意,SpriteBlendMode模式必须为AlphaBlend(这也是默认模式,稍后会提到),才能达到我们期望的透明效果。

    在进行渲染时,我们还需要注意层深度(Layer Depth)。所谓Layer Depth,指的是你需要将目标Sprite绘制在哪一个深度的层次。默认情况下,SpriteBatch会根据你调用的顺序来决定Layer Depth的相对值,比如,你先调用SpriteBatch绘制Sprite A,然后调用SpriteBatch在同一位置绘制Sprite B,那么,Sprite B就会把Sprite A挡住。如果我们依靠调用SpriteBatch绘制的顺序来决定Sprite的深度,那就太不灵活了,为此,调用SpriteBatch绘制方法时,你 可以指定一个代表层次深度的参数(范围0~1)来指示SpriteBatch按照我们希望的顺序来绘制对象。

    有了上面的这些基础之后,我们可以详细讲解一下XNA中的渲染过程。每次渲染的过程都类似下面的模式:

    protected override void Draw(GameTime gameTime)
    {
    GraphicsDevice.Clear(Color.Black);
    spriteBatch.Begin();
    spriteBatch.Draw();
    spriteBatch.End();
    base.Draw(gameTime);
    }

    (1)GraphicsDevice.Clear()方法用于清除屏幕背景。

    (2)SpriteBatch.Begin()方法用于通知显卡准备绘制场景。

    (3)轮流调用SpriteBatch.Draw()绘制所有组件。

    (4)SpriteBatch.End()方法用于告诉显卡绘制已经完成。

    (5)最后,显卡显示绘制的结果。

    SpriteBatch.Begin()方法有一个接受参数的重载:

    void Begin(SpriteBlendMode blendMode, SpriteSortMode sortMode, SaveStateMode stateMode, Matrix transformMatrix) ;

    SpriteBlendMode 参数决定了Sprite的颜色与背景颜色的混合模式,默认为AlphaBlend,我们刚才提到过。

    SpriteSortMode参数决定了Sprite的排序模型,即与前面的Layer Depth关联。

    SaveStateMode参数用于控制是否保存图形设备的状态。

    TransformMatrix参数用于控制旋转、缩放等。

    接下来我们看最重要的绘制Draw方法:

    void Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, float scale, SpriteEffects effects, float layerDepth);

    第一个Texture2D参数,表示要绘制Sprite的纹理。

    第二个Vector2参数,表示要绘制的Sprite的左上角的位置在哪里 。

    第三个Rectangle参数,表示要绘制源纹理的哪一个区域,如果要绘制纹理的全部部分,则传null。

    第四个Color参数,表示绘制时所调制的颜色通道。

    第五个参数rotation,表示要旋转的角度。

    第六个参数origin,表示围绕哪个位置进行旋转。

    第七个参数scale,表示绘制时要缩放的比例。

    第八个参数effects,表示是否进行翻转。SpriteEffects.FlipHorizontally — 水平翻转;SpriteEffects.FlipVertically — 垂直翻转。

    最后一个参数layerDepth,就是我们前面说过的要在哪一个深度层次绘制对象。

    最后,要解释一下,为什么需要在每次Draw调用时,都先调用GraphicsDevice.Clear()方法清除屏幕然后再重新绘制所有物件了?这样性能不是很差吗?而且有时候我们可能只有极小的一部分需要重新绘制。

    假设我们每次只重新绘制变动的那一部分,那我们就需要一个复杂的管理器来追踪每一个部分的变动,这样我们才有可能知道要重绘哪一部分。你 可以想象一下,这个追踪的过程是相当复杂的。而且还存在这样的情况,那就是当一个Sprite移动时,它后面原先被挡住的Sprite会露出来,而这个 Sprite可能又会挡住另外一个Sptrite的一部分,这样一来就更加复杂了。所以,每次重绘整个屏幕并不是一个坏的idea。

    今天就讲到这里,下一节我们将讲述与FrameRate相关的知识。

    [XNA]XNA基础(01) —— 游戏循环

    mikel阅读(832)

         当安装好了VS 2008和XNA GameStudio 3.0后,我们就可以开始学习XNA了。

         首先,在VS 2008中新建一个XNA GameStudio 3.0项目(选择Windows Game类型),会生成一个最简单的、可运行的游戏模板。

         接下来我们将注意力转移到我们要剖析的重点 —— 从Microsoft.Xna.Framework.Game继承的Game1类,其代码如下:          

     public class Game1 : Microsoft.Xna.Framework.Game
        {
            GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            
    public Game1()
            {
                graphics 
    = new GraphicsDeviceManager(this);
                Content.RootDirectory 
    = "Content";
            }
           
            
    protected override void Initialize()
            {            
                
    base.Initialize();
            }
           
            
    protected override void LoadContent()
            {            
                spriteBatch 
    = new SpriteBatch(GraphicsDevice);           
            }
            
            
    protected override void UnloadContent()
            {           
            }
          
            
    protected override void Update(GameTime gameTime)
            {           
                
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                    
    this.Exit();
                
    base.Update(gameTime);
            }
          
            
    protected override void Draw(GameTime gameTime)
            {
                GraphicsDevice.Clear(Color.CornflowerBlue);
                
    base.Draw(gameTime);
            }
        }

     

            我们简单解释一下该类中所用到的重要类型。

    GraphicsDeviceManager 图形设备管理器,用于访问图形设备的通道。

    GraphicsDevice 图形设备。

    Sprite 精灵,绘制在屏幕上的的2D3D图像,比如游戏场景中的一个怪兽就是一个Sprite

    SpriteBatch 它使用同样的方法来渲染一组Sprite对象。

     

            而Microsoft.Xna.Framework.Game 这个基类就像是一个基础框架,它把整个游戏流程串联了起来,即,整个游戏会按如下流程运转。

                 

            该图中的五个方法正好对应着Game1类中的五个方法,它们的作用解释如下。

         Initialize方法用于初始化与游戏相关的对象,比如初始化图形设备、游戏环境设置等。

         LoadContent方法在Initialize方法之后调用,它用于加载游戏所需要的图形或其它素材,比如模型、图片、声音等。

         UpdateDraw方法构成了游戏循环

         Update方法用于改变和控制游戏的状态,主导着游戏逻辑的进行。

         Draw方法用于在屏幕上绘制我们的场景、Sprite。要注意的是,我们应该尽可能少的在Draw方法中处理游戏逻辑——它们应该在Update方法中被处理。Draw方法仅仅负责绘制。

         UpdateDraw方法都接受一个GameTime类型的参数,GameTime有什么作用了?这个参数可以帮助我们依据实际的游戏时间而不是处理器的速度来决定动画或其它事件的发生时刻。

          在这个最简单的例子中,游戏将以默认的60fpsFrame/Second)运行。

         UnloadContent方法在游戏结束时被调用,该方法用于卸载在LoadContent方法中所加载的素材和那些需你要进行特别处理的善后事宜。

     

         状态轮询与事件监听

         写过Windows应用程序的朋友都知道,当用鼠标点击Form上的一个Button时,会触发一个Click事件,而我们的应用程序通过监听到事件的发生进而来处理事件。

         而在游戏开发中,我们需要将我们的这种“事件”思维切换到“轮询”思维。也就是说,游戏中,用户的鼠标、键盘操作并不会触发相关的事件。如果是这样的话,那我们该如何知道用户是否按下了鼠标了?答案是我们需要在游戏循环中(确切的说是在Update方法中)不断地检测输入设备(比如鼠标)的状态来进行判断。

           这就是轮询机制与事件机制的区别,也是游戏开发和普通windows应用开发需要转换思路的地方。

           归根到底,windows事件机制也是对轮询机制(即Windows消息循环)的一个封装

           

           今天的介绍就到这里,下一节我们将介绍与渲染相关的基础知识。   

           最后,附上XNA GameStudio 3.0的下载地址:http://creators.xna.com/en-us/xnags_islive

     

    [SQL]SQL Server 2005 中使用正则表达式匹配

    mikel阅读(1119)

    SQL Server 2005 中使用正则表达式匹配

    CLR 用户定义函数只是在 .NET 程序集中定义的静态方法。Create FUNCTION 语句已扩展为支持创建 CLR 用户定义函数。

    1、创建数据库项目

      

    2、添加用户定义函数

      

    以下是演示代码:

    Code
    using System;
    using System.Data;
    using System.Data.SQLClient;
    using System.Data.SqlTypes;
    using Microsoft.SQLServer.Server;
    using System.Text.RegularExpressions;
    // 示意代码
    public partial class UserDefinedFunctions
    {
        
    public static readonly RegexOptions Options = RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline;
        [Microsoft.SQLServer.Server.SqlFunction]
        
    public static string RegexValue(SqlChars input, SqlString pattern)
        {
            Regex regex 
    = new Regex(pattern.Value, Options);
            
    return  regex.Match(new string(input.Value)).Value;
        }
    }

     

    3、将自定义函数关联到数据库

      

    4、Sql 查询分析器

        

    为了确保SQL可以执行托管代码,执行下面的语句:

    EXEC sp_configure 'clr enabled', 1

    sql 如下:
    select dbo.RegexValue('2008-09-02',N'\d{4}') from Table

    =============================================

    [C#]C#4.0的dynamic代替反射的第一个应用实例——日志跟踪器

    mikel阅读(1053)

    在平时做框架架构设计的时候,头疼之一的是处处得采用反射,但有了C#4.0,发现dynamic完全可以取代反射,这个功能让我有些激动,立马在VS2010将日志跟踪器框架里的第一个反射的代码升级到C#4.0,结果一点都不令人失望,代码简化了很多。
    先看看用dynamic替换反射后的代码吧: 

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Reflection;
     6 using System.IO;
     7 /********************************
     8  * Updated by Lihua at 03/13/2009
     9  * 
    10  * 更新功能:
    11  * 1. 升级到C#4.0,加入dynamic代替反射
    12  * 2. 如果Zivsoft.Log.dll不存在,日志不输出
    13  * 3. 项目编译不依赖于Zivsoft.Log.dll
    14  ****************************************/
    15 namespace Zivsoft.Data
    16 {
    17     /// <summary>
    18     /// 只提供持久数据层框架里的类使用
    19     /// </summary>
    20     internal class Logger
    21     {
    22         private static Assembly _assemblyFile;
    23         private static dynamic _logger;
    24         static Logger()
    25         {
    26             var strDllFile = AppDomain.CurrentDomain.BaseDirectory + "Zivsoft.Log.dll";
    27             if (File.Exists(strDllFile))
    28             {
    29                 _assemblyFile = Assembly.LoadFile(strDllFile);
    30                 try
    31                 {
    32                     _logger = _assemblyFile.CreateInstance("Zivsoft.Log.Logger"false, BindingFlags.CreateInstance, nullnullnullnull);
    33                 }
    34                 catch {
    35                     _logger = null;
    36                 }
    37             }
    38         }
    39 
    40         public static void LogInfo(string message, params object[] args)
    41         {
    42             if (null != _logger)
    43             {
    44                 _logger.LogInfo(message, args);
    45             }
    46         }
    47 
    48         public static void LogWarning(string message, params object[] args)
    49         {
    50             if (null != _logger)
    51             {
    52                 _logger.LogWarning(message, args);
    53             }
    54         }
    55 
    56         public static void LogError(string message, params object[] args)
    57         {
    58             if (null != _logger)
    59             {
    60                 _logger.LogError(message, args);
    61             }
    62         }
    63 
    64         public static void LogDebug(string message, params object[] args)
    65         {
    66             if (null != _logger)
    67             {
    68                 _logger.LogDebug(message, args);
    69             }
    70         }
    71 
    72         public static void LogError(Exception e)
    73         {
    74             LogError("{0}", e);
    75         }
    76     }
    77 }
    78 

     

    以上是持久数据层调用日志跟踪器的入口代码,以前采用反射,刚被我用dynamic改了过来,经调试一点问题都没有,所以这让我欣喜,因为接下来的 我的很多框架采用反射的机制将都可能被dynamic替换,比如《持久数据层框架》中的被反射的SQLServer, MySQL, Access等等数据库。

    不多写了,大家仔细体会吧。