[转载]微软ASP.NET站点部署指南(5):部署到IIS上作为测试环境

mikel阅读(1032)

[转载]微软ASP.NET站点部署指南(5):部署到IIS上作为测试环境 – 汤姆大叔 – 博客园.

1.  综述

开发程序的时候,通常我们是在Visual Studio 里测试,默认使用的是Visual Studio Development Server(Cassini),使用这个可以让我们开发测试工作更容易,但是在IIS上可能不一定能够正常工作。所以,结果可能是在Visual Studio 里运行正常,但是一旦部署到IIS上就会出错。

你可以通过下面一些方法来测试确保你的程序没问题:

  1. 开发的时候,使用IIS Express或者正式版IIS代替默认的Visual Studio Development Server来测试程序,这样可以更能确保在IIS下少出问题。但是这个方法不能测试你的部署流程是否正确,或者验证部署结构。
  2. 部署程序到你的开发机上的IIS,然后用同样的步骤部署到生产环境,这样验证你的部署流程和是否能在生产环境运行成功。
  3. 部署到和生产环境接近的测试环境,由于生产环境是第三方的提供商,所以最理想的测试环境就是提供商开放第二个账户(仅用于测试目的),这样就可以和正式的生产环境使用一样的部署步骤了。

本章节演示的是第二种方式,第三种方式可以在第七章尾部讲到,另外本章节的底部有第一种方式需要用的资源连接地址。

提醒:如果根据本章节所做的操作出现错误信息或一些功能不正常的话,请务必check Troubleshooting页面

2.  配置程序运行在Medium Trust上

安装IIS和部署之前,我们需要修改Web.config文件以便配置更能接近共享主机环境。主机提供商通常会让你的程序运行在medium trust级别上,那就是说有些事情是不允许做的,比如你不能访问注册表或者你程序之外的目录读写文件。本机默认情况下是运行在high trust上,也就是有些你可以做的事情在生产环境上可能做不到了。因此,为了确保部署到生产环境没问题,需要将测试环境的运行配置也改成medium trust

Web.config文件里,在system.web元素节点里添加<trust>元素,像下面一样:

<configuration>
  <!-- Settings -->
  <system.web>
    <trust level="Medium"/>
    <!-- Settings -->
  </system.web>
</configuration>

现在程序可以运行在medium trust上了,这样你可以尽早发现需要high trust才能运行的错误了。

3.  安装IIS和Web Deploy

你必须先安装IIS和Web Deploy 才能将程序部署到你开发机的IIS上。因为默认Windows 7是不安装这些组件的,如果你已经都安装了,那请忽略这个section。

强烈推荐使用Web Platform Installer安装IIS和Web Deploy,,因为会自动安装IIS的推荐配置和自动安装IIS和Web Deploy所需要的依赖软件。

使用Web Platform Installer安装IIS和Web Deploy,请使用下面的连接,如果你曾经安装过其中的一种或者是相关的依赖软件,那Web Platform Installer只会安装剩余的部分。

4.  设置默认的应用程序池为.Net4

安装IIS以后,运气IIS管理器(开始->运行,输入inetmgr,回车),确保默认程序池使用的.NET Framework版本是4。

连接Connections)面板,展开server节点选择应用程序池Application Pools),在右边的面板里,查看DefaultAppPool使用的.NET framework版本是否是v4.0,如果是,请忽略此section。

如果你只能看到2个应用程序池,并且都是.NET Framework 2.0的话,你需要在IIS里安装ASP.NET 4,然后再配置IIS才能使用。

打开一个command prompt窗口,右键选择以管理员身份运行(Run as Administrator),然后运行aspnet_regiis.exe来安装ASP.NET 4到IIS里。使用如下命令:

cd %windir%\Microsoft.NET\Framework\v4.0.30319
aspnet_regiis.exe –iru

上面的命名将会为.NET Framework 4创建2个应用程序池,但默认版本还是v2.0的。因为需要将程序部署到.NET 4的应用程序池上,所以需要修改它们的版本为NET 4。

重新运行IIS管理器,点击应用程序池,重新查看右边的面板,点击DefaultAppPool,然后在操作面板Actions)里点击基本设置Basic Settings)。

编辑应用程序池Edit Application Pool)对话框里,将.NET Framework version里的项修改成.NET Framework v4.0.30319,然后点击确定OK)。

现在就可以将程序发布到IIS上了。

5.  发布到IIS

使用Visual Studio 2010和Web Deploy部署有几种方式:

  • 使用Visual Studio one-click发布
  • 创建部署包,然后使用IIS Manager 管理控制台安装,部署包是一个.zip文件,包含了所有需要部署到IIS上的文件和元数据
  • 创建部署包,然后使用命令行安装

前面的章节都提到了这三种方式的Visual Studio自动部署设置方式。本章节用的是第一种。更多使用部署包的信息,可以查看ASP.NET Deployment Content Map

发布之前,确保你的Visual Studio是运行在管理员模式下(开始菜单,右键Visual Studio 2010,选择以管理员身份运行)。

改变当前active的build配置是Test,你可以从工具栏选择(如下图),或者从编译(Build菜单里的配置管理器Configuration Manager)里选择。

Solution Explorer里,右键ContosoUniversity项目,选择发布(Publish),弹出Publish Web对话框。

设置profile名称为”Test”,如果没看到文本框,而是看到下拉菜单就选择new ,然后输入新名称。

Service URL里输入“localhost”

Site/application里输入”Default Web Site/ContosoUniversity”

选择上Mark as IIS application on destination。(有些情况不需要部署到IIS上,例如项目本身就不是个web程序,它仅仅包含了一些图片,XML文件等,这种情况就不用选择了,以便不建立IIS程序。)

点击发布(Publish),如果你收到如下信息框,请重新以管理员身份运行Visual Studio。

如果你关掉Visual Studio,例如以后的章节来重新打开它再发布的时候,请务必选择以管理员身份运行,如果不是管理员身份,发布程序不会提示出错框而是尝试发布,有时候可能会提示一个没有权限的错误信息。

如果是运行在管理员模式下,Output窗口会显示编译和发布成功的信息。

运行IIS管理器,在连接(Connections)面板里,你可以在默认站点(Default Web Site)下看到一个名字为ContosoUniversity的应用程序,如果之前都打开了IIS,你点击刷新(Refresh)。在操作(Actions)面板点击浏览(Browse *:80 (http))来验证是否可以打开你的首页。

不出错的话,浏览器就会显示Contoso University项目的首页了。

6.  在测试环境中测试

打开浏览器访问http://localhost/ContosoUniversity,重新运行首页,可以看到页面顶部显示的标示符是”(Test)”而不是”(Dev)”了,这就意味着Web.config的transformation结果是正确的。

打开Students页面验证部署的数据库里布包含任何students:

打开Instructors页面验证这些数据依然存在于数据库中:

Students菜单里选择Add Students,添加一个student,然后可以看到Students 页面显示了一个student,说明已经成功保存到数据库里了:

Courses菜单,选择Update Credits,因为该页面需要administrator权限,所以跳转到了Log In页,输入administrator账户信息(”admin” 和 “Pas$w0rd”)登录以后, Update Credits就显示了,这就验证了上个章节创建的administrator账户已经正确的部署到测试环境了。

检查Elmah文件夹是否存在并且值包含一个placeholder文件,这就验证了前面章节设置的忽略部署该文件夹的XML文件执行成功了。

打开C:\inetpub\wwwroot\ContosoUniversity 目录下的Web.config文件验证Code First数据库初始化参数值为Disabled:

现在,你成功将程序部署到你开发机的IIS上并且测试了,这验证了部署流程不仅仅只是copy程序内容(也可以排除不需要部署的内容了),另外Web Deploy也成功配置了IIS站点。下一章节你将测试另外一个还没搞定的部署任务:设置Elmah文件夹的写权限。

7.  更多信息

关于在Visual Studio 里运行IIS或IIS Express的更多信息,请查看下面列出的资源:

该帖提供了很多例子解释为什么使用Visual Studio Development Server (Cassini)测试不如使用IIS Express测试可靠,以及为什么在IIS Express里测试没有在IIS正式版里测试可靠的原因.

关于Visual Studio 2010里的自动化部署工具以及关于one-click发布和部署包更多的信息,请访问:ASP.NET Web Application Project Deployment Overview

关于主机提供商将程序运行在medium trust有可能引起的问题,请访问:Hosting ASP.NET Applications in Medium Trust

[转载]SQLServer行转列问题-pivot使用详解

mikel阅读(936)

[转载]老掉牙的行转列问题-pivot使用详解 – 屠龙解牛 – 博客园.

一背景设定

行转列的典型应用场景,比如报表,交叉表。还有一个就是:面试。。。

行转列是对单行值的拆分,拆分的途径比如利用聚合函数。拆分之后变成多列。

借用网上通用的样例,对,就是小王小明四门功课。有印象了吧。

data

CREATE  TABLE [StudentScores]
(
    [UserName]         NVARCHAR(20),        --学生姓名
    [Subject]          NVARCHAR(30),        --科目
    [Score]            FLOAT,               --成绩
)

INSERT INTO [StudentScores] SELECT 'Nick', '语文', 80

INSERT INTO [StudentScores] SELECT 'Nick', '数学', 90

INSERT INTO [StudentScores] SELECT 'Nick', '英语', 70

INSERT INTO [StudentScores] SELECT 'Nick', '生物', 85

INSERT INTO [StudentScores] SELECT 'Kent', '语文', 80

INSERT INTO [StudentScores] SELECT 'Kent', '数学', 90

INSERT INTO [StudentScores] SELECT 'Kent', '英语', 70

INSERT INTO [StudentScores] SELECT 'Kent', '生物', 85

SQLServer2005出现之前,一般的做法是用case when.

如下所示

SELECT 
      UserName, 
      MAX(CASE Subject WHEN '语文' THEN Score ELSE 0 END) AS '语文',
      MAX(CASE Subject WHEN '数学' THEN Score ELSE 0 END) AS '数学',
      MAX(CASE Subject WHEN '英语' THEN Score ELSE 0 END) AS '英语',
      MAX(CASE Subject WHEN '生物' THEN Score ELSE 0 END) AS '生物'
FROM dbo.[StudentScores]
GROUP BY UserName

——————-以上可忽略不看——————-

二 PIVOT:how?

SQL2005推出了新的函数PIVOT.

pivot这个单词本身是旋转的意思,行转列本质上也一样,从魔方的这个面到另一个。

使用pivot做好 5 steps.

1从返回的字段名开始

2接下来跟上 子查询

3指定聚合列

4for指定要转化的行值

5对整个查询别名

SELECT username,语文,数学,英语,生物

--step1
FROM
(
 SELECT UserName,[Subject],Score FROM StudentScores
 
 ) AS  a 

--step2
PIVOT
(
 MAX(score)

--step3
 FOR [Subject] IN ([语文],[英语],[生物],[数学])

--step4
) AS b
ORDER BY UserName

--step5

ps:几点细节:step1中字符串不能加引号;step4中in后的列表只能加中括号,不能加引号;1和4的引用数目要一致,但和4的顺序无关

[转载]"返回"按钮的解决方案摸索

mikel阅读(1054)

[转载]”返回”按钮的解决方案摸索 – xxfss2 – 博客园.

做过网站或WEB系统的,肯定都曾在自己的页面中用过“返回”按钮。我也不例外,在多次的开发中,我曾经思索:这个小小的“返回”按钮,应该以怎样的代码实现比较好。

先说说自己最常使用的,也是最常见最无脑的。如:

1         protected void Back()
2         {
3             Response.Redirect("CurrList.aspx");
4         }

有时候,当返回的页面的是一个带分页的页面时,则需要在进入页面时传入分页信息,然后在返回时取出这些值。比如:

1     private void Goback()
2     {
3         string size = Request.QueryString["size"];
4         string index = Request.QueryString["index"];
5            Response.Redirect("SellInfoDisplay.aspx?size=" + size + "&index=" + index );
6     }

用的最多的,无非以上了。

有的时候,再复杂一些,比如一个页面可以从不同的页面跳入,那么,还得再记录哪个页面来的,返回的时候,再判断判断。

再有的时候,A页面跳到B页面,B页面再跳到C页面,这个时候想要连着两次返回到A页面,要写的代码就多了。

那么,这个小小的“返回”按钮有没有什么好的方法去处理呢?

首先我尝试了这样的方法:

1 history.go(-1);

曾经在某些页面中用过这样的方法,似乎也没什么问题。但这个方法是有使用范围限制的,通常它适合用在静态页面。另外,当跳转之前页面执行了某个脚本,而这个脚本中alert了一下,此时用这个方法跳转回来的时候,我发现它又alert了一下,这显然不行。

接着我又尝试了这样的方法:

 1     protected void SaveCurrURL()
 2     {
 3         Session["prevURL"] = Request.UrlReferrer.PathAndQuery;
 4     }
 5 
 6     protected string  GetCurrURL()
 7     {
 8         return Session["prevURL"].ToString();
 9     }
10 
11     protected void Back()
12     {
13         Response.Redirect(this.GetCurrURL());
14     }

前两个方法写在自定义的页面基类中,并且我约定了系统中总是将分页,查询,排序等参数信息保存在URL中。接着我制作了对应的分页控件,正当我沾沾自喜,以为大功告成的时候,又一个问题出现了。

Request .UrlReferrer .PathAndQuery  这句代码似乎会有失灵的时候。

经过几次尝试,发现问题是这样的:

假设从A页面跳转至B页面,那么A页面的方式如果是以脚本的方式进行跳转的话,那么在B页面调用SaveCurrURL方法是获取不到预期的值的,比如(location.href=。。。。),而在A页面以超链接的方式进行跳转的话,则可以顺利获取到。

这是何故?又怎麽办呢?我们知道,用户点击超链接属于用户主动行为,而脚本触发,则属于被动行为,有时候某些被动行为会因为安全方面的考虑而受到重重阻碍。我们可以以脚本的方式触发超链接元素的onclick事件,确很难模拟用户直接点击超链接的效果(我认为是不能)。

后来我决定退一步,牺牲一下性能,将代码改成Session[“prevURL”] = Request.Url.PathAndQuery;

并且为了支持两级的返回,最后的代码是这样的

 1     protected void SaveCurrURL(bool first)
 2     {
 3         if (Session["prevURL"] == null)
 4             Session["prevURL"] = new string[3];
 5         string[] urls = (string[])Session["prevURL"];
 6         if (first)
 7         {
 8             urls[0] = Request.Url.PathAndQuery;
 9             urls[1] = null;
10             urls[2] = null;
11         }
12         else
13         {
14             if (urls[1] == null)
15                 urls[1] = Request.Url.PathAndQuery;
16             else
17                 urls[2] = Request.Url.PathAndQuery;
18         }
19         Session["prevURL"] = urls;
20     }
21     ///<summary>
22 /// 获取上次保存的URL
23 ///</summary>
24 ///<returns></returns>
25     protected string  GetCurrURL()
26     {
27         string[] urls = (string[])Session["prevURL"];
28         for (int i = 2; i >= 0; i--)
29         {
30             if (urls[i] != null)
31             {
32                 string url = urls[i-1];
33                 urls[i] = null;
34                 urls[i - 1] = null;
35                 Session["prevURL"] = urls;
36                 return url;
37             }
38         }
39         throw new Exception("");
40     }

最终的代码费了我不少心思,为了使session保持小巧,我用了string数组,而不是string list,为了支持两级返回,我将数组的长度设为3,这是有原因的。

它们是这样的使用方式,假设有A\B\C 3个页面,

A可以跳到B,B可以跳到C,C可以返回B,B可以返回A。

