返回首页


{S0}简介
我看,代码生成是有时尚... ...但是,大多数开发人员忽略的东西。这就是所谓的MSBuild。什么的MSBuild与代码生成?事实上,你们中的大多数已经知道,当你添加x:Name属性XAML对象,你可以从代码隐藏访问它,它像魔术。显然,也没有神奇。如果你能做到这一点自己很容易吗?我很高兴知道你会想象,possibility.nbsp;
我的目标是向您展示如何用代码生成很酷的MSBuild是。为此,我将创建一个强类型的appSettings。

:
{S2}有没有神奇
我建议booknbsp; {A},如果你是在MSBuild感兴趣。这是一个在一个地方的技巧,窍门和引用的凉爽编译。这给了我很多的ideas.nbsp;
X:当您添加一个XAML元素的名称,您可以访问此Objet公司在后面的代码。而事实上,构建过程在您的项目(/ OBJ)中间目录生成一个*. g.cs,这些文件的动态链接您的intellisense。
事实上,你可以做的,自己在三线与MSBuild。只需创建一个。CS(MyMagicalClass.cs)文件,并把它在您的项目相同目录。然后,添加在您的。csproj文件结束(结束,而且是很重要的)。不包括的项目中的代码文件。我只希望你把它复制到项目文件的同一目录。

<target name="BeforeBuild">

    <ItemGroup>

        <Compile Include="MyMagicalClass.cs" />

    </ItemGroup>

</target>

现在,编译第一次,你就会有这个文件的IntelliSense,即使它不是在您的项目在解决方案资源管理器显示。
很快,我将解释如何这是可能的。当你打"; Buildquot在Visual Studio,Visual Studio会调用名为quot目标; Buildquot;您的项目文件。但构建目标有一定的构建目标执行之前,将执行的依赖。让我们来看看什么的依赖在C:\ WINDOWS \ Microsoft.NET \框架\ V3.5 \ Microsoft.Common.targets。{C}
它似乎BeforeBuild的基础上。
<Target Name="BeforeBuild"/>

,这个目标是空的。
在现实中,当你添加一个目标BeforeBuild呼吁在项目结束时,你告诉MSBuild来覆盖任何以前定义的目标称为BeforeBuild(你甚至可以覆盖一个quot; Buildquot;是什么)。
我们在csproj之前写的代码,只是告诉MSBuild来quot;添加的compilationquo​​t文件;每当你。CS项目文件中添加一个新的编译项目包括在您的。csproj,这就是为什么他们在Solution Explorer中所示。
然而,编译项目添加通过调用目标的动态解决方案资源管理器中不显示... ...然而,他们被编译和解释的IntelliSense。
所以现在,我将显示你一件很酷的事情:在app.config文件取决于编译生成一个类,在编译和集成类。让我们去...现在你的想法,让我们尝试创建的AppSettings类生成... ...
我们的目标是可以参考,一个强类型的方式,在App.config文件中的appSettings项目。
我不会进入我的课的实施讨厌的细节,有趣的一点仍然与MSBuild集成。在这段代码中的有趣的事情是WriteClass方法巫传递给构造配置文件的CodeDOM生成代码。
public enum Language

{

    CSharp,

    VB

}

public class Generator

{

    readonly string _ConfigFile;

    public Generator(string configFile)

    {

        if(configFile == null)

            throw new ArgumentNullException("configFile");

        if(!File.Exists(configFile))

            throw new FileNotFoundException(configFile, configFile);

        _ConfigFile = configFile;

        NameSpace = "";

        ClassName = "Settings";

        Language = Language.CSharp;

    }



    public String NameSpace

    {

        get;

        set;

    }



    public String ClassName

    {

        get;

        set;

    }



    public Language Language

    {

        get;

        set;

    }

    CodeDomProvider CreateProvider()

    {

        switch(Language)

        {

            case Language.CSharp:

                return new Microsoft.CSharp.CSharpCodeProvider();

            case Language.VB:

                return new Microsoft.VisualBasic.VBCodeProvider();

            default:

                throw new NotSupportedException();

        }

    }



    public void WriteClass(TextWriter writer)

