2023年2月

FastReport.Net是一款适用于Windows Forms, ASP.NET和MVC框架的功能齐全的报表分析解决方案。可用在Microsoft Visual Studio 2005到2015,支持.Net Framework 2.0到4.x。我下载了一个FastReport进行测试使用,这个报表功能还是很强大的。

对其中FastReport的一些功能,我编写整理了一个小小的案例进行介绍,进行了简单的测试记录,希望对了解FastReport的使用有帮助。案例界面功能如下所示。

1、FastReport的汉化处理

默认安装FastReport提供多语言的资源,我们可以在程序运行的时候指定对应的语言位置和文件即可。

实现中文化界面的代码如下所示

            string baseDir = Path.Combine(Application.StartupPath, "Examples/TestFastReport");
FastReport.Utils.Res.LocaleFolder
= Path.Combine(baseDir, "L18N");var file = FastReport.Utils.Res.LocaleFolder + @"Chinese (Simplified).frl";
FastReport.Utils.Res.LoadLocale(file);

这样我们在运行界面后,就可以看到FastReport的汉化界面了。

2、FastReport打印预览

默认加入FastReport的控件,包含几个主要的界面控件,如下所示。

其中PeviewControl就是预览报表的控件,而DesignerControl则是设计报表的控件,我们这里介绍PeviewControl控件用来显示报表。

首先我们在一个空白的窗体界面上拖动一个PreviewControl进行报表的处理,如下界面所示。

剩下的就是如何展示报表内容了。

加载报表的设计文件代码如下所示。

            //加载报表设计文件
            this.Report = newFastReport.Report();var reportFile = Path.Combine(baseDir, "Report/Simple List.frx");this.Report.Load(reportFile);this.Report.Preview = this.previewControl1;

而报表设计界面加载完毕后,还需要指定报表的数据源,以便整体渲染呈现报表的内容,实现的代码如下所示。

            DataSet ds = newDataSet();var dataFile = Path.Combine(baseDir, "Report/nwind.xml");
ds.ReadXml(dataFile);
Report.RegisterData(ds,
"NorthWind");

Report.Prepare();
Report.ShowPrepared();

运行界面,就可以得到下面的报表界面效果。

整个报表支持很多其他类型的操作,如条形码、二维码、图表、图片等内容的展示,具体可以参考其官方案例的界面。

3、FastReport打印设计

上面介绍了 FastReport的PreviewControl,其设计控件DesignerControl的用法类似,不过这个控件是用来设计修改报表文件的,我们处理的代码如下所示。

加载报表设计文件代码如下。

            this.Report = newFastReport.Report();var reportFile = Path.Combine(baseDir, "Report/Simple List.frx");this.Report.Load(reportFile);

如果报表需要加载数据进行显示,那么需要加载报表数据。

            DataSet ds = newDataSet();var dataFile = Path.Combine(baseDir, "Report/nwind.xml");
ds.ReadXml(dataFile);
Report.RegisterData(ds,
"NorthWind");this.designerControl1.Report = this.Report;
Report.Prepare();
Report.Design();

运行界面,可以得到运行效果如下所示。

4、FastReport导出PDF

FastReport的另一个场景是可以不需要界面展示,直接通过设计文件,实现PDF文件的导出处理,实现界面代码如下所示。

        private void btnPDFReport_Click(objectsender, EventArgs e)
{
Report report
= newReport();var reportFile = Path.Combine(baseDir, "Report/Simple List.frx");
report.Load(reportFile);
//准备数据 DataSet ds = newDataSet();var dataFile = Path.Combine(baseDir, "Report/nwind.xml");
ds.ReadXml(dataFile);
report.RegisterData(ds,
"NorthWind");//运行报表 report.Prepare();//导出PDF报表 var file = FileDialogHelper.SavePdf("result.pdf");if (!string.IsNullOrEmpty(file))
{
PDFExport export
= newPDFExport();
report.Export(export, file);
}
report.Dispose();
if(File.Exists(file))
{
Process.Start(file);
}
}

这个部分没有报表展示,直接导出的PDF并存储,如果需要打开则可以看到报表的PDF文件如下所示。

5、FastReport使用实体业务对象生成报表