那么在A页面,这是一个起点页面,调用SaveCurrURL传入TRUE参数,B和C页面的参数为FALSE,注意C页面也要调用,这属于一个瑕疵。

在B和C的返回按钮中,不论他们是不是从A跳过来的,也不论他们处与第2个页面还是第3个页面,统统调用

Response.Redirect(this.GetCurrURL ());

最后,别忘了在if (!IsPostBack)中调用Save函数.

以上便是我对“返回”功能的小小尝试,只能说支持一定的使用范围,有一定的简化作用和通用的处理方式。当然它没有经过严格的测试,也希望大牛们能提供更好的方案。

[转载]ASP.NET MVC3 20个秘方-(5)发送欢迎邮件

mikel阅读(1030)

[转载]【译】MVC3 20个秘方-(5)发送欢迎邮件 – 技术弟弟 – 博客园.

场景
很多网站要求人们先注册再去访问内容或者发表评论.网站如牛毛,怎么可能让人们记住每个他们注册过的网站。在注册的过程中,可以发送一个电子邮件来提醒用户他们刚刚注册了,这样,他们可能一会还会返回到你的网站。

解决方案

在用户注册之后使用SmtpClient和MailMessage发送邮件通知。

讨论

发送一个邮件之前,你需要配置一个SMTP服务器,端口,用户名和密码。为了使配置简单化,我建议你在web.config的appsetting中配置。

<appSettings>

<add key="webpages:Version" value="1.0.0.0"/>

<add key="ClientValidationEnabled" value="true"/>

<add key="UnobtrusiveJavaScriptEnabled" value="true"/>

<add key="smtpServer" value="localhost"/>

<add key="smtpPort" value="25"/>

<add key="smtpUser" value=""/>

<add key="smtpPass" value=""/>

<add key="adminEmail" value="no-reply@no-reply.com"/>

</appSettings>

必要时可以去更新这些value 去反射你的SMTP server,port,username 和password

提示:你也可以使用Visual studio的ASP.NET配置工具去配置。

选择应用程序-> 配置 SMTP 电子邮件设置

为了便于组织项目的结构,我们需要创建一个新的文件夹和新的类去包含必要的发送邮件函数。

右击项目,添加->新建文件夹并且命名问Uitls。右击新建一个类命名为MailClient.cs.

MailClient类及其函数将被定义成static便于使用。日后他被整合到新的功能里时,也不需要为它创建新的实例。下边是一个完整的MailClient 类:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Web;

namespace MvcApplication.Utils
{

    public static class MailClient
    {
        private static readonly SmtpClient Client;
        static MailClient()
        {
            Client = new SmtpClient
            {
                Host =
                ConfigurationManager.AppSettings["SmtpServer"],
                Port =
                Convert.ToInt32(
                ConfigurationManager.AppSettings["SmtpPort"]),
                DeliveryMethod = SmtpDeliveryMethod.Network
            };
            Client.UseDefaultCredentials = false;
            Client.Credentials = new NetworkCredential(
            ConfigurationManager.AppSettings["SmtpUser"],
            ConfigurationManager.AppSettings["SmtpPass"]);
        }
        private static bool SendMessage(string from, string to,
        string subject, string body)
        {
            MailMessage mm = null;
            bool isSent = false;
            try
            {
                // Create our message
                mm = new MailMessage(from, to, subject, body);
                mm.DeliveryNotificationOptions =
                DeliveryNotificationOptions.OnFailure;
                // Send it
                Client.Send(mm);
                isSent = true;
            }
            // Catch any errors, these should be logged and
            // dealt with later
            catch (Exception ex)
            {
                // If you wish to log email errors,
                // add it here...
                var exMsg = ex.Message;
            }
            finally
            {
                mm.Dispose();
            }
            return isSent;
        }
        public static bool SendWelcome(string email)
        {
            string body = "Put welcome email content here...";
            return SendMessage(
            ConfigurationManager.AppSettings["adminEmail"],
            email, "Welcome message", body);
        }
    }
}

一开始,通过webconfig的配置创建一个新的SmtpClient 实例。然后创建一个SendMessage的函数。这个函数是私有的,不应该直接调用这个函数。这个函数在实际执行发送的时候调用。它创建了一个新的 MailMessage对象,并通过前边歘构建的SmtpClient对象发送它。最后SendWelcome函数是创建接受电子邮件的地址。它生成一个 通用的消息去发送你的电子邮件。它通过SendMessage函数发送。

为了在注册之后发送邮件通知。在Account controller中的register action必须在用户成功创建账户后调用SendWelcome方法。