    {

        var config = 

            ConfigurationManager.OpenMappedExeConfiguration(

            new ExeConfigurationFileMap()

        {

            ExeConfigFilename = _ConfigFile

        }, ConfigurationUserLevel.None);



        CodeNamespace ns = new CodeNamespace(NameSpace);

        CodeTypeDeclaration type = new CodeTypeDeclaration(ClassName);

        ns.Types.Add(type);



        foreach(KeyValueConfigurationElement setting in config.AppSettings.Settings)

        {

            CodeMemberProperty property = new CodeMemberProperty();

            property.HasSet = false;

            property.HasGet = true;

            property.Attributes = MemberAttributes.Static | MemberAttributes.Public;

            property.Type = new CodeTypeReference(typeof(String));

            property.Name = setting.Key;



            var getSettingCall =

                new CodeIndexerExpression(

                    new CodePropertyReferenceExpression(

                        new CodeTypeReferenceExpression(typeof(ConfigurationManager)),

                        "AppSettings"),

                    new CodePrimitiveExpression(setting.Key));

            property.GetStatements.Add(new CodeMethodReturnStatement(getSettingCall));

            type.Members.Add(property);

        }



        var provider = CreateProvider();

        provider.GenerateCodeFromNamespace(ns, writer, new CodeGeneratorOptions());

    }

}

例如,此配置文件:
<configuration>

    <appSettings>

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

        <add key="val2" value="value2"/>

        <add key="val3" value="value2"/>

        <add key="hello" value="value2"/>

        <add key="hello3" value="value2"/>

        <add key="hello6" value="value2"/>

    </appSettings>

</configuration>

会生成这个类:
public class Settings {

    

    public static string val1 {

        get {

            return System.Configuration.ConfigurationManager.AppSettings["val1"];

        }

    }

    

    public static string val2 {

        get {

            return System.Configuration.ConfigurationManager.AppSettings["val2"];

        }

    }

    

    public static string val3 {

        get {

            return System.Configuration.ConfigurationManager.AppSettings["val3"];

        }

    }

    

    public static string hello {

        get {

            return System.Configuration.ConfigurationManager.AppSettings["hello"];

        }

    }

    

    public static string hello3 {

        get {

            return System.Configuration.ConfigurationManager.AppSettings["hello3"];

        }

    }

    

    public static string hello6 {

        get {

            return System.Configuration.ConfigurationManager.AppSettings["hello6"];

        }

    }

}
... ...集成在构建过程中中
首先,我只需要创建一个自定义MSBuild中任务和实施Execute方法。每个记录的错误会出现在Visual Studio错误窗口。 (建立在MSBuild任务,你需要参考Microsoft.Build .*集会。)
正如你可以看到... ...这是一个在我的发电机的包装:
public class ClassGeneratorTask : Task

{

    public ClassGeneratorTask()

    {

        Language = "CS";

    }

    [Required]

    public ITaskItem FilePath

    {

        get;

        set;

    }





    public String Language

    {

        get;

        set;

    }



    [Required]

    public ITaskItem ConfigFile

    {

        get;

        set;

    }



    Language? GetLanguage()

    {

        if("CS".Equals(Language, StringComparison.InvariantCultureIgnoreCase))

            return SettingsClassGenerator.Language.CSharp;

        if("VB".Equals(Language, StringComparison.InvariantCultureIgnoreCase))

            return SettingsClassGenerator.Language.VB;

        return null;

    }



    public override bool Execute()

    {

        var language = GetLanguage();

        if(language == null)

        {

            Error(Language + " is not a valid language, specify CS or VB");

            return false;

        }

        try

        {

            string text = File.ReadAllText(ConfigFile.ItemSpec);

        }

        catch(IOException ex)

        {

            Error("Error when trying to open config file : " + " " + 

                  ConfigFile.ItemSpec + " " + ex.Message);

            return false;

        }

        try

        {

            using(var generatedWriter = 

                  new StreamWriter(File.Open(FilePath.ItemSpec, FileMode.Create)))

            {

                Generator gen = new Generator(ConfigFile.ItemSpec);

                gen.Language = language.Value;

                gen.WriteClass(generatedWriter);

                generatedWriter.Flush();

            }

        }

        catch(IOException ex)

        {

            Error("Error when accessing to " + FilePath.ItemSpec + 

                  " " + ex.Message);

            return false;

        }

        return true;

    }



    public void Error(string message)

    {

        Log.LogError(message);

    }

}

