Kyl*_*Mit 14 c# asp.net asp.net-mvc csproj asp.net-mvc-5
ASP.NET具有特定的应用程序文件夹,例如App_Code
:
包含要作为应用程序的一部分进行编译的共享类和业务对象(例如,.. cs和.vb文件)的源代码.在动态编译的Web站点项目中,ASP.NET会根据对应用程序的初始请求编译App_Code文件夹中的代码.检测到任何更改后,将重新编译此文件夹中的项目.
问题是,我正在构建一个Web应用程序,而不是一个动态编译的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";
}
}
Run Code Online (Sandbox Code Playgroud)
这可能是应用程序中的任何位置,如下所示:
string msg = AppName.Globals.Messages.CodeNotFound;
Run Code Online (Sandbox Code Playgroud)
目标是能够将任何文字存储在可以更新的可配置区域中,而无需重新编译整个应用程序.
我可以.cs
通过将其构建操作设置为编译来使用该文件,但这样做会App_Code/Globals.cs
从我的输出中删除.
问:有没有办法识别项目的某些部分,这些部分应该动态编译,同时允许项目的其余部分进行预编译?
content
- .cs文件将被复制到该bin
文件夹并在运行时编译.但是,在这种情况下,它在设计时不可用. compile
- 我可以在设计/运行时期间访问与任何其他编译类相同的对象,但在发布时它将被删除/ App_Code文件夹.我仍然可以将它放在输出目录中Copy Always
,但是已编译的类似乎优先,所以我不能在不重新部署整个应用程序的情况下推送配置更改.Kyl*_*Mit 12
我们需要克服两个不同的问题:
第一个问题是尝试获得一个既编译又不编译的类.我们需要在设计时编译它,以便代码的其他部分知道它存在并且可以使用其强类型的属性.但通常情况下,已编译的代码会从输出中删除,因此同一类的多个版本不会导致命名冲突.
在任何情况下,我们都需要最初编译该类,但有两个选项可以保留可重新编译的副本:
App_Code
默认情况下在运行时编译,但设置它的Build Action = Compile,以便它在设计时也可用.至少,这对向编译器收费是一项棘手的任务.任何使用类的代码都必须保证它在编译时存在.无论是通过App_Code还是其他方式动态编译的任何内容都将成为完全不同的程序集的一部分.因此,生成一个相同的类更像是该类的图片.底层类型可能是相同的,但ce n'est une pipe.
我们有两个选择:在程序集之间使用接口或crosswalk:
如果我们使用接口,我们可以使用初始构建编译它,并且任何动态类型都可以实现相同的接口.这样我们就可以安全地依赖编译时存在的东西,并且可以安全地将我们创建的类替换为支持属性.
如果我们跨程序集转换类型,请务必注意,任何现有的用法都依赖于最初编译的类型.因此,我们需要从动态类型中获取值,并将这些属性值应用于原始类型.
每个evk,我喜欢AppDomain.CurrentDomain.GetAssemblies()
在启动时查询任何新的程序集/类的想法.我承认使用接口可能是统一预编译/动态编译类的可行方法,但我希望有一个文件/类,只要它发生变化就可以重新读取.
Per S.Deepika,我喜欢从文件动态编译的想法,但不想将值移动到单独的项目中.
App_Code
App_Code确实解锁了构建同一类的两个版本的能力,但实际上很难在发布之后修改任何一个版本,我们将会看到..cs
位于〜/ App_Code /中的任何文件将在应用程序运行时动态编译.因此,在Visual Studio中,我们可以通过将其添加到App_Code并将Build Action设置为Compile来构建相同的类两次.
构建操作和复制输出:
当我们在本地调试时,所有.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>();
Run Code Online (Sandbox Code Playgroud)
编译和动态类型:
这非常接近解决问题#1.每次应用运行时,我们都可以访问这两种类型.我们可以在设计时使用编译版本,对文件本身的任何更改都将由IIS自动重新编译为我们可以在运行时访问的版本.
但是,一旦我们退出调试模式并尝试发布项目,问题就显而易见了.此解决方案依赖于IIS App_Code.xxxx
动态构建程序集,并依赖于.App文件位于根App_Code文件夹中.但是,当编译.cs文件时,它会自动从已发布的项目中删除,以避免我们尝试创建(并精心管理)的确切方案.如果文件被保留,它将产生两个相同的类,无论何时使用任何一个类都会产生命名冲突.
我们可以尝试通过将文件编译到项目的程序集中并将文件复制到输出目录来强制其手.但是App_Code在〜/ bin/App_Code /中没有任何神奇的功能.它只能在根级别工作〜/ App_Code /
App_Code编译源:
每次发布时,我们都可以从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";
}
Run Code Online (Sandbox Code Playgroud)
同样,我们将要转到文件属性(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; }
}
Run Code Online (Sandbox Code Playgroud)
我们可以像这样使用它:
var x = Global.Apple.BodyColor;
Run Code Online (Sandbox Code Playgroud)
我们在静态构造函数中尝试做的是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;
Run Code Online (Sandbox Code Playgroud)
fileName
- 文件路径可能特定于您要部署它的位置,但请注意,在静态方法内部,您需要使用HostingEnvironment.MapPath
而不是Server.MapPath
BuildFileIntoAssembly
- 在从文件加载程序集方面,我已经调整了文档中的代码CSharpCodeProvider
和关于如何从.cs文件加载类的问题.此外,我只是让编译器访问当前在App域中的每个程序集,而不是与原始编译相同.可能有一种方法可以用更少的开销来做到这一点,但这是一次性成本,所以谁在乎.
CopyProperties
- 要将新属性映射到旧对象,我已经在这个问题中调整了如何将属性值从一个对象自动应用到同一类型的另一个对象的方法?它将使用反射来分解两个对象并迭代每个属性.
以下是上述实用程序方法的完整源代码
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;
}
}
Run Code Online (Sandbox Code Playgroud)
好的,所以还有其他更传统的方法来实现相同的目标.理想情况下,我们会拍摄会议>配置.但这提供了绝对最简单,最灵活,强类型的方式来存储我见过的配置值.
通常,配置值是通过XML在同样奇怪的过程中读取的,该过程依赖于魔术字符串和弱类型.我们必须调用MapPath
才能进入有价值存储,然后执行从XML到C#的对象关系映射.相反,在这里,我们从get go开始具有最终类型,并且我们可以自动化恰好恰好针对不同程序集编译的相同类之间的所有ORM工作.
在任何一种情况下,该过程的梦想输出都是能够直接编写和使用C#.在这种情况下,如果我想添加一个额外的,完全可配置的属性,就像向类中添加属性一样简单.完成!
如果该值发生变化,它将立即可用并自动重新编译,而无需发布应用程序的新版本.
动态更改类演示:
归档时间: |
|
查看次数: |
2332 次 |
最近记录: |