在我的Winform开发框架里面,主要采用的数据都是实体类对象数据。FastReport报表里面除了标准的DataSet数据源外,肯定也会支持实体类数据,这种实体类的业务对象数据也是使用很广泛的。

    private void btnRunExisting_Click(objectsender, EventArgs e)
{
//创建报表并加载设计文件 Report report = newReport();
report.Load(Path.Combine(baseDir,
"Report/report.frx"));//注册业务对象数据 report.RegisterData(FBusinessObject, "Categories");//运行报表 report.Show();
report.Dispose();
}

其中的数据对象初始化代码如下所示。

    private voidCreateBusinessObject()
{
FBusinessObject
= new List<Category>();

Category category
= new Category("Beverages", "Soft drinks, coffees, teas, beers");
category.Products.Add(
new Product("Chai", 18m));
category.Products.Add(
new Product("Chang", 19m));
category.Products.Add(
new Product("Ipoh coffee", 46m));
FBusinessObject.Add(category);

category
= new Category("Confections", "Desserts, candies, and sweet breads");
category.Products.Add(
new Product("Chocolade", 12.75m));
category.Products.Add(
new Product("Scottish Longbreads", 12.5m));
category.Products.Add(
new Product("Tarte au sucre", 49.3m));
FBusinessObject.Add(category);

category
= new Category("Seafood", "Seaweed and fish");
category.Products.Add(
new Product("Boston Crab Meat", 18.4m));
category.Products.Add(
new Product("Red caviar", 15m));
FBusinessObject.Add(category);
}

从上面我们可以看到,数据源是一个实体类集合的列表,从而展示如何使用这些数据源构造报表,运行界面效果如下所示。

FastReport的功能很强大,其设计文件是独立的,因此可以对报表设计文件进行修改调整,从而实现客户端的维护处理,它的功能也是很强大,支持在报表中添加文本、图像、线条、形状、语句、条形码、矩阵、表格、RTF、选择框等,列表报表、分组报表、主从报表、多列报表等内容。

在Winform开发中,我们也可以使用XtraReport报表和RDLC报表引擎,这方面可以参考我之前的随笔文章《
DevExpress的XtraReport和微软RDLC报表的使用和对比
》 和《
会员管理系统的设计和开发(2)-- RDLC报表的设计及动态加载
》进行了解,感谢大家对博客的支持。

在Winform界面开发的时候,有时候我们客户喜欢把功能放在列表界面的顶部,这样界面和功能整齐放置,也是一种比较美观的方式,基于这种方式的考虑,改造了代码生成工具的Winform界面生成规则,把增删改查的常规处理功能抽取简化的函数,易于在实际项目中使用工具栏的方式处理。

1、常规的Winform界面

我们在之前的界面中,通常都是使用一些按钮,以及右键菜单的方式进行功能的展示,如下界面所示。

这种方式是我们常规的界面生成和布局方式,对于功能相对较少的业务模块来说,是比较简洁的,多数操作都放在了右键菜单上了。

2、顶端工具栏的处理方式

如果对于一些流程处理,以及复杂业务处理功能的处理上,我们可以考虑在顶端加上一个工具条,放置所需的功能按钮,如下界面所示。

或者这样的小工具栏模式

这两种方式,都是把业务的功能处理,放在了工具栏里面,当然右键菜单还是可以保留相关的功能处理的。通过这种方式的布局,可以把很多功能放在一起统一处理,也是一个不错的处理方式。

为了方便使用这种方式,我们需要把对应不同功能的函数调用封装的更加方便些,因此可以调整代码生成工具Database2Shsarp进行这些规则的处理,从而简化了代码的处理。

在生成的代码模板里面,我们调整对应的代码结构即可生成下面的列表界面代码,如下所示。

其中的增加、编辑、删除、绑定(更新)、导入、导出等功能都可以直接调用这个函数即可,从而给我们在工具栏中整合提供更大的方便。