configfile中,文件路径,和语言时,我们通过调用任务的属性。 filepath是生成的文件的路径。你可以看到,configfile中和FILEPATH ITaskItem,和它的工作,如果它只是一个字符串。然而,ITaskItem更多的信息,如在MSBuild元数据。在现实中,我并不需要它,我只可以使用字符串代替,但我觉得这是一个很好的做法,使用ITaskItem。毕竟,它的接近我的类域(这是MSBuild的)。
现在,我们想要一个简单的方法在适当的时候执行这项任务的app.config文件。对于这一点,我只需要创建一个目标文件只是一个典型的MSBuild项目文件后,我们将进口csproj文件。
注意:SettingsGeneratorTask.dll是包含前面所示的MSBuild任务的大会。大会必须在相同的目录。目标文件。
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">

    <UsingTask AssemblyFile="SettingsGeneratorTask.dll" TaskName="ClassGeneratorTask"/>



    <ItemGroup>

        <SettingsGenerated Include="$(IntermediateOutputPath)AppSettings.g.cs">

        </SettingsGenerated>

    </ItemGroup>

    <Target Name="GenerateSetting" 

           Inputs="@(AppConfigWithTargetPath)" 

           Outputs="@(SettingsGenerated)">

        <ClassGeneratorTask Language="$(Language)" ConfigFile="@(AppConfigWithTargetPath)" 

           FilePath="@(SettingsGenerated)"></ClassGeneratorTask>

        <ItemGroup>

            <Reference Include="System.Configuration"></Reference>

            <Reference Include="System"></Reference>

            <Compile Include="@(SettingsGenerated)"></Compile>

            <FileWrites Include="@(SettingsGenerated)"/>

        </ItemGroup>

    </Target>

    <PropertyGroup>

        <ResolveReferencesDependsOn>

            GenerateSetting;

            $(ResolveReferencesDependsOn)

        </ResolveReferencesDependsOn>

    </PropertyGroup>

</Project>

正如你可以在这里看到:
<PropertyGroup>

    <ResolveReferencesDependsOn>

        GenerateSetting;

        $(ResolveReferencesDependsOn)

    </ResolveReferencesDependsOn>

</PropertyGroup>

我作为一个依赖ResolveReferences插入我的自定义任务。因为在调查Microsoft.Common.targets后,我已经看到了这一步,是唯一一个,我做到了这一点:在编译之前。之后,我在我的任务来定位app.config文件的项目AppConfigWithTargetPath决议。
现在这里:
<ItemGroup>

    <Reference Include="System.Configuration"></Reference>

    <Reference Include="System"></Reference>

    <Compile Include="@(SettingsGenerated)"></Compile>

    <FileWrites Include="@(SettingsGenerated)"/>

</ItemGroup>

你可以看到,我的任务,我想补充系统和System.Configuration参考动态,因为生成的代码依赖于它。我不想打扰用户,如果他忘记这些引用​​添加到项目。
所以,我想补充的两个引用,生成的类编译项目和FileWrites项目。 FileWrites项目是用来到MSBuild的说,应删除该文件,当你清理项目。
另一个技巧:
<Target Name="GenerateSetting" Inputs="@(AppConfigWithTargetPath)" 

        Outputs="@(SettingsGenerated)">

这意味着目标取决于了AppConfig文件,并创建@(SettingsGenerated)项目。事实上,在执行任务时,MSBuild会比较@(SettingsGenerated)和配置文件的时间戳。如果@(SettingsGenerated)修改后的配置文件,配置文件并没有改变,可以跳过自上次任务的平均,这样,在编译期间没有无用的开销。
那么,什么是左?只要包括在您csproj的目标文件后,Microsoft.Common.targets进口。
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

<Import Project="..\SettingsGeneratorTask\bin\Debug\SettingsClassGenerator.targets" />

创建与您最喜爱的设置你的app.config,并编译...你会得到你的配置文件的appSettings的IntelliSense!结论
这个项目是向您展示如何MSBuild和代码生成功能强大的... ...正如你可以看到,我所做的是非常简单的代码,但非常有用的。现在,你知道这一点,你会想象呢?

回答

评论会员:韦斯琼斯 时间:2012/01/25
不Visual Studio的一种已经为我们做这?
在IDE中,去项目 - >属性 - >设置。你添加有什么,你就可以访问,这是一个获取代码生成的类的W / Properties.Settings.Default.MySetting
评论会员:。萨科Dorier 时间:2012/01/25
Visual Studio中使用相同的技术做设置
不过的AppSettings和。设置是不是同样的事情。
的AppSettings创建,使管理员可以轻松地修改您的应用程序的配置,只是通过修改app.config文件,。
设置不是手工设置编辑创建,以便开发人员可以很容易地保存在他的用户首选项的代码。可以是全球性的,或由用户设置
评论会员:。韦斯琼斯 时间:2012/01/25
啊,我看。 ,分享你们的辛勤工作非常出色感谢
评论会员:!OmarGamil 时间:2012/01/25
好工作,有同样的问题太
感谢分享{S3}
评论会员:尼克巴特勒 时间:2012/01/25
你们是真正推动了一些令人印象深刻的东西

感谢分享!

尼克

----------------------------------{ BR}是优秀的相互
评论会员:萨科Dorier 时间:2012/01/25
感谢这是令人鼓舞的!如果你喜欢这篇文章,你应该明白适用本条的原则{S3}
评论会员:萨沙理发 时间:2012/01/25
。尼古拉斯,非常漂亮的工作

但是你要知道你说