[HttpPost]
        public ActionResult Register(RegisterModel model)
        {
            if (ModelState.IsValid)
            {
                // Attempt to register the user
                MembershipCreateStatus createStatus;
                Membership.CreateUser(model.UserName, model.Password, model.Email, null, null, true, null, out createStatus);

                if (createStatus == MembershipCreateStatus.Success)
                {
                    MailClient.SendWelcome(model.Email);
                    FormsAuthentication.SetAuthCookie(model.UserName, false /* createPersistentCookie */);
                    return RedirectToAction("Index", "Home");
                }
                else
                {
                    ModelState.AddModelError("", ErrorCodeToString(createStatus));
                }
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }

前边的代码是一个基本的例子。在当今社会,以自动化的形式处理存在的应用程序是一个好主意。

你可以进一步扩展这个例子。在你发送的欢迎邮件中附带一个验证消息。这样可以验证用户电子邮件地址的有效性。让他点击在欢迎邮件的链接。然后他才可能登陆。

另请参阅

SmtpClient and MailMessage

译者注:在.NET 4.0中,微软有提供了一个新的helper “Webmail”。更加方便。

[转载]java pdf 转 swf

mikel阅读(1041)

[转载]java pdf 转 swf – 肖秋峰 – 博客园.

Java Process.exitValue & Process.waitFor()

Process.exitValue() 采用非阻塞的方式返回,如果没有立即拿到返回值,则抛出异常

Process.waitFor() 当前线程等待,如有必要,一直要等到由该 Process 对象表示的进程已经终止。但是如果我们在调用此方法时,如果不注意的话,很容易出现主线程阻塞,Process也挂起的情况。在调用waitFor() 的时候,Process需要向主线程汇报运行状况,所以要注意清空缓存区,即InputStream和ErrorStream,在网上,很多只提到处理 InputStream,忽略了ErrorStream。以下一段代码,贴出来,仅做参考。

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;

/*
* PDF转SWF工具
* @author tangs
*
*/
public class Converter {
    public static int convertPDF2SWF(String sourcePath, String destPath, String fileName) throws IOException {
////目标路径不存在则建立目标路径
        File dest = new File(destPath);
        if (!dest.exists()) dest.mkdirs();
        
        //源文件不存在则返回
        File source = new File(sourcePath);
        if (!source.exists()) return 0;
        
        //调用pdf2swf命令进行转换
//        String command = "D:\\swftools\\pdf2swf.exe" + " -o \"" + destPath  + fileName +"\"  <SPAN style='COLOR: #ff0000'>-s languagedir=D:\\xpdf\\xpdf-chinese-simplified</SPAN> -s flashversion=9 \"" + sourcePath + "\"";
//        String command = "D:\\swftools\\pdf2swf.exe" + " -o \"" + destPath  +  fileName +"\" -s flashversion=9 \"" + sourcePath + "\"";
        String command= "D:/SWFTools/pdf2swf.exe  -t \""+destPath+"\\Java.pdf\" -o  \""+destPath+"\\test.swf\" -s flashversion=9 -s languagedir=D:\\xpdf\\xpdf-chinese-simplified ";  
        System.out.println("cmd:"+command);
//        Process pro = Runtime.getRuntime().exec(command);
        Process process = Runtime.getRuntime().exec(command); // 调用外部程序   
        final InputStream is1 = process.getInputStream();   
        new Thread(new Runnable() {   
            public void run() {   
                BufferedReader br = new BufferedReader(new InputStreamReader(is1));    
                try {
                    while(br.readLine()!= null) ;
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }   
            }   
        }).start(); // 启动单独的线程来清空process.getInputStream()的缓冲区   
        InputStream is2 = process.getErrorStream();   
        BufferedReader br2 = new BufferedReader(new InputStreamReader(is2));    
        StringBuilder buf = new StringBuilder(); // 保存输出结果流   
        String line = null;   
        while((line = br2.readLine()) != null) buf.append(line); // 循环等待ffmpeg进程结束   
        System.out.println("输出结果为:" + buf);
        
//        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(pro.getInputStream()));
        while (br2.readLine() != null); 
        
        try {
            process.waitFor();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        return process.exitValue();

        
    }

    
    public static void main(String []args) throws IOException {
        String sourcePath = "D:\\Java.pdf";
        String destPath = "D:\\";
        String fileName = "Javssa.swf";
        try{
        Converter.convertPDF2SWF(sourcePath, destPath, fileName);
        
        }catch(Exception ex)
        {
            System.out.println("error");
        }
        System.out.println("success");
    
        
        
    }
}

工具准备
swftools.exe 下载
http://www.swftools.org/download.html
安装至D盘
SWFTools提供了一系列将各种文件转成swf的工具:
font2swf.exe
gif2swf.exe
jpeg2swf.exe
pdf2swf.exe
png2swf.exe
wav2swf.exe
这里我们只使用pdf2swf.exe

flexpaper下载
http://code.google.com/p/flexpaper/
这里我们使用已经编译好的FlexPaper的flash版本

[转载]ASP.NET MVC3 20个秘方-(3)验证用户的输入

mikel阅读(947)

[转载]【译】MVC3 20个秘方-(3)验证用户的输入 – 技术弟弟 – 博客园.

场景
你要确保你的表单捕获的数据包含你预期的数据,这些数据是基于你的数据库或模型设计。

解决方案

.NET 4.0 包含了一个新的数据注解命名空间,提供了一些有用的元数据属性类。这些类已经被应用到MVC3。

对于验证表单输入,下面的属性类可以用来提供各种各样
验证选 项:RequiredAttribute,RegularExpressionAttribute,RangeAttribute和 DataTypeAttribute。当需要自定义的验证的时候,MVC的3还支持改进ValidationAttribute类,允许开发人员定义的验 证。

讨论

接下来的例子是要去扩展“code-first book“model,这个model是在前一“秘方”中创建的。

这个model将按照以下条件被更新:

1. 书名是必须的

2. ISBN是合法的

3. 书的摘要是必须的

4. 作者是必须的

5. 合法的价格(美元)

6. 合法的出版日期

以上6个验证中的5个可以由MVC 3 的内置方法完成。然而,第5个验证需要用一种不同的格式化-它需要一个自定义验证方法。

 public class Book
    {
        public int ID { get; set; }
        [Required]
        public string Title { get; set; }
        [Required]
        [IsbnValidation]
        public string Isbn { get; set; }
        [Required]
        public string Summary { get; set; }
        [Required]
        public string Author { get; set; }
        public string Thumbnail { get; set; }
        [Range(1, 100)]
        public double Price { get; set; }
        [DataType(DataType.Date)]
        [Required]
        public DateTime Published { get; set; }
    }
    public class BookDBContext : DbContext
    {
        public DbSet<Book> Books { get; set; }
    }

在上边的例子,[Required]数据注解被附加在每个字段上,表明这个字段是必须由用户提供。在ISBN number上 [IsbnValidation]特性也被添加了,这是通知MVC 3 IsbnValidation 必须调用IsValid操作,这个操作即将被创建.为了验证价格,[Range] 注解被应用。对于价格的验证,我们也可以用正则表达式特性 [RegularExpression] 来完成。

如下:

[RegularExpression (@”(\b[\d\.]*)”)]

public double Price { get; set; }

最后,对于published date(出版日期)的验证,DataType特性告诉MVC这个字段的类型是一个日期类型。

一个合法ISBN的定义是:10-13个字符。为何合理的组织代码,自定义验证类将被放在一个单独的文件夹里。

右键点击项目:添加->新建文件夹。我们为这个文件夹命名为:Validations.在该文件夹点击右键。添加类:IsbnValidationAttribute.cs

代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text.RegularExpressions;

namespace MvcApplication.Validations
{
    [AttributeUsage(AttributeTargets.Field |
 AttributeTargets.Property, AllowMultiple = false,
 Inherited = true)]
    public class IsbnValidationAttribute :
System.ComponentModel.DataAnnotations.ValidationAttribute
    {
        /**
        * This class is courtesy:
        * http://www.java2s.com/Open-Source/CSharp/
        * Inversion-of-Control-Dependency-Injection/Spring.net/
        * Spring/Validation/Validators/ISBNValidator.cs.htm
        *
        * This class is used for demonstration purposes
        * of performing an ISBN validation. Should you
        * wish to use this in your project, please
        * consult the license agreement here:
        * http://www.apache.org/licenses/LICENSE-2.0
        **/
        private static readonly String SEP = "(?:\\-|\\s)";
        private static readonly String GROUP = "(\\d{1,5})";
        private static readonly String PUBLISHER = "(\\d{1,7})";
        private static readonly String TITLE = "(\\d{1,6})";
        static readonly String ISBN10_PATTERN =
        "^(?:(\\d{9}[0-9X])|(?:" + GROUP + SEP + PUBLISHER +
        SEP + TITLE + SEP + "([0-9X])))$";
        static readonly String ISBN13_PATTERN =
        "^(978|979)(?:(\\d{10})|(?:" + SEP + GROUP + SEP +
        PUBLISHER + SEP + TITLE + SEP + "([0-9])))$";
        public IsbnValidationAttribute() :
            base("Invalid ISBN number")
        {
        }
        public override bool IsValid(object value)
        {
            // Convert to string and fix up the ISBN
            string isbn = value.ToString();
            string code = (isbn == null)
            ? null :
            isbn.Trim().Replace("-", "").Replace("", "");
            // check the length
            if ((code == null) || (code.Length < 10
            || code.Length > 13))
            {
                return false;
            }
            // validate/reformat using regular expression
            Match match;
            String pattern;
            if (code.Length == 10)
            {
                pattern = ISBN10_PATTERN;
            }
            else
            {
                pattern = ISBN13_PATTERN;
            }
            match = Regex.Match(code, pattern);
            return match.Success && match.Index == 0 &&
            match.Length == code.Length;
        }
    }
}

创建完这个类记得在 book.cs添加命名空间引用:using MvcApplication.Validations;

上边的例子包含了一个标准的ISBN验证。这个验证是来自CSharp Open Source example。如果ISBN符合2个正则表达式中的一个。验证函数将返回true。否则返回false。需要用户重新输入

如果你在你的浏览器里转到图书创建页面。当你点击提交按钮。验证就被会触发。

另请参阅

DataAnnotations Namespace

译者注

关于用户验证,可以有更多方法,支持客户端/服务器端验证。

由于是翻译,我会尽量和原著保持一致,关于其他的验证方法会在后续的其他系列写出。

[转载]jQuery Mobile 开源框架对 Windows Phone 更好支持

mikel阅读(976)

[转载]jQuery Mobile 开源框架对 Windows Phone 更好支持 – MSDN 中文网站 – 博客园.

本篇博客原文jQuery Mobile Open Source Framework Support for Windows Phone选自Scott Hanselman的博客,由MSDN中文网站进行了汉化。

Web 和移动开发人员,大家好!

正如您可能已经注意到,JQuery Mobile 版本 1.0 已于本周公布。我们很高兴在这一激动人心的时刻再次强调我们对支持流行的开源移动框架的承诺。

在最近的活动中,我想强调的是在支持 PhoneGap 方面所进行的工作,我们增加了 PhoneGap 对 Windows Phone7.5 (Mango) 的支持,并且现在正在进一步改善在 Windows Phone7.5 中对 JQuery Mobile 的支持。

您可能已经知道,jQuery Mobile 框架是基于 JavaScript HTML5,面向移动设备平台的用户界面系统,构建于 JQueryjQuery UI 基础之上。

虽然今天的版本 1 和最近的 RC 版本包含了许多功能,但我们想花一分钟强调我们与 jQuery Mobile 团队开始的协作。在过去几周里,我们将注意力放在支持 Kin Blas 和社区中的其他人,以改善 Windows Phone7.5 的性能。

具体地讲,正如本周早些时候公布的 RC3 博客 指出的那样,如两个示例应用程序所示,Windows Phone 的性能已经有相当显著的改善:

  • 窗体图库的渲染改善了 226%,将它从 5 秒缩短到 2.2 秒
  • 渲染复杂的 400 项列表视图的时间从 60 秒缩短到 3 秒,速度提高了 20 倍

jQuery 团队还在更改日志中记录了更多针对 Windows Phone 的性能优化技巧,这将节省特定场景中的执行时间。

我们因这种进步而深受鼓舞,并将继续与社区合作,为 Windows Phone 带来更高水平的 jQuery 功能和性能的支持 …… 敬请关注,并再次祝贺 jQuery Mobile 团队!

Abu Obeida Bakhach

互操作性战略项目经理

[转载]在 jQuery Repeater 中检索过滤数据

mikel阅读(1034)

[转载]在 jQuery Repeater 中检索过滤数据 – 麦丝平方 – 博客园.

很多时候, 我们都会用到搜索的功能, 这次将向大家介绍 Repeater 如何设置检索数据.

示例代码下载: http://zsharedcode.googlecode.com/files/JQueryElementDemo.rar

本文将详细的讲解如何在 Repeater 中检索数据信息, 目录如下:

* 准备

* 设置 FilterTemplate

* 设置 FilterField 和 FilterFieldDefault

* 调用 setfilter 和 filter 函数

准备

请先查看 30 分钟掌握无刷新 Repeater 或者准备一节的内容.

设置 FilterTemplate

RepeaterFilterTemplate 属性中, 包含用于填写搜索条件的元素, 比如文本框, 日期框等. 此外, 也可以包含搜索按钮:

<je:Repeater ID="productList" runat="server"        ...    >

    <FilterTemplate>
        <tr>
            <td>
                <input type="text" size="10"
                    je-id="productname"
                    je-value="productname"/>
            </td>
            <td>
                <input type="text" size="5"
                    je-id="model"
                    je-value="model"/>
            </td>
            <td>
                <input type="text" size="4"
                    je-id="price1"
                    je-value="price1"/>
                -
                <input type="text" size="4"
                    je-id="price2"
                    je-value="price2"/>
            </td>
            <td>
                <input type="text" size="3"
                    je-id="amount"
                    je-value="amount"/>
            </td>
            <td>
                <input type="text" size="10"
je-datepicker="dateFormat='yy-mm-dd';changeMonth=true;changeYear=true"
                    je-id="manufactureDate1"
                    je-value="manufactureDate1"/>
                -
                <input
je-datepicker="dateFormat='yy-mm-dd';changeMonth=true;changeYear=true"
                    type="text" size="10"
                    je-id="manufactureDate2"
                    je-value="manufactureDate2"/>
            </td>
        </tr>
        <tr je-class="{highlight}">
            <td colspan="5" align="right">
                <span je-button=";" onclick="javascript:clearCondition();">清空</span>
                <span je-button=";" je-onclick="filter">搜索</span>
            </td>
        </tr>
    </FilterTemplate>

</je:Repeater>

代码中, 使用了 input 元素来添加文本框和日期框, 通过 je-id 来与 FilterField 属性中的搜索字段关联, je-value 则是表示使用指定搜索条件来初始化.

上面还使用了 je-datepicker 创建日期框, 至于如何使用 je-<JQueryUI 插件名> 创建更多 JQueryUI 插件, 可以参考 在 jquery repeater 中添加设置日期,下拉,复选框等控件.

通过 je-buttonje-onclick, 创建了用于搜索的按钮, je-onclick 被指定为 filter, 也就是将执行 repeaterfilter 方法, 可以参考 30 分钟掌握无刷新 Repeater 的特殊绑定一节.

设置 FilterField 和 FilterFieldDefault

FilterField 中设置的字段名, 将作为参数传递给服务器端的方法:

<je:Repeater ID="productList" runat="server"
    FilterField="['productname','price1','price2']"
    FilterFieldDefault="['',-1,-1]"
    FillAsync-Url="product.asmx"
    FillAsync-MethodName="GetProductList">

    <FilterTemplate>
        <tr>
            <td>
                <input type="text" size="10"
                    je-id="productname" je-value="productname"/>
            </td>

            <td>
                <input type="text" size="4"
                    je-id="price1" je-value="price1"/>
                -
                <input type="text" size="4"
                    je-id="price2" je-value="price2"/>
            </td>

        </tr>
    </FilterTemplate>

</je:Repeater>

代码中, 通过 FilterField 设置了 3 个用于搜索的字段 productname, price1, price2, 并通过 FilterFieldDefault 来设置这些搜索条件的默认值, 因此服务器端方法 GetProductList 可以采用如下的形式:

public SortedDictionary<string, object> GetProductList (
    int pageindex, int pagesize,
    string productname, float price1, float price2 )
{

        if ( price1 != -1 )
            ...

        if ( price2 != -1 )
            ...

}

如果 price1 和 price2 等于 -1, 则表示用户没有设置关于价格的搜索条件. 至于服务器端返回数据的格式, 请参考 30 分钟掌握无刷新 Repeater 的请求/返回数据的格式一节.

调用 setfilter 和 filter 函数

经过上面简单的设置, 已经可以通过 FilterTemplate 中的搜索按钮来检索数据, 此外, 还可以使用另一个方法:

<input id="myproductname" type="text" size="10"/>

<je:Button ID="cmdSearch" runat="server" Label="搜索 2" Click="
function(){
    productList.__repeater('setfilter', 'productname', $('#myproductname').val());
    productList.__repeater('filter');
}
">
</je:Button>

上面的示例中, id 为 myproductname 的文本框用来输入搜索的产品名称, 而在按钮的 Click 事件中, 调用 repeatersetfilter 方法来将 myproductname 中的值设置到 repeater 的过滤条件中, 调用 filter 方法来检索数据.

JQueryElement 是开源共享的代码, 可以在 http://code.google.com/p/zsharedcode/wiki/Download 页面下载 dll 或者是源代码.

实际过程演示: http://www.tudou.com/programs/view/HJmCQRpGQEg/, 建议全屏观看.

欢 迎访问 panzer 开源项目, http://zsharedcode.googlecode.com/ , 其中包含了 IEBrowser 控制 WebBrowser 执行各种 js 和 jQuery 脚本以及录制功能 和 jQueryUI 的 ASP.NET 控件 jQueryElement.

[转载]Asp.net MVC源码分析 -- 获取ModelBinder的优先级

mikel阅读(871)

[转载]Asp.net MVC源码分析 — 获取ModelBinder的优先级 – 十一月的雨 – 博客园.

ASP.NET mvc 框架中我们可以对System.Web.Mvc.Binders 进行扩展我们自定义的binder 类型,但是同时它还有一些其它的方法可以实现自定义的model binder.而且mvc在使用的时候还有一些策略,现分析如下:

获取ModelBinder 对象的入口方法是GetParameterValue, 其中

IModelBinder binder = GetModelBinder(parameterDescriptor);

这一句代码决定了ModelBinder 的使用策略。

System.Web.Mvc.ControllerActionInvoker

01 protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) {
02 // collect all of the necessary binding properties
03 Type parameterType = parameterDescriptor.ParameterType;
04 //Hey 请过来获取binder 的入口在这里,csdn 代段没法加高亮,垃极啊~!
05 IModelBinder binder = GetModelBinder(parameterDescriptor);
06 IValueProvider valueProvider = controllerContext.Controller.ValueProvider;
07 string parameterName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;
08 Predicate<string> propertyFilter = GetPropertyFilter(parameterDescriptor);
09
10 // finally, call into the binder
11 ModelBindingContext bindingContext = new ModelBindingContext() {
12 FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
13 ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),
14 ModelName = parameterName,
15 ModelState = controllerContext.Controller.ViewData.ModelState,
16 PropertyFilter = propertyFilter,
17 ValueProvider = valueProvider
18 };
19
20 object result = binder.BindModel(controllerContext, bindingContext);
21 return result ?? parameterDescriptor.DefaultValue;
22 }
23
24 private IModelBinder GetModelBinder(ParameterDescriptor parameterDescriptor) {
25 // look on the parameter itself, then look in the global table
26 return parameterDescriptor.BindingInfo.Binder ?? Binders.GetBinder(parameterDescriptor.ParameterType);
27 }

