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のいくつかのクラスの詳細:

  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のサポートです.特にコードヒントは確かに気持ちがいいです.