在预编译项目/库的其余部分时动态编译App_Code中的类

时间:2017-12-21 15:49:13

标签: c# asp.net asp.net-mvc asp.net-mvc-5 csproj

ASP.NET具有类似App_Code的特定应用程序文件夹:

  

包含要作为应用程序的一部分进行编译的共享类和业务对象(例如,.. cs和.vb文件)的源代码。在动态编译的Web站点项目中,ASP.NET会根据对应用程序的初始请求编译App_Code文件夹中的代码。检测到任何更改后,将重新编译此文件夹中的项目。

问题是,我正在构建一个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

2 个答案:

答案 0 :(得分:4)

您可以将配置部件移动到单独的项目,并创建公共接口,如(IApplicationConfiguration.ReadConfiguration)来访问它。

您可以在运行时动态编译代码,如下所示,您可以使用反射访问配置详细信息。

public static Assembly CompileAssembly(string[] sourceFiles, string outputAssemblyPath)
{
    var codeProvider = new CSharpCodeProvider();

    var compilerParameters = new CompilerParameters
    {
        GenerateExecutable = false,
        GenerateInMemory = false,
        IncludeDebugInformation = true,
        OutputAssembly = outputAssemblyPath
    };

    // Add CSharpSimpleScripting.exe as a reference to Scripts.dll to expose interfaces
    compilerParameters.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);

    var result = codeProvider.CompileAssemblyFromFile(compilerParameters, sourceFiles); // Compile

    return result.CompiledAssembly;
}

答案 1 :(得分:3)

让我们看看App_Code中文件的动态编译是如何工作的。当第一个请求到达你的应用程序时,asp.net会将该文件夹中的代码文件编译成程序集(如果之前没有编译),然后将该程序集加载到asp.net应用程序的当前应用程序域中。这就是为什么你在手表中看到你的消息的原因 - 程序集已经编译并在当前的应用程序域中可用。因为它是动态编译的,当然你在尝试显式引用它时会有编译时错误 - 这段代码尚未编译,当它被编译时 - 它可能有完全不同的结构和你引用的消息可能就不存在一点都不因此,您无法从动态生成的程序集中明确引用代码。

那么你有什么选择?例如,您可以拥有消息的界面:

// this interface is located in your main application code,
// not in App_Code folder
public interface IMessages {
    string CodeNotFound { get; }
}

然后,在您的App_Code文件中 - 实现该接口:

// this is in App_Code folder, 
// you can reference code from main application here, 
// such as IMessages interface
public class Messages : IMessages {
    public string CodeNotFound
    {
        get { return "The entered code was not found"; }
    }
}

然后在主应用程序中 - 通过搜索当前应用程序域以提供代理,使用实现IMessage接口的类型(仅一次,然后缓存它)并代理对该类型的所有调用来提供代理:

public static class Messages {
    // Lazy - search of app domain will be performed only on first call
    private static readonly Lazy<IMessages> _messages = new Lazy<IMessages>(FindMessagesType, true);

    private static IMessages FindMessagesType() {
        // search all types in current app domain
        foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) {
            foreach (var type in asm.GetTypes()) {
                if (type.GetInterfaces().Any(c => c == typeof(IMessages))) {
                    return (IMessages) Activator.CreateInstance(type);
                }
            }
        }
        throw new Exception("No implementations of IMessages interface were found");
    }

    // proxy to found instance
    public static string CodeNotFound => _messages.Value.CodeNotFound;
}

这将实现您的目标 - 现在当您更改App_Code Messages类中的代码时,在下一个请求中,asp.net将拆除当前的应用程序域(首先等待所有待处理的请求完成),然后创建新的应用程序域,重新编译您的Messages并加载到新的应用程序域(请注意,当您在App_Code中更改某些内容时,这种重新创建的应用程序域始终会发生,而不仅仅是在这种特定情况下)。因此,如果没有您明确地重新编译任何内容,下一个请求就会看到您的消息的新值。

请注意,显然无法在不重新编译主应用程序的情况下添加或删除消息(或更改其名称),因为这样做需要更改属于主应用程序代码的IMessages接口。如果您尝试 - asp.net将在下一个(以及所有后续)请求中抛出编译失败错误。

我个人避免做这些事情,但如果你对此不满意 - 为什么不呢。