这里优先从 parameterDescriptor.BindingInfo中得到IModelBinder, 接下来我们看一下怎么从parameterDescriptor中获取binder

首先找到 ReflectedActionDescriptor对象,可以看到在GetParameters方法中生成了ReflectedParameterDescriptor 对象,

System.Web.Mvc.ReflectedActionDescriptor

01 public override ParameterDescriptor[] GetParameters() {
02 ParameterDescriptor[] parameters = LazilyFetchParametersCollection();
03
04 // need to clone array so that user modifications aren't accidentally stored
05 return (ParameterDescriptor[])parameters.Clone();
06 }
07 view plain
08 private ParameterDescriptor[] LazilyFetchParametersCollection() {
09 return DescriptorUtil.LazilyFetchOrCreateDescriptors<ParameterInfo, ParameterDescriptor>(
10 ref _parametersCache /* cacheLocation */,
11 MethodInfo.GetParameters /* initializer */,
12 parameterInfo => new ReflectedParameterDescriptor(parameterInfo, this) /* converter */);
13 }

这个对象中调用了ModelBinders.GetBinderFromAttributes方法来获取binder

ReflectedParameterDescriptor -> ReflectedParameterBindingInfo

1 public override IModelBinder Binder {
2 get {
3 IModelBinder binder = ModelBinders.GetBinderFromAttributes(_parameterInfo,
4 () => String.Format(CultureInfo.CurrentCulture, MvcResources.ReflectedParameterBindingInfo_MultipleConverterAttributes,
5 _parameterInfo.Name, _parameterInfo.Member));
6
7 return binder;
8 }
9 }

