NVelocity for ASP.NET MVC
48358 ワード
私の
この編博文には、「ビュー内のコードへのアクセスをtrust levelのように単独で制限する方法はないかと考えていましたが、このtrust levelはテンプレート内のコードを制限するために使用されています」という言葉があります.読者がいる
johngeng問、なぜtrust levelでviewをロックしたのか、彼はよく理解していない.私の本意は、viewでは、開発者が特定の機能のコードしか書けないことを望んで、特定のオープンなAPIを呼び出して、ほとんどのセキュリティレベルの高いコード、例えば読み書きファイルなどのAPIやクラスライブラリに対して、viewで使用することを許可しません.これは、テンプレートを公開し、オンラインでユーザーに修正する必要がある場合に非常に重要です.現在、WebFormもRazorも、非常に自由なテンプレートであり、ViewでできることはControllerや他の場所で書かれたコードと同じであり、Viewはユーザーがオンラインで修正することを許可していない.
同じブログでは、johngengの読者が@ではなく$が好きだと言っていました.私はNVelocityを知らなかったので、クライアント開発パッケージjqueryを言っていると誤解しました.今から見れば、彼の言うことはNVelocityであるべきで、彼はこの人が教えられないと思っているかもしれないが、彼は私の疑問に直接返事していない.これも自分の知識面が狭すぎるのを責めるしかない.
最近、プロジェクトにマルチテンプレートエンジンを追加していないと、上記の2つの質問の答えは永遠に得られないかもしれませんが、この2つの答えはNVelocityと関係があります.私は普段NVelocityという言葉を見たことがあるに違いないが、WebForm以外のテンプレートエンジンを選ぶには、私はまだ彼のことを全く覚えていない.同僚の@浪子がNVelocityというテンプレートエンジンを試してみる価値があると注意してくれた.公式の文法の紹介を見て、非常に簡潔で実用的なテンプレートであり、柔軟性と安全性を失わないと言わざるを得ません.私が指している柔軟性は、StringTemplateのように制限されていないほど死んでいて、オブジェクトの関数さえ呼び出すことができません.セキュリティの面では、テンプレート上で開発者がテンプレート上で指定したAPIを呼び出すしかないことを制限することもできます.これまでNVelocityはとても満足していました.
ASP.NET MVCのビュー切り替えエンジンはASP.NET MVC1.0が出てから、MvcContribでは複数のビューエンジンの切り替え選択が提供されていましたが、最近のバージョンでは、関連するコードが見つかりませんでした.これらのコードはすでに移動されたはずですが、その紹介ドキュメントでは関連するテーマが削除されていません.@重典の子供靴のブログで、MvcContribから抽出したインプリメンテーションを見つけた.しかし、この実装はMVC 3に比べて相対的に時代遅れであり、IViewLocatorのようなインタフェースはすでに存在しないように変更または削除されているインタフェースもある.また、HtmlHelper拡張メソッドを呼び出す機能を排除し、必要な拡張メソッドをカスタマイズしたため、拡張関数をサポートすることが最も重要です.次にNVelocity for ASPを見てみましょう.NET MVCのいくつかのクラスの詳細:
以前の実装ではIViewEngineというインタフェースが直接実装されており,Viewを検索する経路はIViewLocatorを実装することによって位置決めされている.MVC 2では、この部分の実装が修正され、MVC内部にはVirtualPathProviderViewEngineというテンプレートメソッドクラスが提供されています.サブクラスでは、検索するパスフォーマットを設定する必要があります.他のイベントはテンプレートメソッドクラスに渡して完了することができます.このように、実装を簡略化する一方で、デフォルトのパス検索方式と統一することもできます.
また、Nvelocity内蔵の相対ファイルパスを使用してテンプレートを検索し、VirtualPathのスタイルを使用するため、VirtualPathを見つけた後、実際の物理パスに変換し、直接物理パスを通じてテンプレートの内容をロードする必要がありますが、内蔵のFileResourceLoaderは物理パスからテンプレートをロードすることをサポートしていないので、FileResourceLoaderを追加して実装する必要があります.物理パスからのロード方法をサポートします.この2つのクラスのコードは次のとおりです.
主にIViewインタフェースを実装し,Render法を実装してテンプレートと現在のコンテキストを結合して出力する.このクラスは実現しているが,IViewDataContainerは特に必要ではないようだ.NVelocityのRendererも簡単で、必要な対像をNVelocityが実行するコンテキストに詰めて、Mergeメソッドを呼び出すだけでOKです.ここで特に説明するのは、NVelocityテンプレートの上で、コンテキストオブジェクトの任意の属性と方法を呼び出すことができますが、オブジェクト上の拡張方法を呼び出すことはできません.この場合、NVelocityが提供するIDuckというインタフェースを利用して、拡張方法のサポートを提供する必要があります.次のコードがあります.new HtmlExtensionDuck(context,this);完全なコードは次のとおりです.
ExtensionDuckはIDuckインタフェースの実装であり、拡張メソッドのサポートが必要なDuckオブジェクトのベースクラスです.拡張メソッドに接続する必要があるすべてのオブジェクトは、このメソッドを継承することで、ほとんどの作業を簡素化できます.
次に、HtmlExtensionDuckを実装し、ViewでHtmlHelperに呼び出すことができる拡張メソッドを指定します.オープンな拡張メソッドが必要な場合はHTML_EXTENSION_TYPESでは、拡張メソッドが存在する静的クラス名を指定します.
完全なNVelocity for ASP.NET MVCの実装は以上のクラスで可能である.その後、システムに直接登録することができます.私たちはConrollerを書き換える必要はありません.私たちは直接ViewEngineをMVCに登録して使用することができます.プログラムが複数のビューエンジンが共存する調和のとれた場面をサポートすることもできます.簡単な登録コードはGlobalに置きます.asaxファイル:
詳細な使用例は、添付ファイルのダウンロードで詳細を参照してください.
最後にまとめると、NVelocityは確かに簡単で実用的なテンプレートエンジンであり、特に文法が非常に簡潔であり、APIの拡張性が強い.Razorの文法の基本的なスタイルは、その文法のスタイルを参考にしているはずです.私は今でもNVelocityが大好きですが、特別な場合の特別なニーズでなければ、普通のASP.NET MVCプログラムでは、私はやはりRazorを横に使います.それはもっと自由で、直接的なC#コンパイルサポートなので、私たちは何でもすることができます.これは多くの開発者にとって重要です.もう一つ無視できないのは、IDEのサポートです.特にコードヒントは確かに気持ちがいいです.
この編博文には、「ビュー内のコードへのアクセスをtrust levelのように単独で制限する方法はないかと考えていましたが、このtrust levelはテンプレート内のコードを制限するために使用されています」という言葉があります.読者がいる
johngeng問、なぜtrust levelでviewをロックしたのか、彼はよく理解していない.私の本意は、viewでは、開発者が特定の機能のコードしか書けないことを望んで、特定のオープンなAPIを呼び出して、ほとんどのセキュリティレベルの高いコード、例えば読み書きファイルなどのAPIやクラスライブラリに対して、viewで使用することを許可しません.これは、テンプレートを公開し、オンラインでユーザーに修正する必要がある場合に非常に重要です.現在、WebFormもRazorも、非常に自由なテンプレートであり、ViewでできることはControllerや他の場所で書かれたコードと同じであり、Viewはユーザーがオンラインで修正することを許可していない.
同じブログでは、johngengの読者が@ではなく$が好きだと言っていました.私はNVelocityを知らなかったので、クライアント開発パッケージjqueryを言っていると誤解しました.今から見れば、彼の言うことはNVelocityであるべきで、彼はこの人が教えられないと思っているかもしれないが、彼は私の疑問に直接返事していない.これも自分の知識面が狭すぎるのを責めるしかない.
最近、プロジェクトにマルチテンプレートエンジンを追加していないと、上記の2つの質問の答えは永遠に得られないかもしれませんが、この2つの答えはNVelocityと関係があります.私は普段NVelocityという言葉を見たことがあるに違いないが、WebForm以外のテンプレートエンジンを選ぶには、私はまだ彼のことを全く覚えていない.同僚の@浪子がNVelocityというテンプレートエンジンを試してみる価値があると注意してくれた.公式の文法の紹介を見て、非常に簡潔で実用的なテンプレートであり、柔軟性と安全性を失わないと言わざるを得ません.私が指している柔軟性は、StringTemplateのように制限されていないほど死んでいて、オブジェクトの関数さえ呼び出すことができません.セキュリティの面では、テンプレート上で開発者がテンプレート上で指定したAPIを呼び出すしかないことを制限することもできます.これまでNVelocityはとても満足していました.
ASP.NET MVCのビュー切り替えエンジンはASP.NET MVC1.0が出てから、MvcContribでは複数のビューエンジンの切り替え選択が提供されていましたが、最近のバージョンでは、関連するコードが見つかりませんでした.これらのコードはすでに移動されたはずですが、その紹介ドキュメントでは関連するテーマが削除されていません.@重典の子供靴のブログで、MvcContribから抽出したインプリメンテーションを見つけた.しかし、この実装はMVC 3に比べて相対的に時代遅れであり、IViewLocatorのようなインタフェースはすでに存在しないように変更または削除されているインタフェースもある.また、HtmlHelper拡張メソッドを呼び出す機能を排除し、必要な拡張メソッドをカスタマイズしたため、拡張関数をサポートすることが最も重要です.次にNVelocity for ASPを見てみましょう.NET MVCのいくつかのクラスの詳細:
NVelocityViewEngine
以前の実装ではIViewEngineというインタフェースが直接実装されており,Viewを検索する経路はIViewLocatorを実装することによって位置決めされている.MVC 2では、この部分の実装が修正され、MVC内部にはVirtualPathProviderViewEngineというテンプレートメソッドクラスが提供されています.サブクラスでは、検索するパスフォーマットを設定する必要があります.他のイベントはテンプレートメソッドクラスに渡して完了することができます.このように、実装を簡略化する一方で、デフォルトのパス検索方式と統一することもできます.
また、Nvelocity内蔵の相対ファイルパスを使用してテンプレートを検索し、VirtualPathのスタイルを使用するため、VirtualPathを見つけた後、実際の物理パスに変換し、直接物理パスを通じてテンプレートの内容をロードする必要がありますが、内蔵のFileResourceLoaderは物理パスからテンプレートをロードすることをサポートしていないので、FileResourceLoaderを追加して実装する必要があります.物理パスからのロード方法をサポートします.この2つのクラスのコードは次のとおりです.
public
class
FileResourceLoaderEx : FileResourceLoader
{
public
FileResourceLoaderEx() :
base
() { }
private
Stream FindTemplate(
string
filePath)
{
try
{
FileInfo file
=
new
FileInfo(filePath);
return
new
BufferedStream(file.OpenRead());
}
catch
(Exception exception)
{
base
.runtimeServices.Debug(
string
.Format(
"
FileResourceLoader : {0}
"
, exception.Message));
return
null
;
}
}
public
override
long
GetLastModified(
global
::NVelocity.Runtime.Resource.Resource resource)
{
if
(File.Exists(resource.Name))
{
FileInfo file
=
new
FileInfo(resource.Name);
return
file.LastWriteTime.Ticks;
}
return
base
.GetLastModified(resource);
}
public
override
Stream GetResourceStream(
string
templateName)
{
if
(File.Exists(templateName))
{
return
FindTemplate(templateName);
}
return
base
.GetResourceStream(templateName);
}
public
override
bool
IsSourceModified(
global
::NVelocity.Runtime.Resource.Resource resource)
{
if
(File.Exists(resource.Name))
{
FileInfo file
=
new
FileInfo(resource.Name);
return
(
!
file.Exists
||
(file.LastWriteTime.Ticks
!=
resource.LastModified));
}
return
base
.IsSourceModified(resource);
}
}
public
class
NVelocityViewEngine : VirtualPathProviderViewEngine, IViewEngine
{
public
static
NVelocityViewEngine Default
=
null
;
private
static
readonly
IDictionary DEFAULT_PROPERTIES
=
new
Hashtable();
private
readonly
VelocityEngine _engine;
static
NVelocityViewEngine()
{
string
targetViewFolder
=
Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
"
views
"
);
//
DEFAULT_PROPERTIES.Add(RuntimeConstants.RESOURCE_LOADER, "file");
DEFAULT_PROPERTIES.Add(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, targetViewFolder);
DEFAULT_PROPERTIES.Add(
"
file.resource.loader.class
"
,
"
NVelocityEngine.FileResourceLoaderEx\\,NVelocityEngine
"
);
Default
=
new
NVelocityViewEngine();
}
public
NVelocityViewEngine()
:
this
(DEFAULT_PROPERTIES)
{
}
public
NVelocityViewEngine(IDictionary properties)
{
base
.MasterLocationFormats
=
new
string
[] {
"
~/Views/{1}/{0}.vm
"
,
"
~/Views/Shared/{0}.vm
"
};
base
.AreaMasterLocationFormats
=
new
string
[] {
"
~/Areas/{2}/Views/{1}/{0}.vm
"
,
"
~/Areas/{2}/Views/Shared/{0}.vm
"
};
base
.ViewLocationFormats
=
new
string
[] {
"
~/Views/{1}/{0}.vm
"
,
"
~/Views/Shared/{0}.vm
"
};
base
.AreaViewLocationFormats
=
new
string
[] {
"
~/Areas/{2}/Views/{1}/{0}.vm
"
,
"
~/Areas/{2}/Views/Shared/{0}.vm
"
};
base
.PartialViewLocationFormats
=
base
.ViewLocationFormats;
base
.AreaPartialViewLocationFormats
=
base
.AreaViewLocationFormats;
base
.FileExtensions
=
new
string
[] {
"
vm
"
};
if
(properties
==
null
) properties
=
DEFAULT_PROPERTIES;
ExtendedProperties props
=
new
ExtendedProperties();
foreach
(
string
key
in
properties.Keys)
{
props.AddProperty(key, properties[key]);
}
_engine
=
new
VelocityEngine();
_engine.Init(props);
}
protected
override
IView CreateView(ControllerContext controllerContext,
string
viewPath,
string
masterPath)
{
Template viewTemplate
=
GetTemplate(viewPath);
Template masterTemplate
=
GetTemplate(masterPath);
NVelocityView view
=
new
NVelocityView(controllerContext, viewTemplate, masterTemplate);
return
view;
}
protected
override
IView CreatePartialView(ControllerContext controllerContext,
string
partialPath)
{
Template viewTemplate
=
GetTemplate(partialPath);
NVelocityView view
=
new
NVelocityView(controllerContext, viewTemplate,
null
);
return
view;
}
public
Template GetTemplate(
string
viewPath)
{
if
(
string
.IsNullOrEmpty(viewPath))
{
return
null
;
}
return
_engine.GetTemplate(System.Web.Hosting.HostingEnvironment.MapPath(viewPath));
}
}
NVelocityView
主にIViewインタフェースを実装し,Render法を実装してテンプレートと現在のコンテキストを結合して出力する.このクラスは実現しているが,IViewDataContainerは特に必要ではないようだ.NVelocityのRendererも簡単で、必要な対像をNVelocityが実行するコンテキストに詰めて、Mergeメソッドを呼び出すだけでOKです.ここで特に説明するのは、NVelocityテンプレートの上で、コンテキストオブジェクトの任意の属性と方法を呼び出すことができますが、オブジェクト上の拡張方法を呼び出すことはできません.この場合、NVelocityが提供するIDuckというインタフェースを利用して、拡張方法のサポートを提供する必要があります.次のコードがあります.new HtmlExtensionDuck(context,this);完全なコードは次のとおりです.
public
class
NVelocityView : IViewDataContainer, IView
{
private
ControllerContext _controllerContext;
private
readonly
Template _masterTemplate;
private
readonly
Template _viewTemplate;
public
NVelocityView(ControllerContext controllerContext,
string
viewPath,
string
masterPath)
:
this
(controllerContext, NVelocityViewEngine.Default.GetTemplate(viewPath), NVelocityViewEngine.Default.GetTemplate(masterPath))
{
}
public
NVelocityView(ControllerContext controllerContext, Template viewTemplate, Template masterTemplate)
{
_controllerContext
=
controllerContext;
_viewTemplate
=
viewTemplate;
_masterTemplate
=
masterTemplate;
}
public
Template ViewTemplate
{
get
{
return
_viewTemplate; }
}
public
Template MasterTemplate
{
get
{
return
_masterTemplate; }
}
private
VelocityContext CreateContext(ViewContext context)
{
Hashtable entries
=
new
Hashtable(StringComparer.InvariantCultureIgnoreCase);
if
(context.ViewData
!=
null
)
{
foreach
(var pair
in
context.ViewData)
{
entries[pair.Key]
=
pair.Value;
}
}
entries[
"
viewdata
"
]
=
context.ViewData;
entries[
"
tempdata
"
]
=
context.TempData;
entries[
"
routedata
"
]
=
context.RouteData;
entries[
"
controller
"
]
=
context.Controller;
entries[
"
httpcontext
"
]
=
context.HttpContext;
entries[
"
viewbag
"
]
=
context.ViewData;
CreateAndAddHelpers(entries, context);
return
new
VelocityContext(entries);
}
private
void
CreateAndAddHelpers(Hashtable entries, ViewContext context)
{
entries[
"
html
"
]
=
entries[
"
htmlhelper
"
]
=
new
HtmlExtensionDuck(context,
this
);
entries[
"
url
"
]
=
entries[
"
urlhelper
"
]
=
new
UrlHelper(context.RequestContext);
entries[
"
ajax
"
]
=
entries[
"
ajaxhelper
"
]
=
new
AjaxHelper(context,
this
);
}
public
void
Render(ViewContext viewContext, TextWriter writer)
{
this
.ViewData
=
viewContext.ViewData;
bool
hasLayout
=
_masterTemplate
!=
null
;
VelocityContext context
=
CreateContext(viewContext);
if
(hasLayout)
{
StringWriter sw
=
new
StringWriter();
_viewTemplate.Merge(context, sw);
context.Put(
"
childContent
"
, sw.GetStringBuilder().ToString());
_masterTemplate.Merge(context, writer);
}
else
{
_viewTemplate.Merge(context, writer);
}
}
private
ViewDataDictionary _viewData;
public
ViewDataDictionary ViewData
{
get
{
if
(_viewData
==
null
)
{
return
_controllerContext.Controller.ViewData;
}
return
_viewData;
}
set
{
_viewData
=
value;
}
}
}
ExtensionDuck
ExtensionDuckはIDuckインタフェースの実装であり、拡張メソッドのサポートが必要なDuckオブジェクトのベースクラスです.拡張メソッドに接続する必要があるすべてのオブジェクトは、このメソッドを継承することで、ほとんどの作業を簡素化できます.
public
class
ExtensionDuck : IDuck
{
private
readonly
object
_instance;
private
readonly
Type _instanceType;
private
readonly
Type[] _extensionTypes;
private
Introspector _introspector;
public
ExtensionDuck(
object
instance)
:
this
(instance, Type.EmptyTypes)
{
}
public
ExtensionDuck(
object
instance,
params
Type[] extentionTypes)
{
if
(instance
==
null
)
throw
new
ArgumentNullException(
"
instance
"
);
_instance
=
instance;
_instanceType
=
_instance.GetType();
_extensionTypes
=
extentionTypes;
}
public
Introspector Introspector
{
get
{
if
(_introspector
==
null
)
{
_introspector
=
RuntimeSingleton.Introspector;
}
return
_introspector;
}
set
{ _introspector
=
value; }
}
public
object
GetInvoke(
string
propName)
{
throw
new
NotSupportedException();
}
public
void
SetInvoke(
string
propName,
object
value)
{
throw
new
NotSupportedException();
}
public
object
Invoke(
string
method,
params
object
[] args)
{
if
(
string
.IsNullOrEmpty(method))
return
null
;
MethodInfo methodInfo
=
Introspector.GetMethod(_instanceType, method, args);
if
(methodInfo
!=
null
)
{
return
methodInfo.Invoke(_instance, args);
}
object
[] extensionArgs
=
new
object
[args.Length
+
1
];
extensionArgs[
0
]
=
_instance;
Array.Copy(args,
0
, extensionArgs,
1
, args.Length);
foreach
(Type extensionType
in
_extensionTypes)
{
methodInfo
=
Introspector.GetMethod(extensionType, method, extensionArgs);
if
(methodInfo
!=
null
)
{
return
methodInfo.Invoke(
null
, extensionArgs);
}
}
return
null
;
}
}
次に、HtmlExtensionDuckを実装し、ViewでHtmlHelperに呼び出すことができる拡張メソッドを指定します.オープンな拡張メソッドが必要な場合はHTML_EXTENSION_TYPESでは、拡張メソッドが存在する静的クラス名を指定します.
public
class
HtmlExtensionDuck : ExtensionDuck
{
public
static
readonly
Type[] HTML_EXTENSION_TYPES
=
new
Type[]
{
typeof
(DisplayExtensions),
typeof
(DisplayTextExtensions),
typeof
(EditorExtensions),
typeof
(FormExtensions),
typeof
(InputExtensions),
typeof
(LabelExtensions),
typeof
(LinkExtensions),
typeof
(MvcForm),
typeof
(PartialExtensions),
typeof
(RenderPartialExtensions),
typeof
(SelectExtensions),
typeof
(TextAreaExtensions),
typeof
(ValidationExtensions)
};
public
HtmlExtensionDuck(ViewContext viewContext, IViewDataContainer container)
:
this
(
new
HtmlHelper(viewContext, container))
{
}
public
HtmlExtensionDuck(HtmlHelper htmlHelper)
:
this
(htmlHelper, HTML_EXTENSION_TYPES)
{
}
public
HtmlExtensionDuck(HtmlHelper htmlHelper,
params
Type[] extentionTypes)
:
base
(htmlHelper, extentionTypes)
{
}
}
完全なNVelocity for ASP.NET MVCの実装は以上のクラスで可能である.その後、システムに直接登録することができます.私たちはConrollerを書き換える必要はありません.私たちは直接ViewEngineをMVCに登録して使用することができます.プログラムが複数のビューエンジンが共存する調和のとれた場面をサポートすることもできます.簡単な登録コードはGlobalに置きます.asaxファイル:
ViewEngines.Engines.Add(NVelocityViewEngine.Default);
詳細な使用例は、添付ファイルのダウンロードで詳細を参照してください.
最後にまとめると、NVelocityは確かに簡単で実用的なテンプレートエンジンであり、特に文法が非常に簡潔であり、APIの拡張性が強い.Razorの文法の基本的なスタイルは、その文法のスタイルを参考にしているはずです.私は今でもNVelocityが大好きですが、特別な場合の特別なニーズでなければ、普通のASP.NET MVCプログラムでは、私はやはりRazorを横に使います.それはもっと自由で、直接的なC#コンパイルサポートなので、私たちは何でもすることができます.これは多くの開発者にとって重要です.もう一つ無視できないのは、IDEのサポートです.特にコードヒントは確かに気持ちがいいです.