C/S架构应用程序开发培训笔记

最近为客户组织了一项C/S架构程序的开发培训,讲解C/S应用程序开发中需要注意的点。

我主要是做C/S方面的ERP/CRM程序开发,界面是用Windows Forms技术,有遗漏或错误的地方欢迎批评指正。

1 异常处理

为处理应用程序中的异常,需要增加以下代码。

Application.ThreadException += new ThreadExceptionEventHandler(eh.OnThreadException);
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

2  Excel文件生成

我们以Infragistics Excel作为生成Excel的基础组件。它提供一套面向对象的模型以简化Exel文件操作。

excelWorkbook = new Workbook();
Worksheet currentWorksheet = this.excelWorkbook.Worksheets.Add("WorkSheet1");
foreach (var cell in currentWorksheet.GetRegion("A1:D1"))
{
    cell.CellFormat.Fill = CellFill.CreateSolidFill(Color.Gray);
    cell.CellFormat.Font.ColorInfo = new WorkbookColorInfo(Color.White);
}

currentWorksheet.Rows[0].Cells[0].Value = "Order ID";
currentWorksheet.Rows[0].Cells[1].Value = "Contact Name";
currentWorksheet.Rows[0].Cells[2].Value = "Shipping Address";
currentWorksheet.Rows[0].Cells[3].Value = "Order Date";

currentWorksheet.Columns[0].Width = 3000;
currentWorksheet.Columns[0].CellFormat.Alignment = HorizontalCellAlignment.Left;
currentWorksheet.Columns[1].Width = 7100;
currentWorksheet.Columns[2].Width = 3000;
currentWorksheet.Columns[2].CellFormat.Alignment = HorizontalCellAlignment.Left;
currentWorksheet.Columns[3].Width = 6100;

如果需要将网格数据导出为Excel,它专门为此提供一个导入格式对象,简单的调用以下代码即可达到目的。

using (System.Windows.Forms.SaveFileDialog dialog = new System.Windows.Forms.SaveFileDialog())
{
     dialog.DefaultExt = "xls";
     dialog.Filter = Shared.ExportToFileFilter;
     dialog.Title = Microsoft.Common.Shared.TranslateText("Export to File");
     dialog.FileName = this.Text;
     if (dialog.ShowDialog() != DialogResult.OK)
     {
          return;
     }
     if (dialog.FilterIndex == 1 || dialog.FilterIndex == 2)
     {
         using (UltraGridExcelExporter exporter = new UltraGridExcelExporter())
        {
            exporter.BandSpacing = BandSpacing.None;
            exporter.Export(gridFunction, dialog.FileName);
        }
     }
 }

3 第三方类库

为了简化第三方类库的部署,我在项目中直接将需要引用到的第三方类库作为嵌入的资源生成为一个程序集。

这样在部署时,根据需要将我生成的程序集复制到执行文件目录即可。同时需要增加一个程序集加载事件。

AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;

static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
     return EmbeddedAssembly.Get(args.Name);
}

这个技巧来自于CodeProject,参考以下地址Load DLL From Embedded Resource

4 日志追踪

部署到生产环境中后,难免会出一些不可预料的异常。我使用SmartInspectPro来跟综这些问题。

官方网址是 http://www.gurock.com/smartinspect/

只需要下面简单的几行代码,就可以将程序中的异常信息或对象信息搜集起来,传送到日志查看工具中。

SiAuto.Si.Connections = "file(filename=c:\\log.sil)";
SiAuto.Si.Enabled = true;
SiAuto.Main.LogMessage("First Message!");

日志的内容可以写到文件,或是通过TCP或命名管道(named-pipes)发送到工具窗口中。

SiAuto.Si.Connections = string.Format("tcp(host={0},timeout=10000)", Microsoft.Common.Shared.ApplicationServer);

5 自动更新

以文件所在的位置来区分,我们考虑局域网,HTTP,FTP三种自动更新方式。.NET有许多自动更新组件,简单的列举。