System.Web.Mvc.ModelBinders

1 internal static IModelBinder GetBinderFromAttributes(ICustomAttributeProvider element, Func<string>
2 errorMessageAccessor) {
3 CustomModelBinderAttribute[] attrs = (CustomModelBinderAttribute[])element.
4 GetCustomAttributes(typeof(CustomModelBinderAttribute),
5 true /* inherit */);
6
7 return GetBinderFromAttributesImpl(attrs, errorMessageAccessor);
8 }

接下来我们看

return parameterDescriptor.BindingInfo.Binder ?? Binders.GetBinder(parameterDescriptor.ParameterType);

语句后面的的分支.

System.Web.Mvc.ModelBinderDictionary

01 private IModelBinder GetBinder(Type modelType, IModelBinder fallbackBinder) {
02
03 // Try to look up a binder for this type. We use this order of precedence:
04 // 1. Binder returned from provider
05 // 2. Binder registered in the global table
06 // 3. Binder attribute defined on the type
07 // 4. Supplied fallback binder
08
09 IModelBinder binder = _modelBinderProviders.GetBinder(modelType);
10 if (binder != null) {
11 return binder;
12 }
13
14 if (_innerDictionary.TryGetValue(modelType, out binder)) {
15 return binder;
16 }
17
18 binder = ModelBinders.GetBinderFromAttributes(modelType,
19 () => String.Format(CultureInfo.CurrentCulture, MvcResources.ModelBinderDictionary_MultipleAttributes, modelType.FullName));
20
21 return binder ?? fallbackBinder;
22 }
23
24 internal static IModelBinder GetBinderFromAttributes(Type type, Func<string> errorMessageAccessor) {
25 AttributeCollection allAttrs = TypeDescriptorHelper.Get(type).GetAttributes();
26 CustomModelBinderAttribute[] filteredAttrs = allAttrs.OfType<CustomModelBinderAttribute>().ToArray();
27 return GetBinderFromAttributesImpl(filteredAttrs, errorMessageAccessor);
28 }

到这里我们就清楚的知道了获取IModelBinder的优先级。

结论:

1.如果在Action参数中的对象中标记了元数据的IModelBinder 实现时则调用该实现。//注:如果这句不理解可以继续看下面的实例

2.如果在参数中没有标记元数据的IModelBinder 实现 ,则调用的优先顺序为:

// (1).从全局的ModelBinderProviders.BinderProviders 中查找,我们也可以注册自己的provider
// (2).从全局的System.Web.Mvc.Binders 对象中查找,自己也可以注册自己的binder
// (3). 从参数对象的类型中的attribute 找到 ModelBinderAtribute中的IModelBinder 实现.
// (4). 最后如果经过以上步骤都没有找到IModelBinder的实现的话,就用MVC默认的实现DefaultModelBinder类,

1 public virtual IModelBinder GetBinder(Type modelType, bool fallbackToDefault) {
2 if (modelType == null) {
3 throw new ArgumentNullException("modelType");
4 }
5
6 return GetBinder(modelType, (fallbackToDefault) ? DefaultBinder : null);
7 }

示例代码:

下面我们通过实例代码来印证以上的结论。

01 //Model 类:
02 public class FormTestModel
03 {
04 [Required]
05 [DataType(DataType.Text)]
06 [Display(Name = "Room Name")]
07 public string RoomName { get; set; }
08
09 [Required]
10 [DataType(DataType.Text)]
11 [Display(Name = "Room Count")]
12 public string RoomCount { get; set; }
13
14 }
15
16 //Action类:
17 [HttpPost]
18 public ActionResult FormTest(FormTestModels model, string returnUrl)
19 {
20 <span style="white-space:pre">    </span>return view();
21 }
22
23 //我们新建四个IModelBinder 的实现;
24 internal sealed class FormTestModelBinderImpl1 : IModelBinder
25 {
26 public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
27 {
28 DefaultModelBinder defBinder = new DefaultModelBinder();
29 return defBinder.BindModel(controllerContext, bindingContext);
30 }
31 }
32 internal sealed class FormTestModelBinderImpl2 : IModelBinder
33 {
34 public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
35 {
36 DefaultModelBinder defBinder = new DefaultModelBinder();
37 return defBinder.BindModel(controllerContext, bindingContext);
38 }
39 }
40 internal sealed class FormTestModelBinderImpl3 : IModelBinder
41 {
42 public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
43 {
44 DefaultModelBinder defBinder = new DefaultModelBinder();
45 return defBinder.BindModel(controllerContext, bindingContext);
46 }
47 }
48 internal sealed class FormTestModelBinderImpl4 : IModelBinder
49 {
50 public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
51 {
52 DefaultModelBinder defBinder = new DefaultModelBinder();
53 return defBinder.BindModel(controllerContext, bindingContext);
54 }
55 }
56
57 public sealed class FormTestModelBinderProviderImpl : IModelBinderProvider
58 {
59 public IModelBinder GetBinder(Type modelType)
60 {
61 if (modelType == typeof(FormTestModels))
62 {
63 //"provider implementition";
64 return new FormTestModelsModelBinderImpl2();
65 }
66
67 return null;
68 }
69 }
01 然后我们分别加入:
02 //第一优先:
03 [HttpPost]
04 public ActionResult FormTest([ModelBinder(typeof(FormTestModelBinderImpl1))] FormTestModel model, string returnUrl)
05 {
06 return view();
07 }
08
09 //第二优先:
10 ModelBinderProviders.BinderProviders.Add(new FormTestModelBinderProviderImpl2());
11 //第三优先:
12 System.Web.Mvc.ModelBinders.Binders[typeof(FormTestModel)] = new FormTestModelBinderImpl3();
1 //第四优先:
2 [ModelBinder(typeof(FormTestModelBinderImpl4))]
3 public class FormTestModel
4 {
5 //codes
6 }