然后在后台代码中,注册相关事件的实例的代码如下所示。

        /// <summary>
        ///为工具栏注册相关的事件处理/// </summary>
        private voidRegisterEvent()
{
#region 工具栏处理事件 this.barAddNew.ItemClick += delegate(objectsender, DevExpress.XtraBars.ItemClickEventArgs e)
{
AddData();
};
this.barEdit.ItemClick += delegate(objectsender, DevExpress.XtraBars.ItemClickEventArgs e)
{
EditData();
};
this.barDelete.ItemClick += delegate(objectsender, DevExpress.XtraBars.ItemClickEventArgs e)
{
DeleteData();
};
this.barExport.ItemClick += delegate(objectsender, DevExpress.XtraBars.ItemClickEventArgs e)
{
ExportData();
};
this.barRefresh.ItemClick += delegate(objectsender, DevExpress.XtraBars.ItemClickEventArgs e)
{
BindData();
};
this.barPreview.ItemClick += delegate(objectsender, DevExpress.XtraBars.ItemClickEventArgs e)
{
PreviewData();
};
this.barBack.ItemClick += delegate(objectsender, DevExpress.XtraBars.ItemClickEventArgs e)
{
//撤回 UpdateTrack(barBack);
};
this.barConfirm.ItemClick += delegate(objectsender, DevExpress.XtraBars.ItemClickEventArgs e)
{
//确认 UpdateTrack(barConfirm);
};
#endregion this.winGridViewPager1.GridView1.SelectionChanged += delegate(objectsender, SelectionChangedEventArgs e)
{
this.SetButtonEnabled();
};
}

以上就是针对这种界面布局对代码生成工具生成Winform界面代码的改进,从而使得我们根据方便调用不同的功能逻辑实现更加丰富的界面控制处理。

在我们很多情况下的开发,为了方便或者通用性的考虑,都首先考虑SQL Server数据库进行开发,但有时候客户的生产环境是Oracle或者其他数据库,那么我们就需要把对应的数据结构和数据脚本转换为对应的数据库,数据结构一般来说,语法都遵循了SQL92的标准,或者我们根据不同的PowerDesigner文件进行生成对应的结构脚本即可,但是实际数据的脚本我们就需要进行一定的处理,以及文本的替换处理了,本文结合Notepad++的文本正则表达式替换,实现一些如日期较为特殊的数据脚本调整,把它从SQL Server转换为Oracle的处理过程,本文就是针对这些整体的数据库处理进行介绍。

1、数据库设计文件及数据库结构脚本

我们一般在做数据库设计的时候,都会使用PowerDesigner这样的数据库建模工具进行设计,默认把它设计为SQL Server的数据库设计模型,如下所示。

当然我们如果需要其他数据库,那么把它转换为对应的数据库,然后进行一定的数据库类型调整,以及字段的大小写转换即可。

根据这种方式我们调整后的各种数据库设计文件如下所示。

不同的数据库的设计模型有所差异,那么我们进行一些核对,主要是数据库类型的核对即可,如
备注字段的大文本应该设置为CLOB,二进制的应该调整为BLOB等

例如对于Oracle的数据库设计(从SQL Server转换过来的),同时也需要把它的字段名转换为大写才好,在PowerDesigner里面可以执行自定义函数进行处理。

其中在对话框选择打开对应的大写字段表名的脚本,如下操作。

为了大家方便使用,我把它贴出来,供使用。

'文件:powerdesigner.ucase.VBs'版本:1.0'功能:遍历物理模型中的所有表,将表名、表代码、字段名、字段代码全部由小写改成大写;'并将序列的名和代码由小写改成大写。'用法:打开物理模型,运行本脚本(Ctrl+Shift+X)'备注:'*****************************************************************************
dim model 'current model
set model =ActiveModelIf (model Is Nothing) Then
MsgBox "There is no current Model"
ElseIf Not model.IsKindOf(PdPDM.cls_Model) Then
MsgBox "The current model is not an Physical Data model."
ElseProcessTables model
ProcessSequences model
End If '*****************************************************************************'函数:ProcessSequences'功能:递归遍历所有的序列'***************************************************************************** subProcessSequences(folder)'处理模型中的序列:小写改大写 dimsequencefor each sequence infolder.sequences
sequence.name
= UCase(sequence.name)
sequence.code
= UCase(sequence.code)next end sub '*****************************************************************************'函数:ProcessTables'功能:递归遍历所有的表'***************************************************************************** subProcessTables(folder)'处理模型中的表 dimtablefor each table infolder.tablesif not table.IsShortCut thenProcessTable tableend if next '对子目录进行递归 dimsubFolderfor each subFolder infolder.Packages
ProcessTables subFolder
next end sub '*****************************************************************************'函数:ProcessTable'功能:遍历指定table的所有字段,将字段名由小写改成大写,'字段代码由小写改成大写'表名由小写改成大写'***************************************************************************** subProcessTable(table)dimcolfor each col intable.Columns'将字段名由小写改成大写 col.code = UCase(col.code)
col.name
= UCase(col.name)nexttable.name= UCase(table.name)
table.code
= UCase(table.code)end sub