http://wyday.com/wyupdate/

序号 名称 地址
1 AutoUpdater.NET https://autoupdaterdotnet.codeplex.com/
2 wyUpdate http://wyday.com/wyupdate/
3 Updater http://www.codeproject.com/Articles/9566/Updater
4 NetSparkle http://netsparkle.codeplex.com/
5 NAppUpdate https://github.com/synhershko/NAppUpdate
6 AutoUpdater https://autoupdater.codeplex.com/

微软本身也提供ClickOnce方式的更新方法,由于配置稍微麻烦我们并未采用。

6 版本检测

由于有多个客户的版本同时存在,我们在系统启动时,会检测当前文件夹中的所有文件的版本是否一致,如果不一致则抛出异常,终止执行。可参考如下的代码片段。

private static void VerifyAssembliesVersion()
{
    string[] files = Directory.GetFiles(Application.StartupPath, "Microsoft.EnterpriseSolution.*.dll", SearchOption.TopDirectoryOnly);

    Parallel.ForEach<string>(files, file =>
    {
       FileVersionInfo fileVersion = FileVersionInfo.GetVersionInfo(file);
       if (string.CompareOrdinal(fileVersion.FileVersion, AssemblyVersion.FileVersion) != 0)
          throw new AppException(string.Format("File version mismatch detected");                    }

7 源代码控制

我要提到的不是Team Foundation,SVN或Visual SourceSafe等源代码管理工具,而是如何控制客户正在使用的版本和程序员的开发版本。程序员的开发版本功能最多,同时也问题最多,许多新功能加入到程序中,没有经过完整的测试。

Team Foundation有一个分支管理功能,可以将客户正在使用的版本(正式版)看作是开发版本的(程序员开发)的一个子分支,每当在开发版中check in某项bug fix或feature并且经过完整测试后,将开发版本的变更集(changeset)合并到客户正在使用的分支版本中。

8 x86 x64 Any CPU的选择

现在.NET程序员真是太幸福了,编译时设定为Any CPU,JIT运行时根据机器的架构(x86,x64)生成相应的机器码。

我们的项目绝大多数情况下都选Any CPU作为生成架构。如果遇到一些编译依赖项它只有x86版本的程序集,这时我们考虑将依赖于这个x86的程序集的功能单独设计为一个DLL或EXE,这样整个项目还是以Any CPU架构来编译。

有时候出于安全原因,有一些代码以native语言来编写,比如C++,这时我们就分别生成两套(x86和x64)程序集,在部署时根据目标平台来部署相应架构的文件。

9 资源(图片,文档模板,标准报表)

为简化部署,我们将常用的资源项编译到一个程序集中。可参考以下代码提取嵌入的资源项。

 private static void ExtractEmbeddedResource(string resourceLocation, string output)
 {
   using (System.IO.Stream stream = Assembly.Load("Microsoft.Data").GetManifestResourceStream(resourceLocation))
   {
       using (BinaryReader r = new BinaryReader(stream))
       using (FileStream fs = new FileStream(output, FileMode.OpenOrCreate))
       using (BinaryWriter w = new BinaryWriter(fs))
       {
           w.Write(r.ReadBytes((int)stream.Length));
       }
   }
}

运行时我们从程序集中提取资源到硬盘临时文件夹,根据需要生成相应的文件返回给用户。

10 数据库访问

大型的项目离不开ORM,对象之间的运算与关联已不容易相处,如果还要去考虑数据读写,那程序的可维护性相对差很多。ORM带来的好处除了数据读写的完全解放,还有强类型的数据绑定。为此,我们的数据读写接口都是用Code Smith模板生成的,比如一个对象的读取方法

AccountEntity account = null;
using (DataAccessAdapter adapter = GetCompanyDataAccessAdapter(sessionId, companyCode))
{
    account = new AccountEntity(accountNo);
    bool found = adapter.FetchEntity(account, prefetchPath, null, fieldList);
    if (!found) throw new RecordNotFoundException(accountNo, "Invalid Account No.");
}

ORM带来另一个好处是强类型绑定,这样在设计时即可预知对象的类型和它的属性成员,方便做数据绑定。

ORM的第三个好处,可能是胜于直接写SQL语句(事务脚本模式)的地方,它会默认检测对象有哪些属性发生值改变,这样在保存对象时只会生成这些有发生值变更的SQL更新语句。许多同事甚至于我的上司都极度怀疑ORM的性能,我不确定他们是否真的验证过SQL语句(事务脚本模式)和ORM的性能比较。

11 性能

写的不合理的代码会导致性能问题,但不至于上升到怀疑技术的程度。微软的Entity Framework有那么多客户在用,难道这些客户的程序都是小规模,小应用吗? .NET代码的性能问题,我举例以下几个。

1) 主动要求GC进行垃圾回收会导致性能问题。

GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized);
GC.WaitForPendingFinalizers();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized);
最后在stackoverflow中找到回答是,任何时候都不应该调用此代码,注释以上代码后程序速度是快很多了。

2)  释放内存的代码会导致性能问题

[DllImport("kernel32.dll")]
private static extern bool SetProcessWorkingSetSize(IntPtr process, int minSize, int maxSize);

具体原因可参考这里

 http://www.cnblogs.com/kex1n/archive/2011/01/26/2286427.html

3) 反射会影响性能

这个结论不是空口而谈,我是用ANTS Performance Profiler 8亲自测试反射和非反射的代码的运行时间得出的结论。

比如我想增加一个动态报表控件,根据系统安装的水晶报表的版本来加载水晶报表控件。于是有以下两种写法

//反射版
object  _crystalReportViewer;
_crystalReportViewer = ReflectionHelper.CreateObjectInstance(CrystalReportHelper.GetLongAssemblyName("CrystalDecisions.Windows.Forms", CrystalReportVersion), "CrystalDecisions.Windows.Forms.CrystalReportViewer");

//非反射版
CrystalDecisions.Windows.Forms.CrystalReportViewer  _crystalReportViewer;
_crystalReportViewer=new  CrystalDecisions.Windows.Forms.CrystalReportViewer();

之后调用Load方法,反射版的Load方法需要耗费的时间要比非反射版本多一倍左右。

ReflectionHelper.InvokeMethod(_crystalReportViewer, "Load", new System.Type[] {typeof (string), obj3.GetType()}, new object[] {path, obj3});

至于是否要用反射,我的结论是取决于应用场景。如果应用要求运行速度第一,可维护性其次。则应用最快的那种方法。比如有些医药行业的录单模块,对键盘的响应速度要求极高,这时用反射是不合适的。

反射可以通过预处理(pre-init,pre-load)等方式提高响应速度,这样可在性能和可维护性方面双赢。

4) 频繁的数据库读写会有性能问题

ORM实在是太方便了,各种计算和取值,只需要取到对象即可完成,代码的可复用性高。不过有时候会导致性能问题。

在包含很多逻辑操作时,为了取一个字段值而去频繁的构造对象是不合适的。比如在一个采购单列表功能中,为了取到采购单的部门编码对应的部门名称,我们频繁的去取数据库,并且以构造对象的方法来完成,这样会导致性能问题。正确的做法是构造DataTable来完成,构造一个包含1000行记录的DataTable要比构造1000个部门对象(DepartmentEntity)要快很多。

ORM另一个好处是按需分配,我们可以根据需要只读取部分字段的值,好比SELECT * 与SELECT 具体字段的区别。

参考以下的代码,为了提高性能,我们的系统绝大多数情况下都是以这种方式读取数据库字段。

IItemManager itemMan = ClientProxyFactory.CreateProxyInstance<IItemManager>();
ExcludeIncludeFieldsList fieldList = new ExcludeIncludeFieldsList(false);
fieldList.Add(ItemFields.Description);
fieldList.Add(ItemFields.StockUom);
fieldList.Add(ItemFields.ScrapRate);
fieldList.Add(ItemFields.DefBomNo);
fieldList.Add(ItemFields.ExtendedDesc);
fieldList.Add(ItemFields.RohsCompliance);
fieldList.Add(ItemFields.TempDescription);
fieldList.Add(ItemFields.Specification);
fieldList.Add(ItemFields.ColorCode);

ItemEntity item = itemMan.GetValidItem(Shared.CurrentUserSessionId, this.PartItemNo, null, fieldList, Shared.SystemParameter.TailorSinojoint);

ExcludeIncludeFieldsList 对象可以理解为SELECT语句中的具体字段的集合。

5) 控件的不合适操作会引起性能问题

设定选项卡控件的选中的方法,以下代码中第一种要比第二种快

//快一点
tabControl.SelectedTab=tabControl.Tabs[0];
//慢一些
tabControl.Tabs[0].Selected=true;

水晶报表控件的设定数据源连接的时候,ApplyLogonInfo要比SetConnection慢。

//快一点的代码
reportDocument.DataSourceConnections[0].SetConnection(
    connectionStringBuilder.DataSource,
    connectionStringBuilder.InitialCatalog,
    connectionStringBuilder.UserID,
    connectionStringBuilder.Password
);

//慢一些的代码
crDatabase = crReportDocument,Database
crTables = crDatabase.Tables

For Each crTable In crTables
      crTableLogOnInfo = crTable.LogOnInfo
      crTableLogOnInfo.ConnectionInfo = crConnectionInfo
      crTable.ApplyLogOnInfo(crTableLogOnInfo)
Next

12 事件销毁

C/S程序包含丰富的事件机制,我认为可用性要高于B/S程序。但是随之而来的是代码要比B/S慢。

当我们的程序中有太多事件时,我们需要在窗本释放时,将这些事件从委托链中移出。

protected override void ReleaseResources()
{
  this.btnPrintRouting.Click -= new System.EventHandler(this.btnPrintRouting_Click);
  this.btnPrintMaterialsList.Click -= new System.EventHandler(this.btnPrintMaterialsList_Click);
  this.btnSortMaterials.Click -= new System.EventHandler(this.btnSortMaterials_Click);
}

protected override void Dispose(bool disposing)
{
   if (disposing && components != null)
   {
        components.Dispose();
   }
   ReleaseResources();
   base.Dispose(disposing);
}
这个方法也是为了改善性能。
 
时间: 2025-01-02 06:02:07

C/S架构应用程序开发培训笔记的相关文章

小程序开发--学习笔记

公众平台登录网页:https://mp.weixin.qq.com 小程序社区:http://developers.weixin.qq.com/ ------------------ 默认开发目录 ---------------- 1.  app.js.app.json.app.wxss 这三个文件必须有不能删掉. 一个小程序主体部分由这三个文件组成,而且必须放在项目的根目录 js后缀的是脚本文件,调用小程序框架提供的 API-- API 文档 json后缀的文件是对整个小程序的全局配置文件--

微信小程序开发个人笔记

1,配置文件.json 小程序的全局配置app.json和页面配置page.json每单页页面也有相应的.json文件,设置每个页面中.json配置,会覆盖与app.json相同的配置项.如下:是一个包含了所有配置选项的简单配置app.json "pages": [//设置页面的路径 "pages/index/index", //不需要写index.wxml,index.js,index,wxss,框架会自动寻找并整合 "pages/logs/logs&q

微信小程序-开发组件-笔记1

1.视图容器组件(view) view组件代表了一个页面的基本视图,也就是一个新的页面的最外层容器,相当于HTML中的DIV容器.对于每一个微信小程序的控件而言,有一些属性是通用的.表1是一些通用的属性. 表1  view属性和说明 属性名 类型 描述 注释 Id String 组件的唯一标识 保持整个页面唯一 class String 组件的样式类 在对应的wxss中定义的样式类 style String 组件的内联样式 可以动态设置的内联样式 hidden Boolean 组件是否显示 所有

#读书笔记#温伯格技术思想三部曲:程序开发心理学——第10章、积极性、培训及经验

在心理学家的眼中,人们在完成特定任务时的工作绩效,是由任务本身及其承担者对任务理解的深度共同决定的.心理学家同样认为,工作绩效同时也受到任务承担者个体在诸如性格和智力等方面差异的影响.尽管性格是可以改变的,而且智力也可以有所提高,但是工作绩效的实质提高还要依赖于培训和实践经验. 不过心理学本身并非一门严密的科学,而且也从来没有严密过.无论心理学家如何对任务及其承担者的理解深度进行考察,无论他们如何对任务承担者个体之间所有差异进行衡量,无论他们如何考虑到培训与实践经验,工作绩效总还是有很多方面无从

Linux及Arm-Linux程序开发笔记(零基础入门篇)

Linux及Arm-Linux程序开发笔记(零基础入门篇)  作者:一点一滴的Beer http://beer.cnblogs.com/ 本文地址:http://www.cnblogs.com/beer/archive/2011/05/05/2037449.html 目录 一.Arm-Linux程序开发平台简要介绍... 3 1.1程序开发所需系统及开发语言... 3 1.2系统平台搭建方式... 4 二.Linux开发平台搭建... 5 2.1安装虚拟工作站... 5 2.2安装Linux虚拟

《程序开发心理学读书笔记》

<程序开发心理学>出自软件领域著名思想家,美国计算机名人堂首批成员之一的Gerald M.Weinberg温伯格之手.其关注的是程序开发过程中人的因素,作者从人类行为.社会行为.个人行为等角度,分析了人在计算机程序开发的过程中所表现出的行为及其影响,探讨了诸如什么样的程序员才是好的程序员,怎样才能打造出优秀的开发团队,我们在求职或招聘时做的那些智力测验是否真的有用,等等这类问题.此书一共分为四篇13章,以下是我从每个章节中汲取到的知识以及自己的见解: 在第一篇<作为人类行为的程序开发&g

Linux 程序设计学习笔记----进程管理与程序开发(下)

转载请注明出处:http://blog.csdn.net/suool/article/details/38419983,谢谢! 进程管理及其控制 创建进程 fork()函数 函数说明具体参见:http://pubs.opengroup.org/onlinepubs/009695399/functions/fork.html 返回值:Upon successful completion, fork() shall return 0 to the child process and shall re

《程序开发心理学读书笔记之三》

以下是我对本书中最感兴趣的另一篇软件开发个人行为中收获的知识以及自己从中受益的一些启发: 从此篇中总结了一些我认为是比较好的作者的观点: (1)程序开发任务的差异:在程序开发的不同阶段,程序员之间的能力差异,因此,只要能够把所有人的努力划分为不同类型的工作,而不是不同的程序,那么任何项目的开发效率都可以得到这种量级的提高. (2)人格因素:所谓人格,就是一个人的所有特征的集合,根据这种具有唯一性的集合,能够确定这个人任何适应不断变化中的环境,同时反过来,这个集合本身也会受到这个适应过程的影响.任

程序开发心理学阅读笔记——结束篇

作为个人行为的程序开发及程序开发工具 程序开发的差异->人格因素->智力水平以及问题求解能力->积极性.培训以及实验1.我们所关注的个体偏差,可以进一步地按照一般性的方式被划分为“个性”.“智力”.以及“培训”或者“经验”.2.如果需要衡量程序员的绩效.语言的性能.操作系统的性能,或者其他方面,我们首先需要确定的是:所有人正在为之努力的,是同一个问题.3.要把定义说明转换成最中的程序,需要经过各种不同的工作:而为了完成这些不同的工作,就必须要有各种类型的人才.4.程序开发并不是一个一成不