最后我们在四个binder上面设置断点,看一下代码执行的情况,就可以清楚的看到我们的结论是多么的无比正确.:)

最后感谢大家观看,以上分析如有雷同纯属巧合。

谢谢大家。

后记:

其它的相关文章:

MVC运行机制之源码剖析http://blog.csdn.net/study_live/article/details/4871745

[转载]分享.NET ERP项目开发中应用到的重量级工具 选择合适的工具和资源,做项目效率高而且规范程度高

mikel阅读(760)

[转载]分享.NET ERP项目开发中应用到的重量级工具 选择合适的工具和资源,做项目效率高而且规范程度高 – James Li – 博客园.

去年年初,我写过三篇文章,来讲解自己在.NET项目开发中用到的工具和资源。这三篇文章的地址是

《做一个项目,平时都用到哪些工具提高效率(上)》

《做一个项目,平时都用到哪些工具提高效率(中)》

《做一个项目,平时都用到哪些工具提高效率(下)》

经 过了近两年的发展和学习,技术方面有了长足的进步。而现在再来看那些工具和资源,自认为那时还处于工具系列的初级阶段,技术含量不高,也没有形成一个体 系。这两天在整理自己的.NET通用开发平台和框架,认为很有必要更新一下自己的工具系列,作为一个负责任的作者,所以有了这一篇文章,希望能给你带来帮 助。

这两年我一直从事WinForms的开发,所以没有关注到Web开发的工具。Web方面,一直想集成一个Web IIS到自己的WinForms程序中,这样做演示的时候,可以不用打开和配置IIS,也能测试HTTP方面的内容。对Web项目中的数据抓取也有一些经 验和体会,可以参考我的知识管理系统Data Solution。

代码生成 Code Smith

折 腾了很多代码生成器,自己也用C#+WinForms写过代码生成器,最后学乖了,做代码生成的工具,只选Code Smith,与代码生成相关的程序,只选择CodeDom。Code Smith有成熟的技术,数量庞大的模板库资源;而CdoeDom技术,一套源代码,可以同时生成C#和VB.NET两套代码。这是我选择它们的主要理 由。

选择Code Smith的一个主要原因是,不仅仅Code Smith Studio做的稳定,强大,而且它支持以API的方式调用模板引擎,这种强大的可编程性,远远的把第三方的工具抛在后面。如下面的代码所示

CodeTemplateCompiler compiler = new CodeTemplateCompiler("..\\..\\StoredProcedures.cst");
compiler.Compile();

if (compiler.Errors.Count == 0)
{
   CodeTemplate template = compiler.CreateInstance();

   DatabaseSchema database = new DatabaseSchema(new SqlSchemaProvider(), @"Data Source=.\SQLEXPRESS;
       AttachDbFilename=PetShop.mdf;Integrated Security=True;Connect Timeout=30;User Instance=True;");
   TableSchema table = database.Tables["Inventory"];

   template.SetProperty("SourceTable", table);
   template.SetProperty("IncludeDrop", false);
   template.SetProperty("InsertPrefix", "Insert");

   template.Render(Console.Out);
}
else
 {
    for (int i = 0; i < compiler.Errors.Count; i++)
    {
    Console.Error.WriteLine(compiler.Errors[i].ToString());
    }
}

在Code Smith Studio中制作模板,运行和调试模板。再以编程的方式把这个模板集成到自己的开发工具系列中。这项技术,我曾经写过一个Smith Builder,ERP代码生成器,它以集成的方式,把所有的代码生成的工具封装到一个环境。

image

2002年,有一篇文章《每个.NET开发人员应该下载的10种开发工具》中就提到它的名字,可见Code Smith在做代码生成方面是非常有经验的。Code Smith 6.0 已经于今年9月释放出来,但不如5.x系列的稳定。从5.3开始,Code Smith支持.NET 3.0/3.5的C#脚本代码,意味着可以应用Linq,C# 3.5的技术特性。

版本比较,合并 Beyond Compare

一个项目,通常会有多个客户同时在使用。一般建立的项目结构是,一个Development版,这是开发人员工作的版本,所有新特性,新功能,都会 加在这个版本中,这也导致了这是bug最多的一个版本;同时,在特定的时间阶段,建立一个Build版本,比如2012年,公司会把ERP项目升级到 4.0,于是所有的客户都会用到这个4.0版本。开发人员做好的程序功能,或是修改过的bug,都是建立在Development版本之上,要发送给客户 的是Build版本,于是要把Development版中的功能或是bug fixed添加到Build版本中。你不能直接把Development版本的代码复制到Build版中,这样会导致客户使用的版本非常不稳定。 Visual Studio Team Foundation创建了changset这个概念,表示对源代码的一个变更。

有一个常见的情景是,客户在Build版本中发现了问题,需要做出修复。程序员修改好Development版本,程序管理员负责把程序员修改好的 Development版本的代码,合并(Merge)到Build版本中。Visual Studio Team Foundation有它的文件比较工具,但是也推荐Beyond Compare这个工具,作为默认的比较合并工具,可见它的功能非常强大。

image

左边是Development开发版的文件,右边是Build版。用这个工具,马上就看到了程序员在开发版中做出的修改,点击差异片段左上方的小箭头,代码就被复制到右边。这样就完成了把开发版的功能合并到客户构建版本中。

还有另一种情形,经常忘记了合并开发版本的代码,随着时间的累计,Development版和Build版的差异越来越大,合并起来也越来越困难。 比如,客户那边的程序有问题,但是开发版没有问题,表示代码没有合并到Build版中。这个问题,一直是困扰着我们的团队,用肉眼去识别哪些代码是相同 的,哪些是不同的,几乎是不可能。这个工具就是很好的帮手。

对象关系映射 LLBL Gen

在以前的技术选型中,我会看一下,ORM是否合适大型的项目这类型的文章。可是找到的大部分文章都是批判ORM的效率问题。经过这两年的理解和消化,如果要做大型的项目,我一定会向你推荐ORM,也就是这里提到的LLBL Gen。

image

LLBL Gen 从3.x起,支持NHibernate,也就是说一个LLBL Gen项目文件,可以同时生成LLBL Gen Runtime的项目,也可以生成NHibernate的项目。它内置了基于模板的代码生成器,你可以定制所有的模板与代码生成的结果。

比如,为了能在Visual Studio中,当鼠标悬停在一个实体或字段名上时,可以显示它的源数据库对象。

当鼠标悬停在实体名上时,可以显示实体对应的表名

clip_image015_thumb2

当鼠标悬停在字段上时,可以显示字段对应的表名

clip_image016_thumb1

你需要修改它的模板。打开Template Binding Viewers,按照如下的内容编辑模板

  1. entityIncludeAdatper.template #549行 增加
    ///Mapped on <[ CaseCamel TargetType ]> field: <[SourceObjectName]>.<[SourceColumnName]></summary>
  2. entityAdapter.template #31 增加
    ///Mapped on table: <[ElementTargetObjectName]></summary>

这样对它的模板定制,就达到了上面的效果。

另一个可以做出的定制是,为项目生成一个partial类。比如GBEMP员工表,对应于实体类EmployeeEntity,这是代码生成器自动生成的,我还需要创建一个Entity.Implmentation.cs文件,用于存放我加的自定义逻辑。你需要这样做

image

点击右边的Add Tasks,在SD.Task.Base中添加一个CreateAFolder的任务,参数名字为EntityImplementation.cs,再添 加模板EntityImpemention.template,用Template Binding Viewers绑定到模板项中。看起来的效果是这样,添加模板ID与模板文件名的射影,如果模板不存在,它会提示你创建它。

image

虽然这么几句话,几个图片,就讲解完了我的思路和方法。我也没有夸奖它是如何的好,在这里仅仅以实例的方式,展示我理解的工具,以及我可以做的定制 工作。微软的.NET Framework API几乎处处都考虑到了Extension,或是Customization,我这里给你推荐的LLBL Gen 作为ERP项目开发中的ORM框架,也是基于它的扩展性,灵活性。

好吧,总结一下,要做NHibernate开发,可以选择LLBL Gen工具作为代码生成器。如果已经是LLBL Gen 2.x的老用户,升级到新的3.x版本也很容易。
抱怨一下,3.0版本的LLBL Gen不稳定,EntityCollection在做设计时数据绑定时,经过提示Unable to cast object of type “Foundation.EntityCollection” to type “SD.LLBLGen.Pro.ORMSupportClasses.IEntityCollection2”, 我发誓我什么也没有改,你就让我无法绑定数据。升级到3.1后,这个问题就没有再发生过。看来,自己也给人家当了一回小白鼠,也不是老外的产品就一定没有 bug,没有问题(issue)。要是被你发现了bug/issue,及时主动的给人家report issue,会是一个不小的鼓励。我自己在CodeProject看到文章后,发现了代码或是配置有问题,会及时留言,如果急,就给作者写email,通 常一两天就回复了。一个是Russian,另一位是我这个Chinese,用英语(in English)交流技术细节,这时你就会感觉语言还真是个用于交流的好东西。

日志 跟踪 Log Trace

好吧,我承认你手头有很多很厉害的log/trace工具。著名的log4net, Enterpise Libaray的Log Application Block,还有大量的自己写的log/trace 工具库。可是,我还是不忍心把这个东西藏在硬盘里,不让你知道,这就是著名的TraceTool。CodeProject中有一篇文章,标题是 《TraceTool 12.4: The Swiss-Army Knife of Trace》,你知道瑞士军刀很实用,是的,这里的Trace Tool也很实用。这个用Delphi写的实用工具,提供了任意编程语言的接口。

你可以在CodeProject的那篇文章中找到所有编程语言的接口例子,细节方面做的非常好。

image

在这个跟踪工具中看到的内容,是我下面的这几句代码所发射出来的

TTrace.Error.Send("Hello world");
TTrace.Warning.Send("Hello", "world");

TraceNode Hello = TTrace.Debug.Send("Hello"); // keep node reference

Hello.Send("world");           // use node to send sub trace

// or in one line :
TTrace.Debug.Send("Hello").Send("world");

不写log文件,也不写数据库。但是这个功能在跟踪你的程序运行流程方面,有很大的帮助。客户的服务器是不可以装上Debugger来给你发现问题的,除了报告异常的堆栈外,通过这个程序,把堆栈中的变量值输出来,及时查看。

为了可以输出当前的堆栈到trace窗口中,你可能需要这样的代码片段用于追踪call stack。

public static class Logger
{ 
    public static string CurrentStackDefaultLog()
    {
        // the true value is used to include source file info
        var l_CurrentStack = new System.Diagnostics.StackTrace(true);
        return l_CurrentStack.ToString();
    }

}

再深入到细节的地方,你可以参考CodeProject上的这篇《How to log the current call stack in .NET》。

我看中TraceTool的两点是,一是提供了图形化的界面可以看输出结果,二是支持任意的编程语言,连JavaScript都可以写日专输出,像这样的代码片段,用JavaScript写的trace代码。

var TTrace = new ActiveXObject("TraceToolCom.XTrace"); 
TTrace.Debug.Send("hello from jScript") ;

最后一点,它完全是free的,你不用到处找crack或是license key。即使你发现有问题了,下载它的源代码,跟踪进去看一下什么问题,也可以马上解决。

虚拟机 VMware Workstation

为了能建立一个真实的测试环境,我尝试过许多方法。在公司可以单独安装一台开发服务器,用于测试编译后的程序。在家里,通过笔记本,使用远程桌面, 连接到台式机里面,进行测试。但是我要改一下设置,结果要在公司的测试服务器和自己家里的测试主机中,同时更改,总觉得有些不方便,于是就有这个虚拟机软 件,构建一个尽可能真实的测试环境。

image

你可以也会选择Microsoft Virtual PC,或是Oracle Virtual Box,或者Windows Server 2008内置的虚拟机。都行,只要达到目的,尽可能建立一个测试环境就可以了。这里我有一些经验与你分享。

  1. 拒绝在虚拟机中安装Visual Studio,这是必要的。真实的环境,真的没有Visual Studio和它的Debugger。
  2. 同时准备两个虚拟机,一个x86,一个是x64的。这时,你就会发现Visual Studio中的Target=AnyCPU的作用。Visual C++中的Win32项目,只能在x86中跑。MSVCR.dll的含义是Microsoft Visual C++ Relase,表示是Release版本的动态连接库(在.NET中叫程序集Assembly),如果你的项目是用Debug方式编译的,它在这里是无法 运行的,会提示的找不到动态连接库,Debug方式编译的要找MSVCD.dll。在制作Smith Builder的过程中,因为Code Smith的Target是x86的,所以,我要引用到它的程序集CodeSmith.Engine.dll,则我的项目的Target必须设置为 x86,否则会提示bad image format。

Data Loader编译的Target是AnyCPU,同时我也提供它的x86版本,代码是这样写的

public class Program
{
    // Methods
    private static void Main(string[] args)
    {
        string str = Path.Combine(Application.StartupPath, "DataLoader.exe");
        Console.WriteLine("Trying to load and execute: {0} under x86", str);
        MethodInfo method = Assembly.LoadFile(str).GetType("Gui.Startup").GetMethod("Main", BindingFlags.NonPublic | BindingFlags.Static);
        object[] parameters = new object[] { args };
        method.Invoke(null, parameters);
    }
}

这个控制台项目的Target为x86,它反射DataLoader的启动窗体,并且调用它。以这种方式实现AnyCPU的程序以x86的方式运行。

3.  虚拟机只装操作系统的基本组件,再加上各种开发工具包的Runtime。如下面的这张图所示

image

这些基础的Runtime,都应该好好收藏起来,放在硬盘的角落里。

4. 如果你的硬盘空间充足,尽可能的准备x86和x64两种OS环境,两种Runtime,以充分测试。看下面的这张图片

image

为了测试到x64与x86中的问题,我把Visual C++的两种Runtime都下载到了硬盘中。这是一个细节方面的处理,也是为了能在交付程序之前,发现更多的问题。

5. .NET平台不是x86和x64的问题,而是旧的.NET程序集引用新版本的.NET程序集的问题。像下面这个图所示的,.NET 2.0的项目,引用了.NET 3.5的类型库,会抛出这个异常

image

项目的Target是.NET2.0,但是被引用的程序集编译是的Target是.NET 4.0,会出现上面的异常。强制引用的结果是,代码根本无法引有到程序集中定义的类型,编译出错。

为了看清楚一个程序集编译是依赖的.NET版本,你需要用Reflector 7打开这个程序集,查看References中的System.Core的版本,如下图所示,它是4.0,则表示要引用这个程序集的项目,它的Target至少是.NET 4.0。

image

我以为,把编译程序集的Target设为.NET 2.0是个好的选择,除非你真的需要应用.NET 3.5的特性。

在升级Workflow项目时,把.NET 3.5的工作流项目的Target改成.NET 4.0后,出现了很多未知的问题,改回去为.NET 3.5后,问题没了。在网上查到.NET 3.5的工作流和.NET 4.0的工作流,要当成完全不同的产品来对待。真遗憾,我肯定不会考虑升级工作流技术了,这样带来的问题和麻烦,远远超过你将要得到的好处。

这个分享工具系列的文章,差不多就介绍完了。在后继的文章中,我会介绍自己开发的工具,所做的的扩展Extension,以及最终把这些扩展和工具集,集成到一个环境中,做成一个ERP开发工具和框架平台,敬请期待。