"configfile中,文件路径,和语言时,我们通过调用任务的属性。filepath是生成的文件的路径。您可以看到configfile中和FILEPATH ITaskItem,和它的工作,如果它只是一个字符串,但,ITaskItem有更多的信息,喜欢的MSBuild的元数据在现实中,我做的不是需要它,和我只是可以使用字符串代替,但觉得一个好的做法,以使用ITaskItem。毕竟,它的接近我的领域类(这是MSBuild的)。"

它不是很清楚,我在语言传递。您有以下


<ClassGeneratorTask ConfigFile="@(AppConfigWithTargetPath)" 

FilePath="@(SettingsGenerated)"></ClassGeneratorTask>

那么,语言提供有

萨沙理发
微软的Visual C#MVP 2008/2009Codeproject MVP 2008/2009Your最好的朋友就是你。
我是我最好的朋友。我们有着共同的看法,并未落认为

我的博客:
评论会员:萨科Dorier 时间:2012/01/25
。有效,不是很清晰,我会解决这个问题
语言有一个默认值硬编码为"C#"。,

在此,这应该是:
<ClassGeneratorTask Language="$(Language)" ConfigFile="@(AppConfigWithTargetPath)" 

FilePath="@(SettingsGenerated)"></ClassGeneratorTask>

指定的"语言"属性是VB或CSHARP内每csproj或vbproj
评论会员:。萨沙理发 时间:2012/01/25
哦,OK,这使得
意义。
嘿人在哪里你拿起你的WCF的知识,你做了一些非常先进的WCF的东西,在您的其他物品的人

你做了什么学习?

萨沙理发
微软的Visual C#MVP 2008/2009Codeproject MVP 2008/2009Your最好的朋友就是你。
我是我最好的朋友。我们有着共同的看法,并未落认为

我的博客:
评论会员:řICKCODE 时间:2012/01/25
我为什么这样做?我不明白任何的实际需要... ...我失去了一些点
评论会员:?萨科Dorier 时间:2012/01/25
想象一下,您删除您设置app.config文件,或重新命名它。你必须手动更新所有在您的代码引用appsetting的关键。而且,如果你忘了一个,你有没有在编译时发现了一个bug。 我干了什么,你不能拼错AppSetting关键在你的代码,如果关键的变化,编译器将捕获你的错误。 这篇文章的主要观点是,你可以很容易地编译生成的类,在生成过程透明,并获得它的IntelliSense。
一个很好的例子是,如果你有一堆xsd文件,并且您希望类编译过程中自动生成每次修​​改一个XSD文件。
这样,如果你修改XSD,打破的东西在你的代码,编译器将能够赶上它,而不必担心任何事情。
的代码也更容易代码,感谢的IntelliSense。微软的XML队这样做,我觉得他们称之为LINQ到XSD。

但现在你能想象从DSL生成这样的类
评论会员:。twebb72 时间:2012/01/25
我觉得这篇文章很有趣,很有创意
话虽这么说,我同意该线程的顶部,它并没有成为大多数应用程序有用的目的。
如果您需要更改根据环境或数据库连接的设置;,你有问题,因为你需要重新发出另一个构建。最小化的生成数量,通常是发布管理的最佳战略。 TFS使得它很容易重新建立一个特定的版本,但它往往只是堵塞的Drop文件夹,使版本号的意义不大。
最终,如果你需要你的释放代码的可移植性(这是有应用程序的99%),你需要在运行时设置待定离开,
尤伯杯尊重所有文章!我通过阅读他们所有的工作我的方式!
感谢。 {七}

"我说我们起飞和NUKE从轨道,以确保它的唯一方法。"
评论会员:NatzaMitzi 时间:2012/01/25
创建一个设置文件,将它移动到属性文件夹
一旦你这样做,所有的设置都充分打字,控制和可以改变使用的设置设计师。
代码类是自动创建的,并可以通过属性的名称空间访问。

Natza Mitzi
评论会员:萨科Dorier 时间:2012/01/25
的设置是保存在C:\用户\ [用户​​名] \应用程序数据\本地和C:\用户\ [用户​​名] \应用程序数据\漫游用户机器上,而不是在配置文件。
设置用于大多保存用户的偏好,而在配置文件中的appSettings使用轻松配置您的应用程序所使用的一些键/值对。
但也许我应该做的,而不是appSettings节的ConnectionString节的文章。 (你只需要更改2行代码中的发电机,做到这一点)
评论会员:丹尼尔沃恩 时间:2012/01/25
我真的很喜欢你做的MSBuild萨科。非常酷。
我不知道的文章,如果在引进更多的解释可能会从中受益;画一个更清楚地了解你正在努力实现的代码,什么。我认为,额外的清晰度可以让你的代码更加闪耀,它应该是因为它的伟大的工作。我有一个5。

欢呼声,
丹尼尔

丹尼尔沃恩
博客:
公司名称: