gpt4 book ai didi

c# - 在预编译项目/库的其余部分时动态地在App_Code中编译一个类

转载 作者:行者123 更新时间:2023-12-03 21:50:36 24 4
gpt4 key购买 nike

ASP.NET具有特殊的应用程序文件夹,例如 App_Code ,该文件夹:

Contains source code for shared classes and business objects (for example, ..cs, and .vb files) that you want to compile as part of your application. In a dynamically compiled Web site project, ASP.NET compiles the code in the App_Code folder on the initial request to your application. Items in this folder are then recompiled when any changes are detected.



问题是,我正在构建一个Web应用程序,而不是一个动态编译的网站。但是我很想能够直接在C#中存储配置值,而不是通过XML服务,并且必须在 Application_Start期间读取并存储在 HttpContext.Current.Application

所以我在 /App_Code/Globals.cs中有以下代码:

namespace AppName.Globals
{
public static class Messages
{
public const string CodeNotFound = "The entered code was not found";
}
}

可能在应用程序中的任何位置,如下所示:

string msg = AppName.Globals.Messages.CodeNotFound;

目的是能够将任何文字存储在可配置区域中,而无需重新编译整个应用程序就可以对其进行更新。

我可以使用 setting its build action to compile使用 .cs文件,但这样做会从输出中剥离 App_Code/Globals.cs

:是否有一种方法可以识别 项目的某些部分,这些部分应该 dynamically compile,同时允许对项目的其余部分进行预编译?

  • 如果我将构建操作设置为content,则.cs文件将复制到bin文件夹并在运行时进行编译。但是,在这种情况下,它在设计时不可用。
  • 如果将生成操作设置为compile,则可以在设计/运行时访问与任何其他编译类相同的对象,但是发布时,它会从/ App_Code文件夹中删除。我仍然可以通过Copy Always将其放置在输出目录中,但是已经编译的类似乎具有较高的优先级,因此如果不重新部署整个应用程序就无法推送配置更改。

  • App_Code Content vs Compile

    最佳答案

    问题概述

    我们需要在这里克服两个不同的问题:

  • 首先是拥有一个文件,该文件可以在构建时编译,也可以在运行时重新编译。
  • 第二步是解决通过解决第一个问题而创建的该类的两个不同版本,以便我们可以实际使用它们。

  • 问题1-Schrödinger的编译

    第一个问题是试图获取一个既已编译又未编译的类。我们需要在设计时对其进行编译,以使其他代码部分都知道它的存在,并且可以将其属性与强类型一起使用。但是通常,编译后的代码会从输出中剥离,因此同一类不会有多个版本引起命名冲突。

    无论如何,我们都需要首先编译该类,但是有两种选择来保留可重新编译的副本:
  • 将文件添加到App_Code,默认情况下在运行时编译该文件,但将其设置为Build Action = Compile,以便在设计时也可以使用。
  • 添加一个常规类文件,默认情况下会在设计时编译该类文件,但将其设置为复制到输出目录=复制Always ,因此我们也有可能在运行时对其进行评估。

  • 问题2-自强DLL hell

    至少,这是向编译器收费的棘手任务。使用类的任何代码都必须保证它在编译时存在。任何通过App_Code或其他方式动态编译的内容,都将成为完全不同的程序集的一部分。因此,产生相同的类将被视为该类的图片。基础类型可能相同,但为 ce n'est une pipe

    我们有两个选择:在装配体之间使用接口(interface)或人行横道:
  • 如果我们使用接口(interface),则可以使用初始构建对其进行编译,并且任何动态类型都可以实现相同的接口(interface)。这样,我们可以安全地依赖于编译时存在的内容,并且可以安全地将我们创建的类替换为支持属性。
  • 如果我们使用cast types across assemblies,则需要注意的是,任何现有用法都依赖于最初编译的类型。因此,我们需要从动态类型和apply those property values to the original type中获取值。

  • Apple Class

    现有答案

    对于 evk,我喜欢在启动时查询 AppDomain.CurrentDomain.GetAssemblies()来检查是否有任何新的程序集/类的想法。我承认使用接口(interface)可能是统一预编译/动态编译的类的一种明智方法,但是理想情况下,我希望有一个文件/类,如果它发生更改,则可以简单地重新读取。

    对于 S.Deepika,我喜欢从文件动态编译的想法,但是不想将值移动到单独的项目中。

    排除 App_Code
    App_Code 确实解锁了构建同一类的两个版本的功能,但是实际上很难在发布后修改其中一个,我们将看到。当应用程序运行时,位于〜/ App_Code /中的任何 .cs文件都会被动态编译。因此,在Visual Studio中,我们可以通过将同一类添加到App_Code并将 Build Action 设置为 编译来两次构建同一类。

    构建操作并复制输出:

    Build Action and Copy Output

    当我们在本地调试时,所有.cs文件都将被构建到项目程序集中,并且〜/ App_Code中的物理文件也将被构建。

    我们可以这样识别这两种类型:
    // have to return as object (not T), because we have two different classes
    public List<(Assembly asm, object instance, bool isDynamic)> FindLoadedTypes<T>()
    {
    var matches = from asm in AppDomain.CurrentDomain.GetAssemblies()
    from type in asm.GetTypes()
    where type.FullName == typeof(T).FullName
    select (asm,
    instance: Activator.CreateInstance(type),
    isDynamic: asm.GetCustomAttribute<GeneratedCodeAttribute>() != null);
    return matches.ToList();
    }

    var loadedTypes = FindLoadedTypes<Apple>();

    编译和动态类型:

    Compiled and Dynamic Types

    这真的很接近解决问题1。每当应用运行时,我们都可以访问这两种类型。我们可以在设计时使用编译后的版本,并且文件本身的任何更改都将由IIS自动重新编译为我们可以在运行时访问的版本。

    但是,一旦退出 Debug模式并尝试发布项目,问题就显而易见了。此解决方案依赖于IIS动态构建 App_Code.xxxx程序集,并且依赖于位于App_Code根文件夹中的.cs文件。但是,在编译.cs文件时,该文件会自动从发布的项目中删除,从而避免了我们尝试创建(和精细管理)的确切情况。如果保留该文件,它将产生两个相同的类,每当使用其中一个时,它们就会产生命名冲突。

    我们可以尝试通过将文件编译到项目的程序集中以及将文件复制到输出目录中来发挥作用。但是App_Code在〜/ bin / App_Code /里面没有任何魔力。它只能在根级别〜/ App_Code /下工作

    App_Code编译源:

    App_Code Compilation Source

    每次发布时,我们都可以从bin中手动剪切和粘贴生成的App_Code文件夹,并将其放回根目录级别,但这充其量是不稳定的。也许我们可以将其自动化到构建事件中,但是我们将尝试其他方法...



    编译+(复制到输出并手动编译文件)

    让我们避免使用App_Code文件夹,因为它会增加一些意想不到的后果。

    只需创建一个名为 Config的新文件夹并添加一个类,该类将存储我们希望能够动态修改的值:

    ~/Config/AppleValues.cs :

    public class Apple
    {
    public string StemColor { get; set; } = "Brown";
    public string LeafColor { get; set; } = "Green";
    public string BodyColor { get; set; } = "Red";
    }

    再次,我们要转到文件属性(F4)并设置为编译并复制到输出。这将为我们提供该文件的第二个版本,供以后使用。

    我们将通过在可从任何地方公开值的静态类中使用该类来使用它。这有助于分离关注点,尤其是在动态编译和静态访问之间。

    ~/Config/GlobalConfig.cs :

    public static class Global
    {
    // static constructor
    static Global()
    {
    // sub out static property value
    // TODO magic happens here - read in file, compile, and assign new values
    Apple = new Apple();
    }

    public static Apple Apple { get; set; }
    }

    我们可以这样使用它:

    var x = Global.Apple.BodyColor;

    我们将尝试在静态构造函数中执行的操作是使用带有动态类值的 Apple种子。每次重新启动应用程序时,都会调用此方法,并且对bin文件夹的任何更改将自动触发回收应用程序池。

    简而言之,这是我们要在构造函数内部完成的工作:

    string fileName = HostingEnvironment.MapPath("~/bin/Config/AppleValues.cs");
    var dynamicAsm = Utilities.BuildFileIntoAssembly(fileName);
    var dynamicApple = Utilities.GetTypeFromAssembly(dynamicAsm, typeof(Apple).FullName);
    var precompApple = new Apple();
    var updatedApple = Utilities.CopyProperties(dynamicApple, precompApple);

    // set static property
    Apple = updatedApple;
    fileName-文件路径可能特定于您要部署此文件的位置,但是请注意,在静态方法内部,您需要使用 HostingEnvironment.MapPath instead of Server.MapPath
    BuildFileIntoAssembly-就从文件加载程序集而言,我已经修改了 CSharpCodeProvider 上文档的代码以及 How to load a class from a .cs file上这个问题的代码。另外,我没有给出战斗依赖,而是给出了 compiler access to every assembly that was currently in the App Domain,它与原始编译中的结果相同。可能有一种方法可以减少开销,但这是一次性成本,所以谁在乎。
    CopyProperties-要将新属性映射到旧对象上,我对该问题中的方法进行了调整,以了解如何对 Apply properties values from one object to another of the same type automatically?进行操作,该方法将使用反射分解两个对象并遍历每个属性。

    实用工具

    这是上面的Utility方法的完整源代码

    public static class Utilities
    {

    /// <summary>
    /// Build File Into Assembly
    /// </summary>
    /// <param name="sourceName"></param>
    /// <returns>https://msdn.microsoft.com/en-us/library/microsoft.csharp.csharpcodeprovider.aspx</returns>
    public static Assembly BuildFileIntoAssembly(String fileName)
    {
    if (!File.Exists(fileName))
    throw new FileNotFoundException($"File '{fileName}' does not exist");

    // Select the code provider based on the input file extension
    FileInfo sourceFile = new FileInfo(fileName);
    string providerName = sourceFile.Extension.ToUpper() == ".CS" ? "CSharp" :
    sourceFile.Extension.ToUpper() == ".VB" ? "VisualBasic" : "";

    if (providerName == "")
    throw new ArgumentException("Source file must have a .cs or .vb extension");

    CodeDomProvider provider = CodeDomProvider.CreateProvider(providerName);

    CompilerParameters cp = new CompilerParameters();

    // just add every currently loaded assembly:
    // https://stackoverflow.com/a/1020547/1366033
    var assemblies = from asm in AppDomain.CurrentDomain.GetAssemblies()
    where !asm.IsDynamic
    select asm.Location;
    cp.ReferencedAssemblies.AddRange(assemblies.ToArray());

    cp.GenerateExecutable = false; // Generate a class library
    cp.GenerateInMemory = true; // Don't Save the assembly as a physical file.
    cp.TreatWarningsAsErrors = false; // Set whether to treat all warnings as errors.

    // Invoke compilation of the source file.
    CompilerResults cr = provider.CompileAssemblyFromFile(cp, fileName);

    if (cr.Errors.Count > 0)
    throw new Exception("Errors compiling {0}. " +
    string.Join(";", cr.Errors.Cast<CompilerError>().Select(x => x.ToString())));

    return cr.CompiledAssembly;
    }

    // have to use FullName not full equality because different classes that look the same
    public static object GetTypeFromAssembly(Assembly asm, String typeName)
    {
    var inst = from type in asm.GetTypes()
    where type.FullName == typeName
    select Activator.CreateInstance(type);
    return inst.First();
    }


    /// <summary>
    /// Extension for 'Object' that copies the properties to a destination object.
    /// </summary>
    /// <param name="source">The source</param>
    /// <param name="target">The target</param>
    /// <remarks>
    /// https://stackoverflow.com/q/930433/1366033
    /// </remarks>
    public static T2 CopyProperties<T1, T2>(T1 source, T2 target)
    {
    // If any this null throw an exception
    if (source == null || target == null)
    throw new ArgumentNullException("Source or/and Destination Objects are null");

    // Getting the Types of the objects
    Type typeTar = target.GetType();
    Type typeSrc = source.GetType();

    // Collect all the valid properties to map
    var results = from srcProp in typeSrc.GetProperties()
    let targetProperty = typeTar.GetProperty(srcProp.Name)
    where srcProp.CanRead
    && targetProperty != null
    && (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)
    && (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0
    && targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType)
    select (sourceProperty: srcProp, targetProperty: targetProperty);

    //map the properties
    foreach (var props in results)
    {
    props.targetProperty.SetValue(target, props.sourceProperty.GetValue(source, null), null);
    }

    return target;
    }

    }

    但是为什么选择Tho?

    好的,因此还有其他更传统的方法可以实现相同的目标。理想情况下,我们会拍摄“约定”>“配置”。但这提供了我所见过的绝对最简单,最灵活,强类型的方式来存储配置值。

    通常,配置值是通过XML读取的,该过程同样依赖魔术字符串和弱类型键入。我们必须调用 MapPath来获取值的存储,然后执行从XML到C#的对象关系映射。取而代之的是,我们从一开始就拥有最终类型,并且我们可以使恰好针对不同程序集进行编译的相同类之间的所有ORM工作自动化。

    无论哪种情况,该过程的理想输出都是能够直接编写和使用C#。在这种情况下,如果我想添加一个额外的,完全可配置的属性,就像在类中添加一个属性一样简单。做完了!

    如果该值发生更改,它将立即可用并自动重新编译,而无需发布新版本的应用程序。

    动态更改类演示:

    Dynamically Changing Class Demo

    这是该项目的完整,有效的源代码:

    编译配置- Github Source Code | Download Link

    关于c# - 在预编译项目/库的其余部分时动态地在App_Code中编译一个类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47928386/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com