这样处理后,我们在PowerDesigner里面的表名及字段就可以正常转换为大写了,从而可以获得对应表的数据结构脚本,如果需要多个表,那么可以批量生成数据库结构脚本。

2、数据库表数据脚本的生成

上面的数据库表结构的脚本生成,只是我们数据库迁移脚本的一部分操作,有时候我们实际的框架或者业务系统里面,都往往有一些基础数据需要写入的,那么就需要我们构建对应的数据脚本了。

在数据库脚本导出的,我们可以使用很多工具,如SQL Server本身的工具就可以导出数据的SQL脚本,同时我们也可以利用其它数据库管理工具,如Toad For SQLServer或者Navicat Premium等数据库管理工具实现数据的导出脚本操作。

然后在生成脚本的过程中,设置输出的高级选项中的“要编写脚本的数据的类型”为【仅限数据】即可,如下所示。

不过默认采用SQLServer生成出来的数据脚本,对日期类型转换真不是很好,如下结果所示。

因此我使用更加直观显示的Navicat Premium 工具来处理数据库的数据脚本。使用Navicat Premium生成的脚本如下所示(仅仅日期类型有所不同)。

获得生成的数据脚本如下所示。

剩下的工作就是我们对这些数据脚本进行进一步的处理操作了。

3、数据表的数据脚本的替换处理

上面介绍了通过工具来获得正确的数据脚本,我们使用了Navicat Premium或者 Toad For SQLServer都能够获得类似下面格式的时间脚本。

N'2016-06-22 10:35:36.590'

这样我们为了处理为Oracle的日期数据,那么需要转换为

to_date('2016-06-22 10:35:36','yyyy-MM-dd HH24:mi:ss')

这样的格式

那么我们对上面的脚本,进行一定规则的处理,如替换:[dbo]. [ ] N'为' 等常规文本处理后,还需要再进行正则表达式规则的处理才可以,例如我们的日期替换的正则表达式如下:

'(\d{4}-\d{2}-\d{2}\s*\d{2}:\d{2}:\d{2})\.\d{3}'
to_date\('\1','yyyy-MM-dd HH24:mi:ss'\)

如下所示。

最后使用正则表达式替换后的数据库脚本如下所示。

4、数据脚本在PL-SQL Developer工具上执行操作

上面介绍如何实现了表数据的脚本生成,有了这些脚本,我们需要使用Oracle的数据库管理工具 PL-SQL Developer工具进行数据导入,才能最终完成整个过程。这个操作也是有所讲究的。

例如我们创建各类不同的数据库脚本,那么只需要按照顺序加入或者选择加入执行数据库脚本即可。

那么执行这些SQL,该如何操作呢,是不是直接拖动到PL-SQL上就可以了?

当然不是,否则长一点的数据库脚本,就可能导致非常迟缓的执行效率。

一般可以通过两种方式,一种是使用命令行的方式。

这种方式执行速度非常快,比起直接在PL-SQL的SQL窗口上执行更有效率。

另外一种方式,就是可以利用PL-SQL里面的另外一个地方进行执行数据库脚本,如下所示。

在【 工具】【导入表】的操作里面,弹出一个对话框,也是执行脚本高效的操作之一。

上面介绍的这些方式,就是在数据库没有的情况下,根据数据库脚本构建对应的数据对象和数据的。

在之前一段时间里面,我的基类多数使用lock和Hashtable组合实现多线程内缓存的冲突处理,不过有时候使用这两个搭配并不尽如人意,偶尔还是出现了集合已经加入的异常,对代码做多方的处理后依然如故,最后采用了.NET 4.0后才引入的ConcurrentDictionary多线程同步字典集合,问题顺利解决。

1、使用lock和Hashtable组合实现

在我的基类里面,构建业务对象,一般用BLLFactory<T>.Instance就可以获得对应业务对象的应用了。

var result = BLLFactory<Customer>.Instance.FindFirst();
Console.WriteLine(result.ToJson());

因此使用BLLFactory<T>.Instance这个构建对象后,把它们放到HashTable里面,由于需要设计多线程冲突处理,因此需要使用lock对象来实现锁定的处理。

HashTable表示键/值对的集合。在.NET Framework中,Hashtable是System.Collections命名空间提供的一个容器,用于处理和表现类似key-value的键值对,其中key通常可用来快速查找,同时key是区分大小写;value用于存储对应于key的值。Hashtable中key-value键值对均为object类型,所以Hashtable可以支持任何类型的keyvalue键值对,任何非 null 对象都可以用作键或值。

使用这种方式,偶尔在Web端,还是出现多线程访问冲突的问题,为此我们也可以使用多线程的测试代码来进行测试重现错误,

            try{
List
<Thread> list = new List<Thread>();for (int i = 0; i < 10; i++)
{
Thread thread
= new Thread(() =>{var result = BLLFactory<Customer>.Instance.FindFirst();
Console.WriteLine(result.ToJson());
Console.WriteLine();
});

list.Add(thread);
}
for (int i = 0; i < list.Count; i++)
{
list[i].Start();
}
}
catch(Exception ex)
{
LogTextHelper.Error(ex);
}

跟踪代码得到错误信息如下所示。

因此,从上面代码可以看到,使用lock(syncRoot)也无法出现的多线程冲突问题。

2、使用ConcurrentDictionary替代Hashtable

ConcurrentDictionary是.net4.0推出的一套线程安全集合里的其中一个,和它一起被发行的还有ConcurrentStack,ConcurrentQueue等类型,它们的单线程版本(线程不安全的,Queue,Stack,Dictionary)我们一定不会陌生。ConcurrentDictionary<
TKey,
TValue>

可由多个线程同时访问,且线程安全,用法同Dictionary很多相同,但是多了一些方法。ConcurrentDictionary 属于System.Collections.Concurrent 命名空间。

System.Collections.Concurrent 命名空间提供多个线程安全集合类。当有多个线程并发访问集合时,应使用这些类代替 System.Collections 和 System.Collections.Generic 命名空间中的对应类型

ConcurrentDictionary这个类提供了下面几个方法,用于对集合的处理

public boolTryAdd(TKey key, TValue value)public boolTryUpdate(TKey key, TValue newValue, TValue comparisonValue)public TValue this[TKey key] { get; set; }public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue>updateValueFactory)public TValue AddOrUpdate(TKey key, TValue addValue, Func<TKey, TValue, TValue>updateValueFactory)publicTValue GetOrAdd(TKey key, TValue value)public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)

使用ConcurrentDictionary来替代Hashtable,我们来看看BLLFactory的类的实现代码如下所示。

    /// <summary>
    ///对业务类进行构造的工厂类/// </summary>
    /// <typeparam name="T">业务对象类型</typeparam>
    public class BLLFactory<T> where T : class{//采用ConcurrentDictionary线程安全的集合类来缓存,替代Hashtable
        private static ConcurrentDictionary<string, object> conCurrentCache = new ConcurrentDictionary<string, object>();/// <summary>
        ///创建或者从缓存中获取对应业务类的实例/// </summary>
        public staticT Instance
{
get{string CacheKey = typeof(T).FullName;return (T)conCurrentCache.GetOrAdd(CacheKey, s =>{var bll = Reflect<T>.Create(typeof(T).FullName, typeof(T).Assembly.GetName().Name); //反射创建,并缓存 returnbll;
});
}
}
}

我们可以看到代码简化了很多,而且使用前面的多线程测试代码,也顺利获取数据,不会出现异常了。

运行代码可以顺利实现,不会出现之前使用Hashtable出现的多线程访问异常了。

以上就是引入ConcurrentDictionary替代Hashtable对多线程的对象缓存处理,能够顺利解决问题的时候,发现其访问效率也是较之前有所提高,一举两得。

在之前介绍的附件管理模块里面《
Winform开发框架之通用附件管理模块
》以及《
Winform开发框架之附件管理应用
》,介绍了附件的管理功能,通过对数据库记录的处理和文件的管理,实现了附件文件和记录的整合管理,可以运用在单机版的WInform框架,也可以使用在分布式的混合式开发框架中,随着一些开发场景的丰富,我们需要以FTP方式上传文件,因此对这个附件管理模块进行扩展,以便适合更多的实际项目需求。

1、FTP上传、HTTP文件预览实现思路

我们设想的附件管理,底层都是需要在Winform、Web等开发项目上重用的,因此底层的设计需要考虑好对应的处理,另外后面可以利用WInform的HTML编辑控件、或者Web的HTML编辑控件进行集成,附件则是统一在一个组件里面实现的。

借助FTP的文件上传,我们单机版本或者基于局域网的Winform界面程序,也可以单独构建一个FTP服务器,实现文件的共享;而分布式的混合式开发框架中,对于文件的上传,可以选择基于服务的文件系统写入,同时也可以基于FTP的方式上传。

基于混合式框架的FTP方式上传文件,其逻辑关系如下所示。

这样文件通过FTP方式上传的文件系统后,我们在文件系统里面搭建一个HTTP服务,这样对应上的HTTP地址就可以实现文件的下载,以及图片的查看等操作了(可以在HTML编辑器中实现)。

2、引入FTP组件实现文件上传

使用FTP上传,虽然在自己的公用类库里面有FTPHelper类可以使用,不过相对来说,我更愿意引入更为完善强大的FTP开源组件进行相关的处理,这里我们使用FluentFTP这个组件(GitHub地址:
https://github.com/hgupta9/FluentFTP
),这个是一个应用很广,功能很强大的FTP组件。

FluentFTP是一款老外开发的基于.Net的支持FTP及的FTPS 的FTP类库,FluentFTP是完全托管的FTP客户端,被设计为易于使用和易于扩展。它支持文件和目录列表,上传和下载文件和SSL / TLS连接。它可以连接到Unix和Windows IIS建立FTP服务器。这个项目是完全开发托管C #。

这个组件的使用代码,这里粘贴一下,以便总体有一个直观的了解吧。

//create an FTP client
FtpClient client = new FtpClient("123.123.123.123");//if you don't specify login credentials, we use the "anonymous" user account
client.Credentials = new NetworkCredential("david", "pass123");//begin connecting to the server
client.Connect();//get a list of files and directories in the "/htdocs" folder
foreach (FtpListItem item in client.GetListing("/htdocs")) {//if this is a file
    if (item.Type ==FtpFileSystemObjectType.File){//get the file size
        long size =client.GetFileSize(item.FullName);

}
//get modified date/time of the file or folder DateTime time =client.GetModifiedTime(item.FullName);//calculate a hash for the file on the server side (default algorithm) FtpHash hash =client.GetHash(item.FullName);

}
//upload a file client.UploadFile(@"C:\MyVideo.mp4", "/htdocs/big.txt");//rename the uploaded file client.Rename("/htdocs/big.txt", "/htdocs/big2.txt");//download the file again client.DownloadFile(@"C:\MyVideo_2.mp4", "/htdocs/big2.txt");//delete the file client.DeleteFile("/htdocs/big2.txt");//delete a folder recursively client.DeleteDirectory("/htdocs/extras/");//check if a file exists if (client.FileExists("/htdocs/big2.txt")){ }//check if a folder exists if (client.DirectoryExists("/htdocs/extras/")){ }//upload a file and retry 3 times before giving up client.RetryAttempts = 3;
client.UploadFile(
@"C:\MyVideo.mp4", "/htdocs/big.txt", FtpExists.Overwrite, false, FtpVerify.Retry);//disconnect! good bye! client.Disconnect();

有了这些了解,我们在普通Winform程序或者混合式框架的的程序中,我们通过配置指定FTP的相关信息,就可以在代码里面加载这些信息,进行FTP的登陆、文件上传、下载等操作了。

3、附件管理模块实现

有了上面的思路和组件的辅助,我们对原来的附件管理模块进行相关的升级处理即可实现FTP上传模式的处理了。

首先为了方便,我们先定义一个获取FTP服务器、用户名、密码等参数的配置实体类,如下所示。

    /// <summary>
    ///FTP配置信息/// </summary>
[DataContract]
[Serializable]
public classFTPInfo
{
/// <summary> ///默认构造函数/// </summary> publicFTPInfo()
{

}
/// <summary> ///参数化构造函数/// </summary> /// <param name="server"></param> /// <param name="user"></param> /// <param name="password"></param> public FTPInfo(string server, string user, string password, stringbaseUrl)
{
this.Server =server;this.User =user;this.Password =password;this.BaseUrl =baseUrl;
}
/// <summary> ///FTP服务地址/// </summary> [DataMember]public string Server { get; set; }/// <summary> ///FTP用户名/// </summary> [DataMember]public string User { get; set; }/// <summary> ///FTP密码/// </summary> [DataMember]public string Password { get; set; }/// <summary> ///FTP的基础路径,如可以指定为IIS的路径:http://www.iqidi.com:8000 ,方便下载打开/// </summary> [DataMember]public string BaseUrl { get; set; }
}

定义一个函数,专门用来提取配置文件里面的相关FTP参数的,如下所示。

        /// <summary>
        ///获取配置的FTP配置参数/// </summary>
        /// <returns></returns>
        privateFTPInfo GetFTPConfig()
{
var ftp_server = config.AppConfigGet("ftp_server");var ftp_user = config.AppConfigGet("ftp_user");var ftp_pass = config.AppConfigGet("ftp_password");var ftp_baseurl = config.AppConfigGet("ftp_baseurl");return newFTPInfo(ftp_server, ftp_user, ftp_pass, ftp_baseurl);
}

其中我们的配置文件如下所示。

使用FluentFTP的组件代码如下所示。

//使用FluentFTP操作FTP文件
FtpClient client = new FtpClient(ftpInfo.Server, ftpInfo.User, ftpInfo.Password);

然后调用FTP组件对目录进行判断,无则创建一个即可。

//确定日期时间目录(格式:yyyy-MM),不存在则创建
string savePath = string.Format("/{0}-{1:D2}/{2}", DateTime.Now.Year, DateTime.Now.Month, category);bool isExistDir =client.DirectoryExists(savePath);if(!isExistDir)
{
client.CreateDirectory(savePath);
}

最后使用组件上传文件即可,这里上传文件,由于前面FileUploadInfo实体类里面存储的是字节数组,因此也是采用FTP组件直接上传字节数组即可。

//使用FTP上传文件//避免文件重复,使用GUID命名
var ext =FileUtil.GetExtension(info.FileName);var newFileName = string.Format("{0}{1}", Guid.NewGuid().ToString(), ext);//FileUtil.GetFileName(file);
savePath=savePath.UriCombine(newFileName);bool uploaded = client.Upload(info.FileData, savePath, FtpExists.Overwrite, true);

文件上传到文件服务器后,剩下的就是把相关的信息存储到附件管理模块的数据表里面即可,这样可以在使用的时候,直接使用数据库里面的信息,如果是需要查看图片或者下载文件,那么拼接好相关的HTTP地址即可,我们来看看对应的数据库记录截图如下所示。

有了这些基础信息,我们可以同时改造我之前介绍过的Winform之HTML编辑控件:ZetaHtmlEditControl了(
分享一个Winform里面的HTML编辑控件Zeta HTML Edit Control,汉化附源码
),我对这个控件所有英文的菜单、工具栏、对话框、提示内容等资源进行中文化后,并在工具栏中增加插入图片、打印功能后,界面如下所示。

默认情况下,我们加入图片的方式,肯定还是基于本地文件的方式了;但是经过我们改造使用FTP上传文件方式后,在控件上获得HTTP地址,就可以对图片文件进行预览展示的操作了。

这种方法构造的图片地址,属于标准的URL地址,可以在各个地方进行查看的,如下界面所示。

这个就是ZetaHtmlEditControl控件,整合我们前面已经完成了FTP上传模式的附件管理模块,实现编辑在线HTML的功能,这样的HTML内容,同样可以适合在Web界面下的HTML编辑器上进行展示了。

以上就是我为整个WInform开发框架构造的项目组件,增加的FTP上传方式,同时完善了对应的场景需求,在ZetaHtmlEditControl控件上实现编辑在线HTML的功能,希望开发的思路对您有所增益。