很多时候会想在vs编译的时候自定义一些事情或者动作,
例如:
拷贝生成的文件到特定的目录。
部署程序到测试目录或者环境,例如注册到windows服务,更新GAC等。
根据编译环境生成特定的配置文件(例如web.config) PS: 身在一个复杂环境, 这是我最想要的功能。
自动执行外部exe。
同步DLL和其他资源文件。
1.最简单的自然是用Visual Studio自带的编译事件,这东西使用方便,又是Visual Studio自带的功能,就是功能弱了一点(好吧 其实是很弱)
将项目生成的DLL文件拷贝到特定目录,(如果你想拷贝一整个文件夹 用xcopy; 当然,熟悉命令行的人可以弄出更多的玩法)
如下图所示
2.另外一种比较推荐的方式是自定义编译扩展(可以执行C#代码...功能强大多了), 看下面这个项目文件的最后几句 (项目文件就是 项目名.csproj)
<Import Project="..\..\Build\Tasks\Build.tasks" />
<Target Name="BeforeBuild">
<Message Text="Start Automatic Generate Configuration File, Source File: $(ProjectDir)web.config" Importance="high">
</Message>
<ConfigurationEnvironmentTask TemplateFile="$(ProjectDir)web.template.config" TargetFile="$(ProjectDir)web.config" DataSource="$(EnvironmentName).config" />
</Target>
</Project>
这几句话的意思是
1. 包含一个task文件 (该文件包含了ConfigurationEnvironmentTask 的定义,这是一个自定开发的类,其主要作用是根据环境生成web.config文件)
2.输出一行提示信息 Start Automatic..... (该信息将显示在Output Window)
3. 调用ConfigurationEnvironmentTask 并传入一些参数(TemplateFile 等都是自己定义的参数)
Build.tasks的文件内容其实很简单,主要就是说明了这个task定义在哪里 (Build.dll)
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask AssemblyFile="Build.dll" TaskName="Build.ConfigurationEnvironmentTask"/>
</Project>
接下来的重点就是如何实现这个Task了,以下是Build.dll的关键代码
namespace Build
{
#region namespace
using System.Globalization;
using System.IO;
using System.Xml;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
#endregion
#region Standard Config File Sections
/// <summary>
/// this class generate config / xml file by template
/// </summary>
public class ConfigurationEnvironmentTask : Task
{
#region Public Propreties
/// <summary>
/// Gets or Sets template file path (full path : C:\web.config)
/// </summary>
[Required]
public ITaskItem TemplateFile { get; set; }
/// <summary>
/// Gets or Sets traget file path, will automatic overwrite this file (full path : C:\web.config)
/// </summary>
[Required]
public ITaskItem TargetFile { get; set; }
/// <summary>
/// Gets or Sets configuration file, will get settings from this path (full path : C:\web.config , relative path: dev.config)
/// </summary>
[Required]
public ITaskItem DataSource { get; set; }
#endregion
/// <summary>
/// execute replace logic
/// </summary>
/// <returns>ture successful, false failure</returns>
public override bool Execute()
{
string templateContent = File.ReadAllText(this.TemplateFile.ToString());
XmlDocument xmldoc = new XmlDocument();
string fileName = null;
string fileFolder = null;
if (Path.IsPathRooted(this.DataSource.ToString()))
{
fileFolder = Path.GetDirectoryName(this.DataSource.ToString());
}
else
{
fileFolder = Path.GetDirectoryName(this.TargetFile.ToString());
}
fileName = Path.GetFileName(this.DataSource.ToString());
if (fileName == null || string.IsNullOrEmpty(fileName) || fileName.ToLower(CultureInfo.CurrentCulture).Trim() == ".config")
{
fileName = @"local.config";
}
this.Log.LogMessage("DataSource:" + this.DataSource.ToString());
this.Log.LogMessage("IsPathRooted:" + Path.IsPathRooted(this.DataSource.ToString()));
this.Log.LogMessage("fileFolder:" + fileFolder);
this.Log.LogMessage("fileName:" + fileName);
if (!File.Exists(fileFolder + @"\" + fileName))
{
throw new FileNotFoundException("Can not find File :" + fileFolder + @"\" + fileName);
}
xmldoc.Load(fileFolder + @"\" + fileName);
foreach (XmlNode node in xmldoc.SelectNodes("/Settings/*"))
{
templateContent = templateContent.Replace("$(" + node.Name + ")", node.InnerText).Trim(new char[] { ' ', '\r', '\n', '\t' });
}
File.WriteAllText(this.TargetFile.ToString(), templateContent);
return true;
}
}
#endregion Standard Config File Sections
}
代码写得很烂,这不是生产环境的code
关键就是Execute这个方法,
大意就是根据指定的config文件读取出键值对,将web.config中类似于 $(key)的内容替换掉
PS:在正式环境中,其实我们是用放在一个网站上的一个文件和环境变量实现的
当然在这个方法中你想干嘛干嘛,修改配置文件也好,发邮件也好,自动备份也好...
对了 记得让你的项目引用Build.dll (推荐dll和task文件放在同一个目录)
然后点击编译或者Ctrl+Shift+B吧
本文中的所有代码在Visual Studio 2010和 MSBuild Extenstion 4.0 编译和运行成功
此外MS Build Exenstion中的一些功能很不错,而且网上也有很多第三方的Task都挺不错的, 例如部署IIS网站,FTP等,有兴趣可以试试,也许能简化不少工作