[原创]nvelocity模板调用.NET类的方法代码

mikel阅读(1469)

最近利用NVelocity+MVC进行开发过程中,发现NVelocity没有四舍五入的函数,利用js进行页面处理又太慢,如果可以调用.NET类的方法就太完美了。

于是Google了一下,发现篇Velocity调用Java代码的文章:

velocity模板开发中减法 加法

既然NVeclocity是从Velocity来的,自然少不了这个功能了,于是将ASP.NET的代码修改如下:

public class MatchTool

{

/// <summary>

/// 四舍五入

/// </summary>

/// <param name="d"></param>

/// <param name="decimals"></param>

/// <returns></returns>

public Decimal Round(decimal d, int decimals)

{

return System.Math.Round(d,decimals);

}

}

//添加C#的Math数字计算实例给模板用于四舍五入

AddContext("MathTool",new MatchTool());

由于C#的System.Math是静态类型,不能实例化赋值给模板标签$MathTool,于是给包装了一下,实例化了个MathTool类封装了Round方法,于是将它的实例赋值给模板标签MathTool

模板代码调用代码如下:

$MathTool.Round($order.UnitPrice,2)

另外NVelocity也可以调用.NET的String类型的方法,例如String类型的CutString方法

代码如下:

$Str.Cutstring(0,20) 截取20个字符

[转载]项目开发总结:前端开发部分总结[兼容性、DOM操作、跨域等]

mikel阅读(986)

[转载]项目开发总结:前端开发部分总结[兼容性、DOM操作、跨域等]

项目背景:.Net 3.5+MySQL+JQuery+WebService

在公司做这个项目已经6个多月了,总结一些问题,也算 是抛砖引玉吧,希望园子里更多的朋友一起分享一些技巧。

1、 WebService方法返回值不能为void。

当 WebService方法返回值为void时,FF和Chrome会持续等待,认为这个请求没有结束,而在IE中一切是正常的。

2、 当input的type=”button”时或者使用button时,点击后会触发form的submit。

当时查找页面刷新的问题找了很 久,最后才发现是button会自动触发form的submit,导致当前页面的刷新。

3、js设置document.domain只能从 二级域名设置到顶级域名。

比如访问moozi.cnblogs.com,可以在这个页面中写 document.domain=’cnblogs.com’;如果把域设置为cnblogs.com之后,无法再使用 document.domain=’moozi.cnblogs.com’;。

4、使用DOM片段创建iframe,当跨域时,不能用js 写iframe的域。

比如你在moozi.cnblogs.com/test.html页面用js把域设置为 document.domain=’cnblogs.com’,而此时你再用var ifrm=createElement(‘iframe’) ,这时将不能用js设置iframe的域也为’cnblogs.com’,只能让ifrm.src=’test2.htm’,并在test2.htm中设 置域document.domain=’cnblogs.com’。

5、当你修改过当前域的时候,CKEditor是正常的,而 tinyMCE需要重新设置域。

CKEditor能自动识别当前域,并不出错。tinyMCE需要人为的设置。同时,如果你是用 CKFinder上传文件,而需要修改它的输入代码,不然会因为域不同而出现没有权限的错误提示。

6、使用tinyMCE,尽管你的初始化 代码只有一句,但是可能出现多次初如化。发生原因未知,在不确定的情况下会出现此BUG。

7、开发js组件的时候,要时刻注意闭包 和this的指代,这方面之前吃了不少亏。

8、将字符串转换成JQuery对象时,要注意字符串html代码的闭合标签。

比 如:var html='<span>test</span>&nbsp;’var jqEl=$(html);此时,jqEl会把html里的’&nbsp;’丢掉。

9、jQuery1.2.6和 jQuery1.3在细节的处理上有一些区别。

jQuery1.2.6处理hasClass(‘.className’)会出错,而 jQuery1.3不会。

10、尽量少用jQuery的each,多用js原生的代码,性能较高。

———————-

先 写到这里吧…继续更新

[转载]Asp.net MVC并不仅仅只是Linq to SQL

mikel阅读(1589)

[转载]【译】Asp.net MVC并不仅仅只是Linq to SQL – CareySon – 博客园.

很多ASP.NET的教程中的示例代码使用的数据访问方法是Linq to SQL或是Entity Framework。我在www.asp.net的论坛上看到很多关于讨论是否有其他替代的数据库访问方式,回答 是:当然有。这篇文章就讲述了使用Ado.Net作为数据访问层来实现一个典型的增删查改程序。

由于是以练习作为目的,那我就不妨借用Spaanjaar’s 的N层构架文章(Building Layered Web Applications with Microsoft ASP.NET 2.0.)的构架方式。我强烈推荐你阅读他 的系列文章,如果嫌太长起码也得看完前两部分,这样就能对N-Layer构架有个基本的认识。N-Layer构架的三个关键层分别为:业务对象层,业务逻 辑层和数据访问层。而其数据访问层会几乎不加改变的包含在本文的MVC项目中,Spaanjaar的文件详细描述了各个层是如何组织的。这篇文章仅仅讲述 各个层所扮演的角色,但是不会深入到代码的细节中。

首先,我们来看Imar提供的程序,这是一个具有典型增删查改的程序,这个程序允许用户管理联系人,包括联系人的地址,电话,email。它能增,删, 查,改任何实体。

程序内包括的实体有:ContactPersons, PhoneNumbers, Addresses EmailAddresses.他们都隶属于程序的业务对象(BO)层。上述的每一个类都包含可以获取或者赋值的属性,但并不包含任何方法。而所有方法存 放于业务逻辑层(BLL)中的对应类中。在业务对象层和业务逻辑层的实体和实体manger是一对一的关系,在业务逻辑层中类包含的方法都会返回业务对象 层(BO)的实例,或是实例集合,或者保存实例(更新或是添加),或是删除实例。业务逻辑层(BLL)中也可以包含一些业务规则验证,安全性检查的代码。 但在本篇文章为了简便起见,就不添加这些了。如果你对业务规则和安全性有兴趣的话,可以去看Imar文章的6 part series

最后一层是数据访问层(DAL),同样,DAL层的类也和业务逻辑层(BLL)内的类有着一对一的关系,在BLL层的类中会调用相关DAL层中的方法。而 在这些层中,只有DAL层需要知道利用什么技术(linq,entity framework..)保存业务实体。在本例中,使用SQL Server Express数据库和Ado.net。而这样分层的思想是如果你需要更换数据源(XML,oracle,更或者是Web Service甚至是Linq to Sql或者其他ORM框架),因为DAL层给BLL层暴漏的方法的签名是一致的,所以只需要更换DAL层即可。而为了保证所有DAL的实现有着同样的签 名,则利用接口即可。但我想或许是未来帖子中讨论的话题了吧。

MVC构架

已经有很多优秀的文章中已经探讨了MVC程序的构架,所以本篇文章就不再累述相关细节了。如果想要了解更多,我推荐访问Asp.net MVC官方站点.简单二代说,M代表Model,也是包含 BO,BLL,DAL的地方,V代表View,也是UI相关开发的部分,或者说是用户看到的部分,C是Controller的简写,也是控制用户请求与程 序回复的部分。如果用户点击了一个指向特定地址的按钮,请求会和Controller的Action(类的方法)进行匹配,而Action负责处理请求, 并返回响应。通常情况下是一个新的View,或者是更新现有的View。

下面用Visual Studio创建一个MVC应用程序并删除默认的View和Controller,然后将Imar的程序中的Bo,Bll和DAL复制到这个MVC程序的 Model内,我还复制了响应的数据库文件和Style.css。

我还做了 一些其他的修改,把数据库连接字符串添加到Web.Config中。除此之外,我还将复制过来的代码的命名空间做了响应的调整并把DAL层的代码升级到了 3.0.虽然这并不是必须的。做完这些,我按Ctrl+Shift+F5来测试是否编译成功。——————

Controller

我 添加了4个Controller(Visual Studio附带的默认Controller已经被删除),和四个实体类相匹配。它们分别为:ContactController, PhoneController, AddressController and EmailController。

每个 Controller都含有四个Action:List, Add, Edit and Delete。首先需要在Global.exe中为这些Action注册这些路由。

public static void RegisterRoutes(RouteCollection routes)
{
  routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

  routes.MapRoute(
      "Default",                                              
      "{controller}/{action}/{id}",                           
      new { controller = "Contact", action = "List", id = " " }  
  );
}

默认的View会显示所有联系人,BLL层中的ContactPersonManager类内的GetList()方法会获取所有联系人的数 据,响应的List() Action代码如下:

public ActionResult List()
{
  var model = ContactPersonManager.GetList();
  return View(model);
}

强类型View

整个实例代码中,我都会使用强类型的View,因为这样不仅可以使用intellisense(智能提示)的好处,还不必依赖于使用String作为索引 值的ViewData。为了让代码更加有序,我添加了一些命名空间到web.config中的<namespaces>节点,这些都是我用来 替换Imar代码的方式:

<namespaces>
<add namespace="System.Web.Mvc"/>
<add namespace="System.Web.Mvc.Ajax"/>
<add namespace="System.Web.Mvc.Html"/>
<add namespace="System.Web.Routing"/>
<add namespace="System.Linq"/>
<add namespace="System.Collections.Generic"/>
<add namespace="ContactManagerMVC.Views.ViewModels"/>
<add namespace="ContactManagerMVC.Models.BusinessObject"/>
<add namespace="ContactManagerMVC.Models.BusinessObject.Collections"/>
<add namespace="ContactManagerMVC.Models.BusinessLogic"/>
<add namespace="ContactManagerMVC.Models.DataAccess"/>
<add namespace="ContactManagerMVC.Models.Enums"/>
</namespaces>

上面代码意味着我可以在程序的任何地方访问上述命名空间。上面GetList()方法返回的类型是ContactPersonList,这个类型是 ContactPerson对象的集合,在显示List的View中的Page声明如下:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
    Inherits="System.Web.Mvc.ViewPage<ContactPersonList>" %>

我想你已经注意到了我使用了MasterPage,利用MasterPage,我借用了从Imar实例代码中的Css文件。用于显示 ContactPerson对象的HTML如下:

<table class="table">
    <tr>
      <th scope="col">Id</th>
      <th scope="col">Full Name</th>
      <th scope="col">Date of Birth</th>
      <th scope="col">Type</th>
      <th scope="col">&nbsp;</th>
      <th scope="col">&nbsp;</th>
      <th scope="col">&nbsp;</th>
      <th scope="col">&nbsp;</th>
      <th scope="col">&nbsp;</th>  
    </tr>
    <%
      if (Model != null)
      {
        foreach (var person in Model)
        {%>
    <tr>
      <td><%= person.Id %></td>
      <td><%= person.FullName %></td>
      <td><%= person.DateOfBirth.ToString("d") %></td>
      <td><%= person.Type %></td>
      <td title="address/list" class="link">Addresses</td>
      <td title="email/list" class="link">Email</td>
      <td title="phone/list" class="link">Phone Numbers</td>
      <td title="contact/edit" class="link">Edit</td>
      <td title="contact/delete" class="link">Delete</td>
    </tr>
    <%
        }
      }else{%>
    <tr>
      <td colspan="9">No Contacts Yet</td>
    </tr>  
     <% }%>
  </table>

我想你已经能发现强类型View的好处了吧,Model的类型是ContactPersonList,所以你可以任意使用 ContactPersonList的属性而不用强制转换,而强制转换错误只有在运行时才能被发现,所以这样省了不少事。

在做Html时,我小小的作弊了一下,我本可以使用vs对list自动生成的view,但我没有。因为我需要和Imar的css文件相匹配的html.所 以我运行了imar的程序,在浏览器中查看源代码,并把生成的html复制下来,imar使用GridView来生成列表,所以CSS会在运行时嵌入到 html代码中。我将这些css代码移到一个外部的css文件中。并给<th>和<td>元素添加了一些额外样式,你可以在代码 的下载链接中找到。

我还未一些表格的单元格添加了title属性。这些包含了访问其他action的链接。我希望用户在浏览电话本或者编辑或者删除电话本时页面不会post back,换句话说,我希望我的网站是ajax的。而title属性就发挥作用了。而“。link”这个css选择符可以让普通的文本看起来像超链接,也 就是有下划线和鼠标hover时出现小手。

JQuery AJax

在我们深入实现ajax功能的相关代码之前,下面3行代码是需要加入到html中:

<input type="button" id="addContact" name="addContact" value="Add Contact" />
<div id="details"></div>
<div id="dialog" title="Confirmation Required">Are you sure about this?</div>

第一行代码的作用是一个允许用户添加新联系人的按钮,第二行代码是一个空div,方便后面显示信息,第三行代码是JQuery modal 提示验证对话框的一部分,用于提示用户是否删除一条记录。

还需要在页面中添加3个js文件,第一个是主要的jQuery文件,其他两个分别是jQuery UI的核心js,以及date picker和modal dialog部分的js

<script src="../../Scripts/jquery-1.3.2.min.js" type="text/javascript"></script>
<script src="../../Scripts/ui.core.min.js" type="text/javascript"></script>
<script src="../../Scripts/jquery-ui.min.js" type="text/javascript"></script>

下面是我们生成后的列表显示视图以及完全的js代码:

<script type="text/javascript">
  $(function() {
    // row colours
    $('tr:even').css('background-color', '#EFF3FB');
    $('tr:odd').css('background-color', '#FFFFFF');
    // selected row managment
    $('tr').click(function() {
      $('tr').each(function() {
        $(this).removeClass('SelectedRowStyle');
      });
      $(this).addClass('SelectedRowStyle');
    }); 
    // hide the dialog div
    $('#dialog').hide();
    // set up ajax to prevent caching of results in IE
    $.ajaxSetup({ cache: false });
    // add an onclick handler to items with the "link" css class
    $('.link').live('click', function(event) {
      var id = $.trim($('td:first', $(this).parents('tr')).text());
      var loc = $(this).attr('title');
      // check to ensure the link is not a delete link
      if (loc.lastIndexOf('delete') == -1) {
        $.get(loc + '/' + id, function(data) {
          $('#details').html(data);
        });
      // if it is, show the modal dialog   
      } else {
        $('#dialog').dialog({
          buttons: {
            'Confirm': function() {
              window.location.href = loc + '/' + id;
            },
            'Cancel': function() {
              $(this).dialog('close');
            }
          }
        }); 
        $('#dialog').dialog('open');
        }
      }); 
      // add an onclick event handler to the add contact button
      $('#addContact').click(function() {
        $.get('Contact/Add', function(data) {
          $('#details').html(data);
        });
      }); 
    });
</script>

上面代码看起来让人望而却步,但实际上,使用jQuery,这非常简单。并且在上面代码中我多处加入了注释,代码一开始是用js代码是替换了数据源 控件默认呈现出来的隔行变色的颜色。然后我加入了使点击行点击时可以变色的代码。然后下面的代码是防止IE对页面进行缓存。如果不禁止了IE缓存,你会为 执行编辑或者删除以后,数据库改变了但页面却没有发生变化而撞墙的。

接下来的代码更有趣了,依靠live()方法可以确$选择器中所有匹配的元素都被附加了相应的事件,无论元素当前是否存在于页面中。比如说,当你点击了 Addresses链接,相应的结果就会在另一个表中出现:

上图中可以看出表中包含编辑和删除链接,如果不使用live()方法,相应的链接就不会被附加事件。所有具有class为link的元素都会被委派上 click事件。这个事件会首先获取到当前条目的id.以ContactPerson表为例,这个id就是ContactPersonId.在相应的子表 中,则id会是电话号码或者是email地址。这些都是需要传递给controller action作为参数进行编辑,删除,或者显示的。

你现在可以看出为什么我为每个单元格加上title属性了吧。title属性包含相关的route信息,而完全的url则把id附加到route作为 url.然后,上面js会做一个检查,来看route信息内是否包含delete,如果包含delelte,则弹出一个对话框来提示用户是否要删除。看, 是不是很简单?

最后,代码会为添加联系人按钮添加click事件,在文章的下部分我们再进行讨论

添加联系人以及自定义View Models

使用ASP.NET添加一条记录时,通常的做法是提供一个包含一系列输入框的form.对于ContactPerson类的大多属性来说,都是可以直接赋 值的,比如:姓,名字,生日。但是其中有一个属性却不能直接赋值–Type属性,这个值是从PersonType.cs(朋友,同事等)中的枚举类型中 选择出来的.这意味着用户只能从有限的几种选项中选择一种。可以用DropDwonList来实现,但是ContactPerson对象并不包含枚举类型 的所有选项,所以我们只能提供一个自定义版本的ContactPerson类传递给View.这也是为什么要用到自定义View Model

我看过很多关于在程序的哪里放置View Models的讨论,有些人认为自定义View Models是Model的一部分,但是我认为它和View紧密相关,View Models并不是MVC程序必不可少的一部分,也不能服用,所以它不应该放到Model中区。我把所有的View Model放入一个创建的ViewModel文件夹下,ContactPersonViewModel.cs的源代码如下:

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

namespace ContactManagerMVC.Views.ViewModels
{
  public class ContactPersonViewModel
  {
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string MiddleName { get; set; }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; set; }
    public IEnumerable<SelectListItem> Type { get; set; }
  }
}

看上面类的最后一个属性,我将这个type属性设置成IEnumerable<SelectListItem>类型,这样就可以将Type绑 定到View中的Html.DropDownList了。

对应的,也要在Controller中添加2个action,第一个action使用AcceptVerbs(HttpVerbs.Get)标签,第二个 action使用AcceptVerbs(HttpVerbs.Post)标签,第一个方法用于从服务器向客户端传值,第一个方法处理从form提交的数 据。

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Add()
{
  var personTypes = Enum.GetValues(typeof (PersonType))
    .Cast<PersonType>()
    .Select(p => new
                   {
                     ID = p, Name = p.ToString()
                   });
  var model = new ContactPersonViewModel
                {
                  Type = new SelectList(personTypes, "ID", "Name")
                };
  return PartialView(model);
}


[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Add(ContactPerson person)
{
  ContactPersonManager.Save(person);
  return RedirectToAction("List");
}

上面第一个action的前几行代码将ContactType枚举类型转换成数组,数组中的每一个元素都是一个包含id和name属性的匿名对象,id是 枚举值,Name是和对应枚举匹配的constant值,ContactPersonViewModel然后进行初始化并且Type属性被赋值相应的类 型。我使用Partial View来添加联系人和使用ContactPersonViewModel类型的强类型,对应View部分的代码如下:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ContactPersonViewModel>" %>

<script type="text/javascript">
  $(function() {
  $('#DateOfBirth').datepicker({ dateFormat: 'yy/mm/dd' });
  });
</script>

<% using (Html.BeginForm("Add", "Contact", FormMethod.Post)) {%>
      <table>
        <tr>
          <td class="LabelCell">Name</td>
          <td><%= Html.TextBox("FirstName") %></td>
        </tr>
        <tr>
          <td class="LabelCell">Middle Name</td>
          <td><%= Html.TextBox("MiddleName") %></td>
        </tr>
        <tr>v
          <td class="LabelCell">Last Name</td>
          <td><%= Html.TextBox("LastName") %></td>
        </tr>
        <tr>
          <td class="LabelCell">Date of Birth</td>
          <td><%= Html.TextBox("DateOfBirth", String.Empty)%></td>
        </tr>
        <tr>
          <td class="LabelCell">Type</td>
          <td><%=Html.DropDownList("Type")%>
          </td>
        </tr>
        <tr>
          <td class="LabelCell"></td>
          <td><input type="submit" name="submit" id="submit" value="Save" /></td>
        </tr>
      </table>
<% } %>

在最上面的代码我使用了jQuery UI的Date picker插件作为DateOfBirth输入框的选择方式,而DateOfBirth的第二个参数保证这个textbox在初始状态下为空。此外,所 有的输入框对ContactPerson的对应属性名相同,这是为了确保默认的model binder不出差错,MVC还自动为ContactType枚举进行绑定。

负责响应Post请求的方法可以自动将Request.Form的值和ContactPerson对象的对应属性进行匹配,然后调用BLL层的 Save()方法,然后RedicrectToAction会让页面刷新最后调用List这个action.

编辑一个联系人

和前边一样,需要在Controller添加两个action来完成编辑,一个用于响应Get请求,另一个用于响应Post请求:

[AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Edit(int id)
    {
      var personTypes = Enum.GetValues(typeof (PersonType))
        .Cast<PersonType>()
        .Select(p => new { ID = p, Name = p.ToString() });

      var contactPerson = ContactPersonManager.GetItem(id);
      var model = new ContactPersonViewModel
                    { 
                      Id = id,
                      FirstName = contactPerson.FirstName,
                      MiddleName = contactPerson.MiddleName,
                      LastName = contactPerson.LastName,
                      DateOfBirth = contactPerson.DateOfBirth,
                      Type = new SelectList(personTypes, "ID", "Name", contactPerson.Type)
                    };
      return PartialView(model);
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Edit(ContactPerson person)
    {
      ContactPersonManager.Save(person);
      return RedirectToAction("List");
    }

我们在前面已经看到jQuery是如何调用Edit这个action并把被编辑人的id作为参数进行传递的了,然后id用于通过众所周知的bll调用 DAL从数据库将联系人的详细信息取出来。在这个过程中ContactPersonViewModel被创建来返回从数据库取出的 值,SelectList的通过三个参数构造,第一个参数是Person的类型,第三个参数是DropDownList当前选择的值

partial view的代码和Add View几乎一样:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ContactPersonViewModel>" %>

<script type="text/javascript">
  $(function() {
    $('#DateOfBirth').datepicker({dateFormat: 'yy/mm/dd'});
  });
</script>

<% using (Html.BeginForm("Edit", "Contact", FormMethod.Post)) {%> 
     <table>
        <tr>
          <td class="LabelCell">Name</td>
          <td><%= Html.TextBox("FirstName") %></td>
        </tr>
        <tr>
          <td class="LabelCell">Middle Name</td>
          <td><%= Html.TextBox("MiddleName") %></td>
        </tr>
        <tr>
          <td class="LabelCell">Last Name</td>
          <td><%= Html.TextBox("LastName") %></td>
        </tr>
        <tr>
          <td class="LabelCell">Date of Birth</td>
          <td><%= Html.TextBox("DateOfBirth", Model.DateOfBirth.ToString("yyyy/MM/dd")) %></td>
        </tr>
        <tr>
          <td class="LabelCell">Type</td>
          <td><%= Html.DropDownList("Type")%></td>
        </tr>
        <tr>
          <td class="LabelCell"><%= Html.Hidden("Id") %></td>
          <td><input type="submit" name="submit" id="submit" value="Save" /></td>
        </tr>
      </table>
<% } %>

关键不同是DateOfBirth包含一个将生日信息转换更容易识别的方式,还有在提交按钮附近添加了一个Html.Hidden(),用于保存被 编辑用户的id。当然,form的action属性肯定是和Add的View不同。在Form中可以加一个参数用于告诉Action是Add View还是Edit View,这样就能减少代码的重复,但是在示例代码中我还是把他们分成了不同的action,来让职责划分的更清晰一些。

删除一个联系人

删除action就简单多了,并且不需要与之相关的View.仅仅是重新调用List这个action,被删除的数据就不复存在了。

public ActionResult Delete(int id)
{
  ContactPersonManager.Delete(id);
  return RedirectToAction("List");
}

上面代码是我对BLL和DAL做出改变的地方,原来的ContactPersonManager.Delete()方法的参数是Person的实例,而在 DAL中,只有id作为参数。我看不出传递整个对象给BLL,但BLL却只传递对象的唯一ID给DAL的意义所在,所以我将BLL的代码改成接受int类 型的参数,这样写起来会更简单。

当删除链接被点击时,调用jQuery的confirmation modal dialog:

如果点击取消,则什么事也不发生,如果点击确认,则链接就会指向响应的delete action.

管理集合

所有的集合–PhoneNumberList,EmailAddressLIst,AddressList被管理的方式如出一辙。所以,我仅仅挑选 EmailAddressLIst作为示例来说明方法,你可以下载示例代码来看其他部分。

首先,我们来看显示被选择联系人的email地址,这包含了controller中List这个Action:

public ActionResult List(int id)
{
  var model = new EmailAddressListViewModel
                {
                  EmailAddresses = EmailAddressManager.GetList(id),
                  ContactPersonId = id
                };
  return PartialView(model);
}

上面方法使用联系人的id作为联系人,并且返回一个自定义View Model—EmailAddressListViewModel.这也是将联系人的id传到View的方法:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<EmailAddressListViewModel>" %>
<script type="text/javascript">
  $(function() {
    $('#add').click(function() {
      $.get('Email/Add/<%= Model.ContactPersonId %>', function(data) {
        $('#details').html(data);
      });
    });
  });
</script>
<table class="table">
   <tr>
     <th scope="col">Contact Person Id</th>
     <th scope="col">Email</th>
     <th scope="col">Type</th>
     <th scope="col">&nbsp;</th>
     <th scope="col">&nbsp;</th>
   </tr>
   <%if(Model.EmailAddresses != null)
     {foreach (var email in Model.EmailAddresses) {%>
   <tr>
     <td><%= email.Id %></td>
     <td><%= email.Email %></td>
     <td><%= email.Type %></td>
     <td title="email/edit" class="link">Edit</td>
     <td title="email/delete" class="link">Delete</td>
   </tr>
        <%}
    }else
 {%>
   <tr>
     <td colspan="9">No email addresses for this contact</td>
   </tr>
 <%}%>
</table>
<input type="button" name="add" value="Add Email" id="add" />

你可以看出添加方法需要ContactPersonID作为参数,我们需要确保可以添加新的联系人到响应的联系人列表。编辑和删除方法也是如此–id参 数通过url传递到相关action.而所有的单元格都有title属性,这样就可以使用前面部署的live()方法:

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Add(int id)
{
  var contactTypes = Enum.GetValues(typeof(ContactType))
    .Cast<ContactType>()
    .Select(c => new
    {
      Id = c,
      Name = c.ToString()
    });
  var model = new EmailAddressViewModel
                {
                  ContactPersonId = id,
                  Type = new SelectList(contactTypes, "ID", "Name")
                };
  return PartialView("Add", model);
}


[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Add(EmailAddress emailAddress)
{
  emailAddress.Id = -1;
  EmailAddressManager.Save(emailAddress);
  return RedirectToAction("List", new {id = emailAddress.ContactPersonId});
}

创建自定义View Model存在的目的是为了对现有的EmailAddress进行添加或编辑,这包括一些绑定 IEnumerable<SelectListItem>集合到Type dropdown的属性,上面两个Add不同之处在于他们的返回不同,第一个返回partial view,第二个返回List这个action.

集合中的每一个选在在一开始都会将id设为-1,这是为了保证符合”Upsert”存储过程的要求,因为Imar对于添加和删除使用的是同一个存储过程, 如果不设置成-1,则当前新建的联系人会更新掉数据库中的联系人。如果你想了解更多,请看他的文章。下面是添加email address的partial view:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<EmailAddressViewModel>" %>

<script type="text/javascript">
  $(function() {
    $('#save').click(function() {
      $.ajax({
        type: "POST",
        url: $("#AddEmail").attr('action'),
        data: $("#AddEmail").serialize(),
        dataType: "text/plain",
        success: function(response) {
          $("#details").html(response);
        }
      });
    });
  });
</script>

<% using(Html.BeginForm("Add", "Email", FormMethod.Post, new { id = "AddEmail" })) {%>
<table class="table">
<tr>
  <td>Email:</td>
  <td><%= Html.TextBox("Email")%></td>
</tr>
<tr>
  <td>Type:</td>
  <td><%= Html.DropDownList("Type") %></td>
</tr>
<tr>
  <td><%= Html.Hidden("ContactPersonId") %></td>
  <td><input type="button" name="save" id="save" value="Save" /></td>
</tr>
</table>
<% } %>

上面jQuery代码负责通过Ajax提交form.jQuery给html button(而不是input type=”submit”)附加事件,然后将页面中的内容序列化并通过post 请求发送到使用合适AcceptVerbs标签修饰的名为add()的action.

编辑和删除EmailAddress对象

编辑EmailAddress对象所涉及的action和前面提到的很相似,仍然是两个action,一个用于处理get请求,另一个用于处理post请 求:

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Edit(int id)
{
  var emailAddress = EmailAddressManager.GetItem(id);
  var contactTypes = Enum.GetValues(typeof(ContactType))
    .Cast<ContactType>()
    .Select(c => new
    {
      Id = c,
      Name = c.ToString()
    });
  var model = new EmailAddressViewModel
  {
    Type = new SelectList(contactTypes, "ID", "Name", emailAddress.Type),
    Email = emailAddress.Email,
    ContactPersonId = emailAddress.ContactPersonId,
    Id = emailAddress.Id
  };
  return View(model);
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(EmailAddress emailAddress)
{
  EmailAddressManager.Save(emailAddress);
  return RedirectToAction("List", "Email", new { id = emailAddress.ContactPersonId });
}

下面的partial View代码现在应该很熟悉了吧:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<EmailAddressViewModel>" %>

<script type="text/javascript">
  $(function() {
    $('#save').click(function() {
      $.ajax({
        type: "POST",
        url: $("#EditEmail").attr('action'),
        data: $("#EditEmail").serialize(),
        dataType: "text/plain",
        success: function(response) {
          $("#details").html(response);
        }
      });
    });
  });
</script>

<% using(Html.BeginForm("Edit", "Email", FormMethod.Post, new { id = "EditEmail" })) {%>
<table class="table">
<tr>
  <td>Email:</td>
  <td><%= Html.TextBox("Email")%></td>
</tr>
<tr>
  <td>Type:</td>
  <td><%= Html.DropDownList("Type") %></td>
</tr>
<tr>
  <td><%= Html.Hidden("ContactPersonId") %><%= Html.Hidden("Id") %></td>
  <td><input type="button" name="save" id="save" value="Save" /></td>
</tr>
</table>

上面代码仍然和Add View很像,除了包含EmailAddress.Id的html隐藏域,这是为了保证正确的email地址被更新,而Delete action就不用过多解释了吧。

public ActionResult Delete(int id)
{
  EmailAddressManager.Delete(id);
  return RedirectToAction("List", "Contact");
}

总结

这篇练习的目的是为了证明ASP.NET MVC在没有Linq To Sql和Entity framework的情况下依然可以很完美的使用。文章使用了现有分层结构良好的Asp.net 2.0 form程序,还有可重用的,business Objects层,bussiness logic层以及Data Access 层。而DAL层使用Ado.net调用Sql Server的存储过程来实现。

顺带的,我还展示了如何使用强类型的View Models和简洁的jQuery来让UI体验更上一层。这个程序并不是完美的,也不是用于真实世界。程序中View部分,以及混合编辑和删除的 action都还有很大的空间可以重构提升,程序还缺少验证用户输入的手段,所有的删除操作都会返回页面本身。而更好的情况应该是显示删除后用一个子表来 显示删除后的内容,这需要将ContactPersonId作为参数传递到相关action,这也是很好实现的。

—————————————————

原文链接:http://www.mikesdotnetting.com/Article/132/ASP.NET-MVC-is-not-all-about-Linq-to-SQL

translated by CareySon

[转载]LECCO SQL Expert工具之优化sql语句

mikel阅读(1212)

[转载]LECCO SQL Expert工具之优化sql语句

程序开发,特别是web开发,对性能的要求比较高,在线列表信息要快又要占用减少服务器资源,在大众化的网络中可以已较快的速度读取数 据,在程序方面优 化,主要只书写代 码质量,这里对代 码优化不进行讨论,此 篇主是针对数据库查询语句的优化.

编程开手或DBA通过自己的多年的经验可能重写性能低下 的SQL语句,但对于我这样菜鸟来说,写出高性能的SQL有些困难.…,不用怕,我现在介绍个工具, LECCO SQL Expert,这 个工具的下载及使用帮助让大家baidu,就有一大堆.这里不提供下载链接了.

好了,开始我们今天的话题:

1.打开软件,登录后,出现以下界面.

找到我要进行测试的sql语句,如下:

SELECT dbo.合同申请表.ID,

dbo.合同申请表.ApplyUser,

dbo.UserTable.UserName,

dbo.合同申请表.ApplyType,

dbo.合同申请表.Name,

dbo.合同申请表.Object,

dbo.合同申请表.Intention,

dbo.合同申请表.Delivery,

dbo.合同申请表.Howmuch,

dbo.合同申请表.Payment,

dbo.合同申请表.Transit,

dbo.合同申请表.Others,

dbo.合同申请表.Memo,

dbo.合同申请表.Status,

dbo.合同状态表.title AS StatusTitle,

dbo.合同申请表.Attime,

dbo.UserTable.Phone,

dbo.合同申请表.Serial

FROM dbo.合同申请表,

dbo.UserTable,

dbo.合同状态表

WHERE dbo.合同申请表.status = @nStatus

and dbo.合同申请表.ApplyType = @cType

and dbo.合同申请表.WorkerID = @nOfficer

AND dbo.合同申请表.Status = dbo.合同状态表.id

AND dbo.合同申请表.ApplyUser = dbo.UserTable.ID

2.将其copyLECCO SQL Expert sql编辑器中,如图:


3.
点击工具栏的优化按钮,输入参数(sql语句用的是变量@nStatus, @cType, @nOfficer),如图:

4.输入相应的变量值,点击确定,进行优化语句查询:如图:



找到5个可选的执 行计划.,批运行 这5sql,看运行时间最短的,其是最好的优化sql

5.批运行,并显示这5个运行时间

结果显示:sql4是最优的,其语句为:

SELECT dbo.合同申请表.ID,

dbo. 合同申请表.ApplyUser,

dbo.UserTable.UserName,

dbo. 合同申请表.ApplyType,

dbo. 合同申请表.Name,

dbo. 合同申请表.Object,

dbo. 合同申请表.Intention,

dbo. 合同申请表.Delivery,

dbo. 合同申请表.Howmuch,

dbo. 合同申请表.Payment,

dbo. 合同申请表.Transit,

dbo. 合同申请表.Others,

dbo. 合同申请表.Memo,

dbo. 合同申请表.Status,

dbo. 合同状态表.title AS StatusTitle,

dbo. 合同申请表.Attime,

dbo.UserTable.Phone,

dbo. 合同申请表.Serial

FROM dbo.合同申请表

INNER JOIN dbo.合同状态表

ON dbo.合同申请表.Status = dbo.合同状态表.id

INNER JOIN dbo.UserTable

ON dbo.合同申请表.ApplyUser = dbo.UserTable.ID

WHERE dbo.合同申请表.status = @nStatus

and dbo.合同申请表.ApplyType = @cType

and dbo.合同申请表.WorkerID = @nOfficer

OPTION (FORCE ORDER)

好了,本篇只是对LECCO SQL Expert软件进行简 单应用实例说明.ok,这次就到这了.

[转载]LECCO SQL Expert Pro for SQL Server提供下载

mikel阅读(1190)

[转载]LECCO SQL Expert Pro for SQL Server提供下载

写出专家级的SQL语句

LECCO SQL Expert的出现,使SQL的优化变得极其简单,只要能够写出SQL语句,它就能帮用户找到最好性能的写法。LECCO SQL Expert不仅能在很短的时间内找到所有可能的优化方案,而且能够通过实际测试,确定最有效的优化方案。同以往的数据库优化手段相比较,LECCO SQL Expert将数据库优化技术带到了一个崭新的技术高度,依赖人的经验、耗费大量时间、受人的思维束缚的数据库优化手段已经被高效、省时且准确的自动优化 软件所取代了。通过内建的“LECCO小助手”的帮助,即使是SQL的开发新手,也能快速且简单地写出专家级的SQL语句。
下载

[转载]企业定制软件开发的两个核心问题 - TAOWEN的一些个人记录 - 博客园

mikel阅读(1422)

[转载]企业定制软件开发的两个核心问题 – TAOWEN的一些个人记录 – 博客园.

企业定制软件开发不是计算机科学,需要解决的不是编译原理也不是组合数学。那么,企业定制软件开发的核心问题是什么?

越来越感觉到,从事一个领域不需要有特别深刻的理解,但起码要知道做这个领域的事情,需要解决的核心问题是什么。比如说,开发C/S结构软件,状态 同步(C/S状态同步以及窗口之间的状态同步)就是核心问题之一,而开发B/S结构的软件,状态同步就不是那么核心的问题。如果事先知道需要有这些核心问 题需要考虑,在日常应对接踵而来的具体的事务的时候,就能够把解决问题的层次抬到更宏观的层面。

目前而言,个人感觉企业定制软件开发的核心问题有两个:

1、如何保证所有参与者(包括客户在内的开发团队,以及最终用户)的沟通强度,使其能够满足完成开发目标的需要

2、如何管理企业定制带来的软件自身内在的高复杂度,使得复杂度不会超过团队的维护能力范围

在前面一篇介绍组 织的Blog中,谈到了核心问题中的第一个,沟通强度问题。团队而言,刨去个人能力,最重要的就是人与人之间的沟通。没有好的沟通,即便团队的个 体能力都超级强,也无法形成合力。但只要有好的沟通,至少可以做到人尽其用。假设一个标准人的生产力是1/day。某些特别强的人可以达到3/day。但 是如果需要达到10/day的生产力才能在市场允许的时间下完成项目,那么就无法用一个人来完成之间事情,所以才会有团队存在的必要。但是有两个标准人, 并不会达到1+1=2/day的生产力。可能只有1.5。有三个标准人,更加不会达到1+1+1=3/day的生产力,很有可能有1.8。那么是什么制约 了团队的整体生产力,那就是沟通。当两个相关联的任务A和B是一个人做的时候,关于任务A的知识存在于左脑,关于任务B的知识存在于右脑。那么结合两个任 务的知识把其组装起来的沟通效率就是大脑内部的电信号的速度。但是如何任务A是由Dev甲完成的,任务B是由Dev乙完成的,那么整合两个任务的效率就受 到人嘴皮的震动频率的约束,受到表达能力的约束,受到理解能力的约束。有人研究过,两个人即便是面对面,传输的比特率也要低于最早的拨号式Modem。更 不用说,有的时候开发团队是分布式的。在无法看到表情,肢体语言,无法共享白板,只能在越洋电话里听到声音,而且其中还有一方是非工作时间,在这种情况 下,1+1几乎很难达到1/day的生产力。什么是高效的团队,就是能够让1+1的值尽可能大的团队。如何变得高效,让沟通变得更加高效。怎么样让沟通变 得高效高强度?这就是我们要处理的核心问题。

第一个问题可以应用于所有的人的团队行为之中。人只要聚集成群,就会有沟通问题。所谓,有人的地方就有江湖。第二个问题则特定于企业定制软件开发。 对于互联应用开发,也许复杂度的管理是其次的,最需要关注的是大用户量下的可扩展性。但是对于企业定制软件开发,由于业务自身的复杂度,导致了定制软件的 复杂度。特别是业务的组合,导致的组合复杂性。假设在理想情况下,一个系统可以分解为模块A,B,C,其复杂度都是2。在复杂度管理良好的情况下 ,这些模块是被明确划分的,要理解A,只需要关注A,以及B与C少量与A交互的部分,也许理解的复杂度只是2 + 0.5.。但是在复杂度没有管理的情况下,所有的“逻辑”(也就是复杂度)都是随意地放置的。那么也就是没法办法保证,读A的逻辑只需要关注A,甚至这个 A都是不存在的,你看到的知识一个系统,包含了A,B,C的功能,是完整的一块。这个时候要真正了解这个系统行为,可能就需要2 * 2 * 2 = 8这么高的代价了。随着模块(变量,方法,类,包,模块,Bundle)的增加,我们需要同时理解的东西也在不断增加。不去可以地管理复杂度,很有可能, 我们需要了解一块功能,就需要把所有的代码都去阅读一遍。或者说,改动了一个方法,使得整个项目都需要重新被测试,因为没有地方是可以被信任的。如何管理 复杂度?这就是我们要处理的核心问题。

有意思的事情是,这两个核心问题是重叠的。把人,角色等同于类,接口,都抽象地看成点。把沟通理解为人与人之间的联系问题。把复杂度也理解为类与类 的依赖问题。那么沟通问题和复杂度的管理问题都是如何把这些点联上线,组成一个高效的图的问题。这个图,就是一张关于“依赖”的图。人与人之间的依赖,类 与类之间的依赖,包与包之间的依赖。依赖的另外的一个名字就是Coupling。而我们追求的就是Cohesion。Coupling(耦合)/ Cohesion(内聚)这两个词的妙处在于,明白的人根据自己的经验,一看就点头。不明白的人,由于没有对应的经验,无论怎么解释,都是摸不着头脑。正 是因为其“妙不可言”性,所以我可以说这两个词就是所有问题的答案(你也无法反驳)。但至少我们可以知道企业定制软件开发的核心问题其实就是一个:就是管 理好人与人之间的Dependency,包与包之间的Dependency,使得信息可以在高度依赖的人与人之间快速传递(强调Coupling带来的消 息传递的效率),而理解又可以局限在高度内聚的模块内部(强调Cohesion带来的维护便利),但同时又不能让某人过度被依赖倒置工作过劳死了,被依赖 得越多要求其体能越好,对于包的内聚也一样,高内聚做到极致就是最小的编译单元(类?),又会导致包的粒度过小,使得包的数量变得巨大,失去了维护的便利 性。我们需要做的,就是在To Depend or Not To Depend中,根据场景作出取舍。

那么抽象而言,无论是解决沟通问题还是复杂度问题都可以归纳为:

1、设置目标指标

2、度量现有的指标,观测现有的依赖图

3、做出依赖图的调整计划,并执行

4、观察指标的变化

5、重复步骤3,4,直到目标达到

但是问题是:

1、如何度量指标?沟通的效率?代码的质量?都很能度量。

2、如何观测现有的依赖图?包的依赖还可以观测,但是团队的协作是比较难观测的。

3、如何对依赖图做调整?重构?Change Agent?人不比代码那么容易改变。

4、如果指标不是简单数字,怎么比较?怎么知道指标是朝着目标发生变化?

这四个问题,几乎没有硬的科学问题。管理复杂系统的复杂度,可能是一门硬的科学。但是夹杂了人的因素的企业定制软件开发,一定不是一门硬的科学。那 么,数学公式不是这些问题的答案。那么该朝哪个方向努力?

TO BE CONTINUED

[原创]SWFUpload能上传视频不能提交表单的问题解决办法

mikel阅读(1301)

最近在做模板时候发现,原来能用的swfupload上传视频的功能,现在不能用了?!但是视频文件上传完成后不能提交表单信息,总是提示:“Error submit form”

开始以为是代码调用的问题,去除了不必要的js代码,问题依然存在!swfupload上传的流程可以通过调用代码分析出来,代码如下:
……..

upload_progress_handler: uploadProgress, //上传进度显示处理函数uploadProgress
upload_error_handler: uploadError,//上传错误处理函数uploadError
upload_success_handler: uploadSuccess,//上传成功处理函数uploadSuccess
upload_complete_handler: uploadComplete,//上传完成处理函数uploadComplete 问题应该就出在这个函数中
………..

于是打开handler.js中查看uploadComplete的代码如下:

function uploadComplete(file) {
try {
if (this.customSettings.upload_successful) {

this.setButtonDisabled(true);
uploadDone(); //这个就是完成上传文件后的处理函数了
} else {
file.id = “singlefile”;
var progress = new FileProgress(file, this.customSettings.progress_target);
progress.setError();
progress.setStatus(“File rejected”);
progress.toggleCancel(false);

var txtFileName = document.getElementById(“txtFileName”);
txtFileName.value = “”;
//validateForm();

alert(“There was a problem with the upload.\nThe server did not accept it.”);
}
} catch (e) {
}
}

继续查看uploadDone()函数的代码,代码如下:

// Called by the queue complete handler to submit the form
function uploadDone() {
try {

document.forms[1].submit();//罪魁祸首在此

居然遍历取当前页面的forms的第二个进行提交,不管是不是要提交的表单都提交

没有找到时候就提示:Error submitting form

}

catch (ex) {
alert(“Error submitting form”);
}
}

问题找到了于是准备进行如下修改:

1.swfupload引用时的customSettings设置上传文件完成后要提交的form的Id:upload_form

2.修改uploadComplete中读取customSettings设置的upload_form,传递给uploadDone()函数

3.修改uploadDone函数加入参数id,用于传递要提交的表单的ID进行提交操作。

具体代码如下:

1.设置代码

custom_settings : {
progress_target : “fsUploadProgress”,
upload_successful : false,
upload_form:”Upload”
},

2.uploadComplete()

if (this.customSettings.upload_successful) {

this.setButtonDisabled(true);
uploadDone(this.customSettings.upload_form);
} else {

3.uploadDone()

function uploadDone(id) {
try {

document.getElementById(id).submit();
}

catch (ex) {
alert(“Error submitting form”);
}
}

[C#]Dispose模式

mikel阅读(2022)

本文讲解的是你在建立包含内存以外资源的类型,特别是处置非内存资源的时候,如何编写自己的资源管理代码。
我们已经知道了处置那些占用非受控(unmanaged)资源的对象的重要性,现在应该编写资源管理代码来处置那些包含非内存资源的类型了。整 个.NET框架组件都使用一个标准的模式来处理非内存资源。使用你建立的类型的用户也希望你遵循这个标准的模式。标准的处理模式的思想是这样的:当客户端 记得的时候使用IDisposable接口释放你的非受控资源,当客户端忘记的时候防护性地使用终结器(finalizer)。它与垃圾收集器 (Garbage Collector)一起工作,确保只在必要的时候该对象才受到与终结器相关的性能影响。这是处理非受控资源的一条很好的途径,因此我 们应该彻底地认识它。
类层次体系中的根基类(root base class)必须实现IDisposable接口以释放资源。这个类型还必须添加一个作为防御机制的终结 器。所有这些程序都把释放资源的工作委托给一个虚拟的方法,衍生的类可以根据自己的资源管理需求来重载该方法。只要衍生的类必须释放自己的资源,并且它必 须调用该函数的基类版本的时候,它才需要重载这个虚拟方法。
开始的时候,如果你的类使用了非内存资源,它就必须含有一个终结器。你不能依赖客户端总是调用Dispose()方法。因为当它们忘记这样做的时候, 你就面临资源泄漏的问题。没有调用Dispose是它们的问题,但是你却有过失。用于保证非内存资源被正确地释放的唯一途径是建立终结器。
当垃圾收集器运行的时候,它立即从内存中删除所有不带终结器的垃圾对象。所有带有终结器的对象仍然存在于内存中。这些对象都被添加到终结队列,垃圾收 集器引发一个新线程,周期性地在这些对象上运行终结器。在这些终结程序线程完成自己的工作之后,就可以从内存中删除垃圾对象了。需要终结的对象在内存中停 留的时间比没有终结器的对象停留的时间长很多。但是你别无选择。如果要使程序有防护性,在类型包含非受控资源的时候,你必须编写一个终结器。但是也不用担 心性能问题。下一步确保了客户端避免与终结相关的性能开销。
实现IDisposable接口是一种标准的途径,它通知用户和运行时系统持有资源的对象必须及时地释放。IDisposable接口仅仅包含一个方法:
public interface IDisposable
{
void Dispose( );
}
你对IDisposable.Dispose()方法的实现(implementation)负责下面四个事务:
1、释放所有的非受控资源。
2、释放所有的受控资源(包括未解开事件)。
3、设置标志表明该对象已经被处理过了。你必须在自己的公共方法中检查这种状态标志并抛出ObjectDisposed异常(如果某个对象被处理过之后再次被调用的话)。
4、禁止终结操作(finalization)。你调用GC.SuppressFinalize(this)来完成这种事务。
通过实现IDisposable接口你完成了两个事务:你为客户端及时地释放自己持有的所有受控资源提供了机制;你为客户端提供了一种释放非受控资源 的标准途径。这是一个很大的进步。当你在类型中实现了Idisposable接口的时候,客户端可以避免终结操作的开销,你的类就成为.NET世界中的” 良民”了。
但是在你建立的这种机制中仍然存在一些问题。怎样在衍生类清理自己资源的时候同时也让基类能够清理资源?如果衍生类重载了终结操作,或者添加了自己的 IDisposable实现,那么这些方法必须调用基类,否则,基类就不能正确地进行清理操作。同样,finalize(终结操作)和Dispose参与 分担了一些相同的职责。Finalize方法和Dispose方法的代码几乎相同。而且在重载接口函数后并不像你预料的那样工作。标准的Dispose模 式中的第三个方法是一个受保护的虚拟辅助函数,它分解出这些共同的事务,并给衍生类添加一个用于释放资源的”钩子(hook)”。基类包含了核心接口的代 码。作为对Dispose()或终结操作的响应,该虚拟函数为衍生类清除资源提供了”钩子”:
protected virtual void Dispose( bool isDisposing );
这个重载的方法实现支持finalize和Dispose的必要事务,由于它是虚拟的,它为所有的衍生类提供了一个入口点。衍生类可以重载这个方法, 为清除自己的资源提供适当的实现,同时还可以调用基类版本。当isDisposing为真(true)的时候,你可以清除受控和非受控资源,当 isDisposing为假(false)的时候,你只能清除非受控资源。在这两种情况下,你都可以调用基类的Dispose(bool)方法,让它清除 自己的资源。
下面有一个简短的例子,它演示了你在实现这种模式的时候所提供的代码框架。MyResourceHog类演示了实现IDisposable接口、终结器的代码,并建立了一个虚拟的Dispose方法:
public class MyResourceHog : IDisposable
{
// 已经被处理过的标记
private bool _alreadyDisposed = false;
// 终结器。调用虚拟的Dispose方法
~MyResourceHog()
{
Dispose( false );
}
// IDisposable的实现
// 调用虚拟的Dispose方法。禁止Finalization(终结操作)
public void Dispose()
{
Dispose( true );
GC.SuppressFinalize( true );
}
// 虚拟的Dispose方法
protected virtual void Dispose( bool isDisposing )
{
// 不要多次处理
if ( _alreadyDisposed )
return;
if ( isDisposing )
{
// TODO: 此处释放受控资源
}
// TODO: 此处释放非受控资源。设置被处理过标记
_alreadyDisposed = true;
}
}
如果衍生类需要执行另外的清除操作,它应该实现受保护的Dispose方法:
public class DerivedResourceHog : MyResourceHog
{
// 它有自己的被处理过标记
private bool _disposed = false;
protected override void Dispose( bool isDisposing )
{
// 不要多次处理
if ( _disposed )
return;
if ( isDisposing )
{
// TODO: 此处释放受控资源
}
// TODO: 此处释放所有受控资源

// 让基类释放自己的资源。基类负责调用GC.SuppressFinalize( )
base.Dispose( isDisposing );
// 设置衍生类的被处理过标记
_disposed = true;
}
}
请注意,基类和衍生类都包含该对象的被处理过(disposed)标记。这纯粹是起保护作用。复制这个标记可以封装构成某个对象的所有类释放资源时产生的任何可能的错误。
你必须编写防护性的Dispose和finalize。对象的处理可以按任意次序进行,你可能会遇到在调用自己类型的成员对象的Dispose()方 法之前,该对象已经被处理过了。你不应该认为这是问题,因为Dispose()方法会被多次调用。如果它在已经被处理过的对象上被调用,它就不执行任何事 务。Finalizer(终结器)也有类似的规则。如果你引用的对象仍然存在于内存中,你就没有必要检查空引用(null reference)。但是,你引用的任何对象都可能被处理了,它也可能已经被终结了。
这为我带来了与处理或清除相关的任何方法的最重要的建议:你应该仅仅释放资源,在dispose方法中不要执行任何其它操作。如果你在Dispose 或finalize方法中执行其它操作,都可能给对象的生命周期带来严重的不良影响。对象在被构造的时候才”出生”,当垃圾收集器收回它们的时候才”死 亡”。当你的程序再也不能访问它们的时候,你可以认为它们处于”昏睡”状态。如果你不能到达(reach)某个对象,你就不能调用它的方法,对于所有的意 图和目的来说,它是死的。但是带有终结器的对象被宣布死亡之前还有最后一口气。终结器除了清理非受控资源之外不应该执行其它任何操作。如果某个终结器由于 什么原因使某个对象又可以到达了,那么该对象就恢复(resurrected)了。即使它是从”昏睡”状态醒来的,它也是”活着”的。下面是一个很明显的 例子:
public class BadClass
{
// 保存某个全局对象的引用
private readonly ArrayList _finalizedList;
private string _msg;
public BadClass( ArrayList badList, string msg )
{
// 缓冲该引用
_finalizedList = badList;
_msg = (string)msg.Clone();
}
~BadClass()
{
// 把该对象添加到列表中。这个对象是可到达的,不再是垃圾了。它回来了!
_finalizedList.Add( this );
}
}
当某个BadClass对象执行自己的终结器的时候,它向全局列表上添加了对自己的引用。这仅仅使自己可到达了,它活了过来!但是这样操作所带来的问 题使任何人都会感到胆怯。该对象已经被终结了,因此垃圾收集器相信不用再次调用它的终结器了。你真的需要终结一个被恢复的对象的时候,终结操作却不会发生 了。其次,你的一些资源可能不能用了。GC不会把终结器队列中的对象可以到达的任何对象从内存中移除,但是它可能已经终结了这些对象。如果是这样的话,那 些对象一定不能再次使用了。尽管BadClass的成员仍然存在于内存中,它们却像被处理过或被终结了一样。在C#语言中没有控制终结次序的途径。你不能 使这种构造工作更可靠。不要尝试!
除了学院的练习作业之外,我从来没有见到过如此明显地使用被恢复对象的代码。但是我看到有些代码有这个倾向,它们在终结器中试图执行某些实际工作,当 终结器调用的某些函数保存了对该对象的引用的时候,它就正在把对象变成活动的状态。原则上我们必须非常仔细地检查finalizer和Dispose方法 中任何代码。如果有些代码除了释放资源之外还执行了其它的操作,我们就需要再检查一次。这些操作在未来可能引起程序bug。请移除这些操作,并确保 finalizer和Dispose()方法只释放资源,不作其它任务事务。
在受控环境中,你不必为自己建立的每个类型编写终结器,你只需要为存储非受控类型,或者包含了实现IDisposable接口的成员的类型编写终结 器。即使你只需要Disposable接口,不需要finalizer,也应该同时实现整个模式。否则,你会使衍生类的标准Dispose思想的实现变得 很复杂,从而限制了衍生类的功能。请遵循前面谈到的标准的Dispose思想,这将使你、你的类的用户、从你的类型建立衍生类的用户的生活更加轻松。

[C#]构建插件式的应用程序框架

mikel阅读(1816)

转载:http://www.cnblogs.com/guanjinke/archive/2007/03/14/675109.html
构建插件式的应用程序框架(一)----开篇
构建插件式的应用程序框架(二)----订立契约
构建插件式的应用程序框架(三)----动态加载
构建插件式的应用程序框架(四)----服务容器
构建插件式的应用程序框架(五)----管理插件
构建插件式的应用程序框架(六)----通讯机制
构建插件式的应用程序框架(七)----基本服务
构建插件式的应用程序框架(八)----视图服务的简单实现

[Debug]三小时抛出了超过40000个异常,你注意到了没?

mikel阅读(1408)

原文地址:blogs.msdn.com/tess/archive/2005/11/30/498297.aspx
版权归原文作者所有,转载请注明出处
三小时抛出了超过40000个异常,你注意到了没?
看起来很荒谬,不幸的是,这种情况非常常见.
工 作中,我检查许多内存转储,有时候每天5-20个.在得到足够的信息后,我经常快速检查应用程序抛出了多少异常以及异常的类型,以提醒客户注意.多数时 候,应用抛出的异常数量比开发人员预料的多;或者是预料中的异常但他们并不知道这些异常会导致问题出现,因为这些异常看起来不会对应用程序造成直接的影 响.
那么,为什么上面的做法非常不好? 想想如果一个非常严重的异常在后台被处理掉导致用户看不到异常,会怎样?
我想到的有三个原因:
异常是昂贵的
异常会进入不必要的代码路径
异常一般在出现问题时抛出
异常很昂贵
Chris Brumme 写了一片非常棒的关于异常工作原理的日志,叫做异常模型(The Exception Model ),如果有时间的话应该读一下(绝对值得).
该文列出了一些异常抛出时会发生的事情:
通过翻译编译器生成的元数据抓取堆栈跟踪,来指导如何展开堆栈.
在堆栈向上运行一个handler组成的链,调用每个handler两次
补偿SEH,C++以及托管异常之间的匹配错误.
分配一个托管异常实例并调用其构造器.大多数情况下,这牵涉到到资源中查找错误信息.
可能需要进入操作系统内核.通常是发生了硬件异常.
通知任何已经附加的调试器,探察器,向量异常处理器和其他相关部分.
try{
   Myvar = (myClass) myParameter;
}
catch (Exception ex){
   // do some stuff to handle the exception.
}
如 果myParameter为null,并且没有添加参数检查,这段代码导致应用程序执行上面列出的所有任务,另外有一个特殊的功能,应用程序还抛出一个额 外的1st chance access violation exception(在NullReferenceException中总是触发),所以上面列出的动作实际发生了两次.
在深入讨论之前,上面的代码有一点我不太喜欢:捕获了一个基类异常,没法知道捕获的是否是一个NullReferenceException异常,或许是一个需要用其他方式处理的其他类型异常.不过这样还是比直接吞掉异常什么都不做要好.
异常会导致进入不必要的代码路径
分 享一个很久之前做过的案例.客户的服务器工作比较正常,但客户并不完全满意希望我们帮他们找出潜在的瓶颈.在使用调试器附加上去之后,很快发现有一些异常 用户显然没有注意到.不幸的是,由于其中一个异常最后调用了一个不期望调用的函数.这个函数又调用了一些不同的服务.避免出现这种情况的检查非常简单,把 检查加上去之后,吞吐量增加了不少于20倍…他们对调整后的系统性能非常满意
一个常见的例子是:使用全局异常处理记录异常到事件日志,数据库或者文件时,抛出异常可能会导致阻塞.
别误解我的意思,异常一定要记录,但必须确定对异常的处理没有问题.如果3小时之内抛出了超过40000异常并且将其记录到数据库或事件日志,不仅日志很快被填满,而且会碰到很多争用写的问题.(尤其是事件日志或者文件,因为写操作一般是串行的)
异常一般在出现问题时抛出
如果异常一般在出现问题时抛出,那为什么问题出现的这么频繁 ?在前面的例子中,假设你预料参数为null几率为20%,很可能不是异常而是一个问题.
关于异常,好的思考方式是:异常应该仅在例外的情况下才发生.应该采取措施来避免意料之外的异常.可以考虑使用try/catch而不是用编程控制。
下面是如何知道发生了多少.net 异常以及为什么和在什么地方发生的…
性能监视器中可以监视以下计数器
.NET CLR Exceptions/#Exceps Thrown (异常总数) 以及
.NET CLR Exceptions/#Exceps Thrown / sec
来得知应用程序抛出了多少异常.
可以使用windbg.exe来了解抛出了什么异常,根据当前是生产环境还是压力测试环境,几种方式可供选择,以决定是否允许调试器进入并暂停进程一段时间.
这几种方式是:

发生
未处理异常时附加并中断至调试器
获取内存快照来分析当前异常
让调试器运行并记录异常

发生未处理异常时附加并中断至调试器
出于演示的目的我创建了一个webform,并且简单的在Page_Load中调用 throw new System.Exception(“Test”).
windbg.exe附加到w3wp.exe进程之后运行sxe CLR命令使调试器在.net异常时中断
在检查托管结构之前需要加载sos扩展(.load clr10\sos)然后敲按键开始.
一旦捕捉到第一个异常,在调试窗口汇会看到类似以下的显示:
(15c4.163c): CLR exception – code e0434f4d (first chance)
并且中断至Kernel32!RaiseException,这里是真正抛出CLR异常的native部分
这里我们可以用下面的命令来得知是什么异常以及从什么地方抛出
!cen(CheckExceptionName来打印异常类型以及地址)
0:005> !cen
System.Exception (0x642e134)
以及!clrstack(打印托管栈)
0:005> !clrstack
Thread 5
ESP         EIP     
0x0c1bf6e4  0x77e55dea [FRAME: HelperMethodFrame]
0x0c1bf710  0x0c2e0528 [DEFAULT] [hasThis] Void ExceptionsAndStuff.WebForm1.Page_Load(Object,Class System.EventArgs)
  at [+0x30] [+0x0] c:\inetpub\wwwroot\exceptionsandstuff\webform1.aspx.cs:21
0x0c1bf728  0x0c3192dc [DEFAULT] [hasThis] Void System.Web.UI.Control.OnLoad(Class System.EventArgs)
0x0c1bf738  0x0c319224 [DEFAULT] [hasThis] Void System.Web.UI.Control.LoadRecursive()
0x0c1bf74c  0x0c3184d7 [DEFAULT] [hasThis] Void System.Web.UI.Page.ProcessRequestMain()
0x0c1bf790  0x0c317207 [DEFAULT] [hasThis] Void System.Web.UI.Page.ProcessRequest()
0x0c1bf7cc  0x0c316c73 [DEFAULT] [hasThis] Void System.Web.UI.Page.ProcessRequest(Class System.Web.HttpContext)
0x0c1bf7d4  0x0c316c4c [DEFAULT] [hasThis] Void System.Web.HttpApplication/CallHandlerExecutionStep.System.Web.HttpApplication+IExecutionStep.Execute()
0x0c1bf7e4  0x0c1d98b8 [DEFAULT] [hasThis] Class System.Exception System.Web.HttpApplication.ExecuteStep(Class IExecutionStep,ByRef Boolean)
0x0c1bf82c  0x0c1d9322 [DEFAULT] [hasThis] Void System.Web.HttpApplication.ResumeSteps(Class System.Exception)
0x0c1bf874  0x0c1d91eb [DEFAULT] [hasThis] Class System.IAsyncResult System.Web.HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest(Class System.Web.HttpContext,Class System.AsyncCallback,Object)
0x0c1bf890  0x01eb6897 [DEFAULT] [hasThis] Void System.Web.HttpRuntime.ProcessRequestInternal(Class System.Web.HttpWorkerRequest)
0x0c1bf8cc  0x01eb6448 [DEFAULT] Void System.Web.HttpRuntime.ProcessRequest(Class System.Web.HttpWorkerRequest)
0x0c1bf8d8  0x01eb2fc5 [DEFAULT] [hasThis] I4 System.Web.Hosting.ISAPIRuntime.ProcessRequest(I,I4)
0x0c1bf9a0  0x79217188 [FRAME: ContextTransitionFrame]
0x0c1bfa80  0x79217188 [FRAME: ComMethodFrame]
输出部分告诉我们ExceptionsAndStuff.WebForm1.Page_Load 抛出了System.Exception异常(在设置web.config中的Debug=true之后我们还可以看到页面名称以及行号)
输入gn(运行至未处理的异常)继续调试
在 初始异常之后如果继续,将得到两个附加的异常,第一个是System.UI.Page.HandleError中的 System.Web.HttpUnhandledException,接着是System.Web.UI.Page.ProcessRequest中的 System.Web.HttpUnhandledException。现在明白为什么看起来简单的异常会导致三次调用异常处理了把.
调试技巧:考虑运行下面的命令使调试更加自动化:
sxe -c "!cen;!clrstack;gn" CLR
这个命令中断到CLR异常,运行!cen,!clrstack然后继续,每一个异常都可以获得其类型以及调用堆栈。
获取一个内存快照查看当前异常
如果希望查看生产系统,要是记录了太多抛出的异常会显著降低应用程序的速度,可以使用adplus获取一个hang模式转储来得到许多信息,命令如下:
adplus -hang –pn <PROCESSNAME.EXE>
加载了转储文件以及sos.dll后,运行!dumpallexceptions(简写为!dae),将显示目当前堆上的所有异常对象,也就是所有还没有被垃圾搜集的异常对象,同时显示异常的类型.!dae给出的是统计信息,独立显示异常使用!dae -v(详细模式)
每次运行!dae你会注意到顶部有4个异常.NotSupportedException、ExecutionEngineException、StackOverflowException和
OutOfMemoryException,这并不意味着抛出了这些异常.绝大多数情况下,可以忽略这些.
往下,可以看到HttpResponse.Redirect/HttpResponse.End中有3个ThreadAbortException以及尝试填充数据集的时候有4个SQLException.
一个redirect总是抛出一个ThreadAbortException,因为此时需要停止执行当前页面,可以将false作为redirect方法的参数来避免这个异常,但这当然会导致当前页面继续执行.
0:000> !dae
Number of exceptions of this type:        1
Exception 0x151ce76c in MT 0x79bf44d4: System.NotSupportedException
_message: Specified method is not supported.
—————–
Number of exceptions of this type:        1
Exception 0x180000bc in MT 0x79b94ee4: System.ExecutionEngineException
—————–
Number of exceptions of this type:        1
Exception 0x1800007c in MT 0x79b94dac: System.StackOverflowException
—————–
Number of exceptions of this type:        1
Exception 0x1800003c in MT 0x79b94c74: System.OutOfMemoryException
—————–
Number of exceptions of this type:        3
Exception 0x28a74114 in MT 0x79bf881c: System.Threading.ThreadAbortException
_message: Thread was being aborted.
_stackTrace:
0x00000000
0x00000000
0x79bb8a00
0x79a29496 [DEFAULT] [hasThis] Void System.Threading.Thread.Abort(Object)
0x0e54eec8
0x79bb8e68
0x030c28fa [DEFAULT] [hasThis] Void System.Web.HttpResponse.End()
0x0e54eecc
0x0200cbe0
0x030c2075 [DEFAULT] [hasThis] Void System.Web.HttpResponse.Redirect(String,Boolean)
0x0e54eee0
0x0200cab0
0x02e2c898 [DEFAULT] [hasThis] Void ExceptionsAndStuff.WebForm1.Page_Load(Object,Class System.EventArgs)
0x0e54eefc
0x02a4af50
—————–
Number of exceptions of this type:        4
Exception 0x077cf420 in MT 0x02b84e9c: System.Data.SQLClient.SqlException
_message: System error.
_stackTrace:
0x02bf9f18 [DEFAULT] [hasThis] Boolean System.Data.SqlClient.SqlDataReader.Read()
0x0dd1ecf4
0x02b85de8
0x02fe432f [DEFAULT] [hasThis] I4 System.Data.Common.DbDataAdapter.FillLoadDataRow(Class System.Data.Common.SchemaMapping)
0x0dd1ed60
0x02e43110
0x02e2f9cc [DEFAULT] [hasThis] I4 System.Data.Common.DbDataAdapter.FillFromReader(Object,String,Class System.Data.IDataReader,I4,I4,Class System.Data.DataColumn,Object)
0x0dd1ed50
0x02e430f0
0x02e2f849 [DEFAULT] [hasThis] I4 System.Data.Common.DbDataAdapter.Fill(Class System.Data.DataSet,String,Class System.Data.IDataReader,I4,I4)
0x0dd1edec
0x02e430c0
0x02e2f688 [DEFAULT] [hasThis] I4 System.Data.Common.DbDataAdapter.FillFromCommand(Object,I4,I4,String,Class System.Data.IDbCommand,ValueClass System.Data.CommandBehavior)
0x0dd1edc8
0x02e430b0
0x02e2f4d5 [DEFAULT] [hasThis] I4 System.Data.Common.DbDataAdapter.Fill(Class System.Data.DataSet,I4,I4,String,Class System.Data.IDbCommand,ValueClass System.Data.CommandBehavior)
0x0dd1ee60
0x02e43090
0x02fe7e96 [DEFAULT] [hasThis] I4 System.Data.Common.DbDataAdapter.Fill(Class System.Data.DataSet)
0x0dd1ee88
0x02e43060
0x036ed5f4 [DEFAULT] [hasThis] Class System.Data.DataSet ExceptionsAndStuff.DBLib.GetData(I4)
0x0dd1ee94
0x029ff6d8
总共375个异常
SqlException的错误信息为system error,用处不大.SqlException有点特殊因为真实信息保存在异常外部一个错误对象中,所以最好是用!dumpobj命令单独查看对象的地址
0:000> !dumpobj 0x077cf420
Name: System.Data.SqlClient.SqlException
MethodTable 0x02b84e9c
EEClass 0x02af9340
Size 68(0x44) bytes
GC Generation: 2
mdToken: 0x020001c4  (c:\windows\assembly\gac\system.data\1.0.5000.0__b77a5c561934e089\system.data.dll)
FieldDesc*: 0x02b84d98
        MT      Field     Offset                 Type       Attr      Value Name
0x79b947ac 0x4000029     0x34         System.Int32   instance 0 _xptrs
0x79b947ac 0x400002a     0x38         System.Int32   instance -532459699 _xcode
0x02b84e9c 0x4000fb5     0x3c                CLASS   instance 0x077cf464 _errors
—————–
Exception 0x077cf420 in MT 0x02b84e9c: System.Data.SqlClient.SqlException
_message: System error.
_stackTrace:
0x02bf9f18 [DEFAULT] [hasThis] Boolean System.Data.SqlClient.SqlDataReader.Read()
然后把_errors对象dump下来(用下面的地址0x077cf464作为参数)
0:000> !dumpobj 0x077cf464
Name: System.Data.SqlClient.SqlErrorCollection
MethodTable 0x02b8500c
EEClass 0x02af93a4
Size 12(0xc) bytes
GC Generation: 2
mdToken: 0x020001c3  (c:\windows\assembly\gac\system.data\1.0.5000.0__b77a5c561934e089\system.data.dll)
FieldDesc*: 0x02b84f2c
        MT      Field     Offset                 Type       Attr      Value Name
0x02b8500c 0x4000fb4      0x4                CLASS   instance 0x077cf470 errors
0:000> !dumpobj 0x077cf470
Name: System.Collections.ArrayList
MethodTable 0x79ba0d74
EEClass 0x79ba0eb0
Size 24(0x18) bytes
GC Generation: 2
mdToken: 0x020000ff  (c:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll)
FieldDesc*: 0x79ba0f14
        MT      Field     Offset                 Type       Attr      Value Name
0x79ba0d74 0x400035b      0x4                CLASS   instance 0x077cf488 _items
0x79ba0d74 0x400035c      0xc         System.Int32   instance 1 _size
0x79ba0d74 0x400035d     0x10         System.Int32   instance 1 _version
0x79ba0d74 0x400035e      0x8                CLASS   instance 0x00000000 _syncRoot
0:000> !dumpobj -v 0x077cf488
Name: System.Object[]
MethodTable 0x01a2209c
EEClass 0x01a22018
Size 80(0x50) bytes
GC Generation: 2
Array: Rank 1, Type CLASS
Element Type: System.Object
Content: 16 items
—— Will only dump out valid managed objects —-
   Address            MT    Class Name
0x077cf400    0x02b851a4    System.Data.SqlClient.SqlError
———-
0:000> !do 0x077cf400
Name: System.Data.SqlClient.SqlError
MethodTable 0x02b851a4
EEClass 0x02af9408
Size 32(0x20) bytes
GC Generation: 2
mdToken: 0x020001c2  (c:\windows\assembly\gac\system.data\1.0.5000.0__b77a5c561934e089\system.data.dll)
FieldDesc*: 0x02b85090
        MT      Field     Offset                 Type       Attr      Value Name
0x02b851a4 0x4000fad      0x4                CLASS   instance 0x10015bb0 source
0x02b851a4 0x4000fae     0x10         System.Int32   instance 1205 number
0x02b851a4 0x4000faf     0x18          System.Byte   instance 61 state
0x02b851a4 0x4000fb0     0x19          System.Byte   instance 13 errorClass
0x02b851a4 0x4000fb1      0x8                CLASS   instance 0x077cf28c message
0x02b851a4 0x4000fb2      0xc                CLASS   instance 0x077cf3c8 procedure
0x02b851a4 0x4000fb3     0x14         System.Int32   instance 22 lineNumber
0:000> !do 0x077cf28c
Name: System.String
MethodTable 0x79b925c8
EEClass 0x79b92914
Size 316(0x13c) bytes
GC Generation: 2
mdToken: 0x0200000f  (c:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll)
String: Transaction (Process ID 104) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
FieldDesc*: 0x79b92978
        MT      Field     Offset                 Type       Attr      Value Name
0x79b925c8 0x4000013      0x4         System.Int32   instance 150 m_arrayLength
0x79b925c8 0x4000014      0x8         System.Int32   instance 149 m_stringLength
0x79b925c8 0x4000015      0xc          System.Char   instance 0x54 m_firstChar
0x79b925c8 0x4000016        0                CLASS     shared   static Empty
    >> Domain:Value 0x000abfc8:0x18000224 0x00163890:0x18000224 <<
0x79b925c8 0x4000017      0x4                CLASS     shared   static WhitespaceChars
    >> Domain:Value 0x000abfc8:0x18000238 0x00163890:0x14005924 <<
可以看到,能搜集到很多信息,比如存储过程名字等.
现在,假设某函数有一个NullReferenceException,但你并不知道真正在函数的哪个地方或者什么可能为null
0:000> !dumpobj 0x3d86c980
Name: System.NullReferenceException
—————–
Exception 0x3d86c980 in MT 0x79c04e64: System.NullReferenceException
_message: Object reference not set to an instance of an object.
_stackTrace:
0x2fa0536b [DEFAULT] [hasThis] Void ExceptionsAndStuff.WebForm1. btnAdd_Click(Object,Class System.EventArgs)
0x0c10f844
0x0f64be20
0x189b51ec [DEFAULT] [hasThis] Void System.Web.UI.WebControls.LinkButton.OnClick(Class System.EventArgs)
0x0c10f854
0x0e124f00
0x189b4f72 [DEFAULT] [hasThis] Void System.Web.UI.WebControls.LinkButton.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String)
0x0c10f868
0x0e124f60
0x189b4f22 [DEFAULT] [hasThis] Void System.Web.UI.Page.RaisePostBackEvent(Class System.Web.UI.IPostBackEventHandler,String)
0x0c10f878
0x0c2a1308
0x189b4eea [DEFAULT] [hasThis] Void System.Web.UI.Page.RaisePostBackEvent(Class System.Collections.Specialized.NameValueCollection)
0x0c10f880
0x0c2a12f8
0x10920612 [DEFAULT] [hasThis] Void System.Web.UI.Page.ProcessRequestMain()
0x0c10f890
0x0c2a14d8
堆栈顶部的地址显示了代码发生异常的准确位置(在这个例子中是btnAdd_Click),可以使用!u来反汇编函数看这个位置到底做了什么.
0:000> !u 0x2fa0536b
Will print '>>> ' at address: 0x2fa0536b
Normal JIT generated code
[DEFAULT] [hasThis] Void ExceptionsAndStuff.WebForm1.btnAdd_Click (Object,Class System.EventArgs)
Begin 0x2fa05358, size 0x9d
2fa05358 57               push    edi
2fa05359 56               push    esi
2fa0535a 8bf1             mov     esi,ecx
2fa0535c 8b8e68010000     mov     ecx,[esi+0x168]
2fa05362 3909             cmp     [ecx],ecx
2fa05364 e8cf4313f7       call    26b39738 (System.Web.UI.HtmlControls.HtmlInputFile.get_PostedFile)
2fa05369 8bc8             mov     ecx,eax
>>> 2fa0536b 3909             cmp     [ecx],ecx
2fa0536d e8464613f7       call    26b399b8 (System.Web.HttpPostedFile.get_ContentLength)
2fa05372 3b053c8b640f     cmp     eax,[0f648b3c]
2fa05378 7e04             jle     2fa0537e
2fa0537a 33c0             xor     eax,eax
2fa0537c eb05             jmp     2fa05383
2fa0537e b801000000       mov     eax,0x1
2fa05383 25ff000000       and     eax,0xff
2fa05388 7448             jz      2fa053d2
2fa0538a 8bbe68010000     mov     edi,[esi+0x168]
2fa05390 8b8e98010000     mov     ecx,[esi+0x198]
2fa05396 8b01             mov     eax,[ecx]
2fa05398 ff90c4010000     call    dword ptr [eax+0x1c4]
2fa0539e 50               push    eax
2fa0539f 6aff             push    0xff
2fa053a1 8bd7             mov     edx,edi
2fa053a3 8bce             mov     ecx,esi
2fa053a5 ff1520c1640f     call    dword ptr [0f64c120]

从这儿,可以看出,调用了HtmlInputFile对象PostedFile属性的get方法,然后从eax移动到ecx(eax通常包含函数的返回值),
然 后将比较对象的引用,接着就抛出了异常.不需要精通汇编指令就可以知道问题的大概并确推断出PostedFile为null并确在尝试访问 ContentLength属性时抛出了异常.如果有源码的话就更容易找到问题了.这里只需要加入PostedFile是否为null的检查就可以放心.

让调试器自己运行并记录异常

下面展示一个让调试器运行并确记录日志的办法,不过有时候web管理员或其他有需要的人用脚本来做更方便.
创建一个叫做TrackCLR.cfg的配置文件,内容如下:
<ADPLUS>
  <SETTINGS>
   <RUNMODE>CRASH</RUNMODE>
  </SETTINGS>
  <PRECOMMANDS>
   <CMD>!load clr10\sos</CMD>
  </PRECOMMANDS>
  <EXCEPTIONS>
   <OPTION>NoDumpOnFirstChance</OPTION>
   <OPTION>NoDumpOnSecondChance</OPTION>
   <CONFIG><!– This is for the CLR exception –>
    <CODE>clr</CODE>
    <ACTIONS1>Log</ACTIONS1>
    <CUSTOMACTIONS1>!cen;!clrstack;gn </CUSTOMACTIONS1>
    <RETURNACTION1>GN</RETURNACTION1>
   </CONFIG>
  </EXCEPTIONS>
</ADPLUS>
然后执行adplus –pn myprocess.exe –c TrackCLR.cfg
这个命令会在调试器目录下产生一个日志文件,记录每个.net异常的托管栈以及异常类型.
或者更进一步,使用下面示例配置文件,在特定的异常发生时创建一个内存转储文件:
<ADPLUS>
  <SETTINGS>
   <RUNMODE>CRASH </RUNMODE>
  </SETTINGS>
  <PRECOMMANDS>
   <CMD>!load clr10\sos </CMD>
  </PRECOMMANDS>
  <EXCEPTIONS>
   <OPTION>NoDumpOnFirstChance </OPTION>
   <OPTION>NoDumpOnSecondChance </OPTION>
   <CONFIG><!– This is for the CLR exception –>
    <CODE>clr </CODE>
    <ACTIONS1>Log </ACTIONS1>
    <CUSTOMACTIONS1>!clr10\sos.cce System.InvalidOperationException 1; j ($t1 = 1) '.dump /ma /u c:\temp_dumps\exceptiondump.dmp;gn' ; 'gn' </CUSTOMACTIONS1>
    <RETURNACTION1>GN </RETURNACTION1>
    <ACTIONS2>Void </ACTIONS2>
    <RETURNACTION2>GN </RETURNACTION2>
   </CONFIG>
  </EXCEPTIONS>
</ADPLUS>
cce命令根据CLR异常是否匹配指定的类型来设置$t1 为1或0.(如果传入的为1,那么用后面的命令作为cce命令的第二个参数,这里可以选择用t1或者t0)
j ($t1 = 1) 是一个if语句,所以要是$t1设置为1就会进行转储,否则直接跳过