开发目的:C#开发windows服务,并制作安装包。安装过程中配置系统参数。
功能描述:服务监控一个文件目录和其子目录,如果里面有新增rar的文件,就记录其信息。待到设定的时间,使用FTP上传功能,上传到FTP服务器。
代码示例:
1、文件目录监控类:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
namespace FTPUploadComponent
{
/// <summary>
/// 监控目录 FileSystemWatcher
/// </summary>
public class DirectoryWatcher : IDisposable
{
private FileSystemWatcher _fsw = null;
private FTPConfigSection _ftpSection = null;
private FTPWatcherItem _ftpWtchItem = null;
private FTPLog _ftpLog = null;
public DirectoryWatcher(FTPConfigSection ftpCfgSection)
{
if (_fsw == null)
{
_ftpSection = ftpCfgSection;
_ftpWtchItem = _ftpSection.WatcherCollection[0];
_ftpLog = new FTPLog(_ftpSection.LogCollection);
}
}
public void SetWatcherDir()
{
_fsw = new FileSystemWatcher(_ftpWtchItem.WatcherDir, _ftpWtchItem.WatcherFileType);
_fsw.IncludeSubdirectories = _ftpWtchItem.WatcherSubDir;
_fsw.EnableRaisingEvents = true;
_fsw.Created += new FileSystemEventHandler(DirectoryWatcher_Created);
while (true)
{
//一直运行,防止线程停止,对要监视的目录失效
Thread.Sleep(1000*60*24);
}
}
protected void DirectoryWatcher_Created(object sender, FileSystemEventArgs e)
{
string fileName = e.Name.Substring(e.Name.LastIndexOf('\\')+1);
try
{
Thread.Sleep(_ftpWtchItem.WatcherWaitIntival);
//ftpUpload.FTPUpLoadFile(e.Name);
//写记录日志
_ftpLog.LogOpeFlag = OpeFlag.Record;
_ftpLog.LogContent = string.Format("{0}|{1}", e.FullPath, fileName);
_ftpLog.WriteFtpLog();
}
catch (Exception ex)
{
//写失败记录
_ftpLog.LogOpeFlag = OpeFlag.Error;
_ftpLog.LogContent = string.Format("FTP upload file failed.Time is {0}.Rerson is {1}. FilePath:<#{2}#>",
DateTime.Now.ToString("yyyy-MM-dd HH:mi:ss"),
ex.Message,
e.FullPath);
_ftpLog.WriteFtpLog();
}
}
public void Dispose()
{
if (!_fsw.Equals(null))
{
_fsw.Dispose();
}
}
}
}
代码中只对监控目录的新增文件经行处理。如果有新增文件则由DirectoryWatcher_Created方法处理。
参数FTPConfigSection是自定的FTP配置文件对应的操作类。
2、FTP上传类:
using System;
using System.Net;
using System.IO;
using System.Threading;
namespace FTPUploadComponent
{
public class AsynchronousFtpUpLoader:IDisposable
{
private FTPUploadItem _ftpUpItem = null;
private string _ftpUri = "";
private FtpWebRequest _request = null;
private ManualResetEvent _waitObject = null;
private FTPLog _ftpLog = null;
public FTPLog FtpLog
{
set { _ftpLog = value; }
}
public AsynchronousFtpUpLoader(FTPUploadItem ftpUpItem,string fileName)
{
_ftpUpItem = ftpUpItem;
_ftpUri = string.Format(@"{0}:{1}/{2}", _ftpUpItem.FTPHost, _ftpUpItem.FTPPort,fileName);
if (_request == null)
{
_request = CreateFtpWebRequest();
}
}
private FtpWebRequest CreateFtpWebRequest()
{
if (_request != null) return _request;
Uri target = new Uri(_ftpUri);
_request = (WebRequest.Create(target)) as FtpWebRequest;
_request.Proxy = null;
_request.UseBinary = true;
_request.KeepAlive = false;
_request.Method = WebRequestMethods.Ftp.UploadFile;
_request.Credentials = new NetworkCredential(_ftpUpItem.FTPUserName, _ftpUpItem.FTPPwd);
return _request;
}
public void BeginGetRequest(string ftpUploadFileName)
{
string fileName = ftpUploadFileName;
FtpState state = new FtpState();
state.Request = _request;
state.FileName = fileName;
_waitObject = state.OperationComplete;
_request.BeginGetRequestStream(
new AsyncCallback(EndGetStreamCallback),
state
);
_waitObject.WaitOne();
if (state.OperationException != null)
{
//失败日志
_ftpLog.LogOpeFlag = OpeFlag.Error;
_ftpLog.LogContent = string.Format("FTP upload file failed.Time is {0}.Rerson is {1}. FilePath:<#{2}#>",
DateTime.Now.ToString("yyyy-MM-dd HH:mi:ss"),
state.OperationException.Message,
ftpUploadFileName);
_ftpLog.WriteFtpLog();
}
else
{
//成功日志
_ftpLog.LogOpeFlag = OpeFlag.Success;
_ftpLog.LogContent = string.Format("FTP upload file Success.Time is {0}.FilePath:<#{1}#>",
DateTime.Now.ToString("yyyy-MM-dd HH:mi:ss"),
ftpUploadFileName);
_ftpLog.WriteFtpLog();
}
}
private static void EndGetStreamCallback(IAsyncResult ar)
{
FtpState state = (FtpState)ar.AsyncState;
Stream requestStream = null;
// End the asynchronous call to get the request stream.
try
{
requestStream = state.Request.EndGetRequestStream(ar);
// Copy the file contents to the request stream.
const int bufferLength = 2048;
byte[] buffer = new byte[bufferLength];
int count = 0;
int readBytes = 0;
FileStream stream = File.OpenRead(state.FileName);
do
{
readBytes = stream.Read(buffer, 0, bufferLength);
requestStream.Write(buffer, 0, readBytes);
count += readBytes;
}
while (readBytes != 0);
// IMPORTANT: Close the request stream before sending the request.
requestStream.Close();
stream.Close();
stream.Dispose();
// Asynchronously get the response to the upload request.
state.Request.BeginGetResponse(
new AsyncCallback(EndGetResponseCallback),
state
);
}
// Return exceptions to the main application thread.
catch (Exception e)
{
state.OperationException = e;
state.OperationComplete.Set();
return;
}
}
private static void EndGetResponseCallback(IAsyncResult ar)
{
FtpState state = (FtpState)ar.AsyncState;
FtpWebResponse response = null;
try
{
response = (FtpWebResponse)state.Request.EndGetResponse(ar);
response.Close();
state.StatusDescription = response.StatusDescription;
// Signal the main application thread that
// the operation is complete.
state.OperationComplete.Set();
}
// Return exceptions to the main application thread.
catch (Exception e)
{
state.OperationException = e;
state.OperationComplete.Set();
}
}
public void Dispose()
{
if (_request != null)
{
_request.Abort();
_request = null;
}
if (_waitObject != null)
{
_waitObject.Close();
_waitObject.Dispose();
}
}
}
}
采用异步上传。参考MSDN中FtpWebRequest类用法。
其中URI指定的时候,格式是:ftp://地址/文件。如果不是这个格式,在异步获取读取文件流是报指定格式不正确错误。
以上监控类和上传类完成,下面就开始做安装包。
制作安装包步骤:
1、在解决方案中新建安装项目FTPUploadServiceSetUp
2、在新建的安装项目FTPUploadServiceSetUp中添加项目输出
项目输出选择为主输出 。项目选择要运行的服务类。
3、为打包的安装程序添加需要的文件
在应用目录中添加程序运行需要的dll、exe和配置文件。上图FTPUploadComponent.dll文件是封装的类库,FTPUploadConfigs.config是FTP上传需要配置的参数设置,InstallUtil.exe是安装服务需要的组件,uninstall.bat是卸载服务需要的批处理文件。
4、添加用户界面
添加后就可以自定义安装过程中的自定义界面。
上图中Textboxes(B),是获取FTP配置数据。Textboxes(C) 是配饰要监控的目录文件和配置。
右键Textboxes(B)属性,如下图,就可以配置配置参数
5、添加自定安装类
新建类库SetUpLib,在类库中添加Installer Class类。
6、重复第二步,把新增的自定义类,在安装项目中添加为主输出。
7、配置自定义类参数
右键安装项目,在视图中添加自定义操作。之后,在安装项,右键添加自定操作,选择刚才项目主输出添加的内容。
右键自定义操作属性,可以看到CustomActionData属性。这个属性就是获取从自定义界面获取到的值。
CustomActionData是用空格隔开的键值对。
具体使用参见http://msdn.microsoft.com/zh-cn/library/2w2fhwzz(v=vs.80).aspx
8、自定安装类获取自定义界面输入的数据
查看,安装类代码,可以看到该类继承了 System.Configuration.Install.Installer类。此类中定义了安装前,安装后要实现的内容,此例中,我们只重写了OnAfterInstall类。在安装成功后,把从自定义界面输入的内容写到配置文件中。代码如下:
protected override void OnAfterInstall(IDictionary savedState)
{
base.OnAfterInstall(savedState);
//host=[EDITB1] /port=[EDITB2] /username=[EDITB3] /pwd=[EDITB4]
// /targetdir=[TARGETDIR] /DirPath=[DIRPATH] /SubDir =[SUBDIR] /ExeInt=[EXECUTEINTERVAL]
//多值用空格隔开 采用/pwd=[EDITB4]这种新式作为一个键值对
List<string> values = new List<string>();
values.Add(Context.Parameters["host"].ToString());
values.Add(Context.Parameters["port"].ToString());
values.Add(Context.Parameters["username"].ToString());
values.Add(Context.Parameters["pwd"].ToString());
values.Add(Context.Parameters["TARGETDIR"].ToString().Replace(@"\\", @"\"));
values.Add(Context.Parameters["DirPath"].ToString().Replace(@"\\", @"\"));
values.Add(Context.Parameters["SubDir"].ToString());
values.Add(Context.Parameters["ExeInt"].ToString());
values.Add(Context.Parameters["WatFileType"].ToString());
//这里操作添加数据库,只要执行创建数据库的脚本就可以了。
//这个是测试在安装目录下添加接收到的用户填写的数据库信息
try
{
WriteConfig(values);
ExecBat(values[4], "install.bat");
}
catch(Exception ex)
{
File.AppendAllText(Path.Combine(values[4], "log.txt"),ex.Message);
}
}
代码中获取数据,参数名称对应的就是CustomActionData属性中配置的名称。WriteConfig就是把界面中的数据写入到配置文件中,ExecBat执行服务安装并启动服务。
生成安装项目。打包程序完成。
安装测试:
1、安装目录
改值可以通过/targetdir=[TARGETDIR]获取。
2、FTP参数配置
3、监控目录配置
服务安装包安装完成。
在很多项目中,我们需要在安装的过程中还原数据库,下一篇讨论如何在安装包中执行过程中还原sql Server数据库。