ASP.NETには、 App_Code
のような特定のアプリケーションフォルダーがあります。
アプリケーションの一部としてコンパイルする共有クラスおよびビジネスオブジェクト(..csファイルや.vbファイルなど)のソースコードが含まれています。動的にコンパイルされたWebサイトプロジェクトでは、ASP.NETは、アプリケーションへの最初の要求時にApp_Codeフォルダー内のコードをコンパイルします。このフォルダ内のアイテムは、変更が検出されると再コンパイルされます。
問題は、動的にコンパイルされたWebサイトではなく、Webアプリケーションを構築していることです。ただし、XMLを介して提供するのではなく、構成値をC#に直接格納できるようにし、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;
目標は、アプリケーション全体を再コンパイルせずに更新できる構成可能な領域にリテラルを格納できるようにすることです。
ビルドアクションをコンパイルするように設定 で.cs
ファイルを使用できますが、そうすると、出力からApp_Code/Globals.cs
が削除されます。
[〜#〜] q [〜#〜]:識別する方法はありますかプロジェクトの一部は、プロジェクトの残りの部分をプリコンパイルできるようにしながら、 動的にコンパイル する必要がありますか?
content
に設定すると、.csファイルはbin
フォルダーにコピーされ、コンパイルされます。実行時。ただし、その場合、設計時には利用できません。compile
に設定すると、設計/実行時に他のコンパイル済みクラスと同じようにオブジェクトにアクセスできますが、公開されると、/ App_Codeフォルダーから削除されます。 Copy Always
を介して出力ディレクトリに配置することはできますが、コンパイル済みのクラスが優先されるようであるため、アプリケーション全体を再デプロイせずに構成の変更をプッシュすることはできません。ここでは、2つの異なる問題を克服する必要があります。
最初の問題は、コンパイルされたクラスとコンパイルされていないクラスの両方を取得しようとすることです。コードの他のセクションがそれが存在することを認識し、強い型付けでそのプロパティを使用できるように、設計時にコンパイルする必要があります。ただし、通常、コンパイルされたコードは出力から削除されるため、同じクラスの複数のバージョンが名前の競合を引き起こすことはありません。
いずれにせよ、最初にクラスをコンパイルする必要がありますが、再コンパイル可能なコピーを永続化するための2つのオプションがあります。
App_Code
に追加します。これはデフォルトで実行時にコンパイルされますが、 Build Action = Compile に設定して、で利用できるようにします。設計時間も。最低限、これはコンパイラーに請求するのは難しい作業です。クラスを消費するコードはすべて、コンパイル時に存在することが保証されている必要があります。 App_Code経由かどうかに関係なく、動的にコンパイルされるものはすべて、まったく異なるアセンブリの一部になります。したがって、同一のクラスを作成することは、そのクラスの図のように扱われます。基になるタイプは同じかもしれませんが、 ce n'est une pipe 。
2つのオプションがあります。アセンブリ間でインターフェイスまたは横断歩道を使用します。
interfaceを使用する場合、初期ビルドでコンパイルでき、どの動的タイプでも同じインターフェースを実装できます。このようにして、コンパイル時に存在するものに安全に依存し、作成したクラスをバッキングプロパティとして安全にスワップアウトできます。
アセンブリ間で型をキャストする場合 、既存の使用法は最初にコンパイルされた型に依存することに注意することが重要です。したがって、動的タイプから値を取得し、 これらのプロパティ値を元のタイプに適用する にする必要があります。
evk ごとに、起動時にAppDomain.CurrentDomain.GetAssemblies()
にクエリを実行して、新しいアセンブリ/クラスをチェックするというアイデアが好きです。インターフェースを使用することは、プリコンパイルされた/動的にコンパイルされたクラスを統合するためのおそらく賢明な方法であることを認めますが、理想的には、変更された場合に簡単に再読み取りできる単一のファイル/クラスが必要です。
S.Deepika ごとに、ファイルから動的にコンパイルするというアイデアは好きですが、値を別のプロジェクトに移動する必要はありません。
App_Code
を除外するApp_Codeは、同じクラスの2つのバージョンをビルドする機能のロックを解除しますが、公開後にどちらかを変更することは実際には困難ですこれから見ていきます。 〜/ App_Code /にある.cs
ファイルは、アプリケーションの実行時に動的にコンパイルされます。したがって、Visual Studioでは、App_Codeに追加し、Build Actionをに設定することで、同じクラスを2回ビルドできます。コンパイル。
ビルドアクションとコピー出力:
ローカルでデバッグすると、すべての.csファイルがプロジェクトAssemblyに組み込まれ、〜/ 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>();
コンパイル済みおよび動的タイプ:
これは本当に問題#1の解決に近いです。アプリを実行するたびに、両方のタイプにアクセスできます。コンパイルされたバージョンは設計時に使用でき、ファイル自体への変更は、IISによって、実行時にアクセスできるバージョンに自動的に再コンパイルされます。
ただし、デバッグモードを終了してプロジェクトを公開しようとすると、問題は明らかです。このソリューションは、IIS App_Code.xxxx
アセンブリを動的に構築することに依存し、ルートApp_Codeフォルダー内にある.csファイルに依存します。ただし、。csファイルがコンパイルされ、公開されたプロジェクトから自動的に削除されて、作成しようとしている(そして慎重に管理している)正確なシナリオを回避します。ファイルが残っていると、2つの同一のクラスが生成されます。どちらかが使用されるたびに、名前の競合が発生します。
ファイルをプロジェクトのアセンブリにコンパイルすることと、ファイルを出力ディレクトリにコピーすることの両方によって、強制的に手を加えることができます。しかし、App_Codeは、〜/ bin/App_Code /内の魔法のいずれも機能しません。ルートレベルでのみ機能します〜/ App_Code /
App_Codeコンパイルソース:
公開するたびに、生成された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)およびコンパイル[〜#〜]および[〜#〜]コピーして出力するように設定します。これにより、後で使用できるファイルの2番目のバージョンが得られます。
このクラスは、どこからでも値を公開する静的クラス内で使用することで使用します。これは、特に動的にコンパイルする必要性と静的にアクセスする必要性の間の関心の分離に役立ちます。
~/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
です。このメソッドは、アプリケーションが再起動されるたびに1回呼び出され、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
の代わりにServer.MapPath
を使用する必要があることに注意してください
BuildFileIntoAssembly
-ファイルからアセンブリをロードするという点で、 CSharpCodeProvider
のドキュメントのコードと How to .csファイルからクラスをロードします 。また、依存関係と戦うのではなく、元のコンパイルで取得したのと同じように、 現在Appドメインにあるすべてのアセンブリへのコンパイラアクセス を指定しました。おそらくより少ないオーバーヘッドでそれを行う方法がありますが、それは一度のコストなので誰が気にします。
CopyProperties
-新しいプロパティを古いオブジェクトにマップするために、この質問のメソッドをどのように適応させましたか あるオブジェクトから同じタイプの別のオブジェクトにプロパティ値を自動的に適用しますか? これは、リフレクションを使用して両方のオブジェクトを分解し、各プロパティを反復処理します。
上記の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;
}
}
さて、同じ目標を達成するための他のより一般的な方法があります。理想的には、設定より規約で撮影します。しかし、これは私が今まで見た構成値を格納するための絶対的に最も簡単で、最も柔軟で、強く型付けされた方法を提供します。
通常、構成値は、マジックストリングと弱い型付けに依存する同様に奇妙なプロセスでXMLを介して読み込まれます。値のストアに到達するためにMapPath
を呼び出してから、XMLからC#へのオブジェクトリレーショナルマッピングを実行する必要があります。代わりに、ここでは、最初から最終的な型があり、異なるアセンブリに対してコンパイルされた同じクラス間のすべてのORM作業を自動化できます。
いずれの場合も、そのプロセスの夢の出力は、C#を直接記述して使用できるようにすることです。この場合、完全に構成可能なプロパティを追加したい場合は、クラスにプロパティを追加するのと同じくらい簡単です。完了!
アプリの新しいビルドを公開しなくても、その値が変更された場合はすぐに利用可能になり、自動的に再コンパイルされます。
動的に変化するクラスデモ:
コンパイルされた構成- Githubソースコード | ダウンロードリンク
構成パーツを別のプロジェクトに移動し、(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;
}
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
インターフェイスを実装するタイプのアセンブリの現在のアプリドメインを検索してプロキシを提供し(1回だけ、次にキャッシュします)、そのタイプへのすべての呼び出しをプロキシします。
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_CodeMessages
クラスのコードを変更すると、次のリクエストでasp.netは現在のアプリケーションドメインを破棄し(最初に保留中のすべてのリクエストが終了するのを待ちます)、新しいアプリドメインを作成します。 Messages
を再コンパイルし、その新しいアプリドメインにロードします(この特定の状況だけでなく、App_Codeで何かを変更すると、アプリドメインのこの再作成が常に発生することに注意してください)。したがって、次のリクエストでは、明示的に何も再コンパイルしなくても、メッセージの新しい値がすでに表示されます。
メインアプリケーションを再コンパイルせずにメッセージを追加または削除(または名前を変更)することは明らかにできないことに注意してください。これを行うには、メインアプリケーションコードに属するIMessages
インターフェイスを変更する必要があるためです。試してみると、asp.netは次の(および後続のすべての)要求でコンパイル失敗エラーをスローします。
私は個人的にそのようなことをするのを避けたいと思います、しかしあなたがそれで大丈夫なら-なぜそうではありません。