normalian blog

Let's talk about Microsoft Azure, ASP.NET and Java!

ASP.NET MVC3 Previewを利用して VB.NET版 Razorを強引に動かしてみる『改』!

ASP.NET MVC RC版以降では、VB.NET版 Razorに対応している。当記事の寿命は、わずか一週間程度の命だったようだ…orz
何度か私のブログでも紹介させて頂いた ASP.NET MVC3 Previewだが、ASP.NET MVC3から導入された便利なRazorを利用しているだろうか。「Razorってなんだろう?」という方は、以下の記事を一読してRazorを試して頂けると幸いだ。

ASP.NET WebFormやASP.NET MVCで利用していた従来の記述方法と異なり、Razorはデザインとロジックの分離が一層はかられた記述方法であることが理解できるだろう。

今回は、ASP.NET MVC3 Preview版では未サポートであるVB.NET版のRazor記法を動作させる手順を紹介する*1

VB.NET版 Razor(*.vbhtml)を動かすための手順

動作させるためには以下の手順が必要だ。MVC3 Previewソースのリコンパイルが必要であるため、心して準備して頂きたい。

  1. ASP.NET MVC3 Previewのソースをダウンロード&zipファイルを展開
  2. ASP.NET MVC3 Preview側
    1. ASP.NET MVC3 Preview側のソースコードを修正
  3. ASP.NET MVC3 Preview3 VB.NET版ソリューション
    1. ASP.NET MVC3 Preview VB.NET版のソリューション作成
    2. ASP.NET MVC3 Previewの参照先をダウンロードしたソースに変更
    3. Global.asaxの修正
    4. コントローラ(*.vb)のソースファイルを修正
    5. 画面(*.aspx)のソースファイルを修正

上記の手順を踏めば、VB.NET版 Razorが利用できる(次の画像例を参照)。

今回の記事での動作例は、あくまで「サポートしていないものを無理やり動かした」ものである点に注意してほしい。今回紹介したものがVB.NET Razorの完成版文法である保証はない

ASP.NET MVC3 Previewのソースをダウンロード&zipファイルを展開

ASP.NET MVC3 Previewのソースコードは以下から取得できる。「ASP.NET MVC3 Preview 1 Source Code」のリンクから、同バージョンのソースコードを入手してほしい。
http://aspnet.codeplex.com/releases/view/50092

ダウンロード後、任意のフォルダにzipファイルを展開し、「mvc3-preview1-sources」フォルダ内に「MvcDev.sln」が存在することを確認してほしい。

ASP.NET MVC3 Preview側のソースコードを修正

ASP.NET MVC3 Preview側のソースコードを修正する箇所が3点ほど存在する。修正点は以下となる。

  • System.Web.Mvc.PreApplicationStartCode内のコメントアウトを削除

vbhtmlが含まれる行のコメントアウトを削除し、ソースコードを有効化する。

    public static class PreApplicationStartCode {
        private static bool _startWasCalled;
        public static void Start() {
            // Guard against multiple calls. All Start calls are made on same thread, so no lock needed here
            if (_startWasCalled)
                return;
            _startWasCalled = true;

            BuildProvider.RegisterBuildProvider(".cshtml", typeof(InlinePageBuildProvider));
            //次の行を有効化した
            BuildProvider.RegisterBuildProvider(".vbhtml", typeof(InlinePageBuildProvider));
  • System.Web.Mvc.VbhtmlView.cs の追加

System.Web.Mvc.CshtmlView.csをベースに作成する。ソースコードを以下に示す。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace System.Web.Mvc
{
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.IO;
    using System.Web.Compilation;
    using System.Web.Mvc.Resources;
    using Microsoft.WebPages;

    [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Vbhtml", Justification = "Vbhtml is the name of the feature")]
    public class VbhtmlView: IView
    {
        private IBuildManager _buildManager;
        private Func<IMvcServiceLocator> _serviceLocatorThunk = () => MvcServiceLocator.Current;

        [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification = "The purpose is to simplify the API")]
        public VbhtmlView(string viewPath, string layoutPath = "")
        {
            if (String.IsNullOrEmpty(viewPath))
            {
                throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewPath");
            }

            ViewPath = viewPath;
            LayoutPath = layoutPath ?? String.Empty;
        }

        internal VbhtmlView(string viewPath, string layoutPath, IMvcServiceLocator serviceLocator)
            : this(viewPath, layoutPath)
        {
            _serviceLocatorThunk = () => serviceLocator;
        }

        internal IBuildManager BuildManager
        {
            get
            {
                if (_buildManager == null)
                {
                    _buildManager = new BuildManagerWrapper();
                }
                return _buildManager;
            }
            set
            {
                _buildManager = value;
            }
        }

        public string LayoutPath
        {
            get;
            private set;
        }

        public string ViewPath
        {
            get;
            private set;
        }

        protected internal virtual object CreateViewInstance()
        {
            Type type = BuildManager.GetCompiledType(ViewPath);
            if (type == null)
            {
                return null;
            }

            try
            {
                return _serviceLocatorThunk().GetInstance(type);
            }
            catch (ActivationException)
            {
                return Activator.CreateInstance(type);
            }
        }

        public void Render(ViewContext viewContext, TextWriter writer)
        {
            if (viewContext == null)
            {
                throw new ArgumentNullException("viewContext");
            }
            if (writer == null)
            {
                throw new ArgumentNullException("writer");
            }

            object instance = CreateViewInstance();
            if (instance == null)
            {
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        //VB版がなかった。。。
                        MvcResources.CshtmlView_ViewCouldNotBeCreated,
                        ViewPath));
            }

            WebViewPage webViewPage = instance as WebViewPage;
            if (webViewPage == null)
            {
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        //VB版がなかった。。。
                        MvcResources.CshtmlView_WrongViewBase,
                        ViewPath));
            }

            webViewPage.OverridenLayoutPath = LayoutPath;
            webViewPage.VirtualPath = ViewPath;
            webViewPage.ViewContext = viewContext;
            webViewPage.ViewData = viewContext.ViewData;

            webViewPage.InitHelpers();
            webViewPage.ExecutePageHierarchy(new WebPageContext(), writer, webViewPage);
        }
    }
}
  • System.Web.Mvc.VbhtmlViewEngine.csを追加する

System.Web.Mvc.CshtmlViewEngine.csをベースに作成する。ソースコードを以下に示す。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace System.Web.Mvc
{
    using System.Diagnostics.CodeAnalysis;

    [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Vbhtml", Justification = "Vbhtml is the name of the feature")]
    public class VbhtmlViewEngine : VirtualPathProviderViewEngine
    {
        private IBuildManager _buildManager;

        public VbhtmlViewEngine()
        {
            AreaViewLocationFormats = new[] { "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" };
            AreaMasterLocationFormats = new[] { "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" };
            AreaPartialViewLocationFormats = new[] { "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" };

            ViewLocationFormats = new[] { "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.vbhtml" };
            MasterLocationFormats = new[] { "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.vbhtml" };
            PartialViewLocationFormats = new[] { "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.vbhtml" };
        }

        internal IBuildManager BuildManager
        {
            get
            {
                if (_buildManager == null)
                {
                    _buildManager = new BuildManagerWrapper();
                }
                return _buildManager;
            }
            set
            {
                _buildManager = value;
            }
        }

        protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
        {
            return CreateView(partialPath);
        }

        protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
        {
            var view = CreateView(viewPath, masterPath);
            return view;
        }

        private static VbhtmlView CreateView(string viewPath, string layoutPath = "")
        {
            VbhtmlView view = new VbhtmlView(viewPath, layoutPath);
            return view;
        }

        protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
        {
            return BuildManager.FileExists(virtualPath);
        }
    }
}

ASP.NET MVC3 Preview VB版のソリューション作成

Visual Studio 2010 から[ファイル]→[新規作成]→[プロジェクト]を選択し、「ASP.NET MVC3 Web Application(ASPX)」のプロジェクトを作成してほしい。

ASP.NET MVC3 Preview の参照先をダウンロードしたソースに変更

ASP.NET MVC2 Preview時点の記事ではあるが、以下を参照して、ASP.NET MVC3 Previewの参照先を変更して頂きたい。
id:waritohutsu:20090912:1252738647

最終的には、ソリューションエクスプローラが以下の様になるはずだ。参考にしてほしい。

Global.asaxの修正

作成した「System.Web.Mvc.VbhtmlViewEngine」をデフォルトのViewEngineとして登録する。

  • Global.asax(抜粋)
Public Class MvcApplication
    Inherits System.Web.HttpApplication

    (中略)

    Sub Application_Start()
        AreaRegistration.RegisterAllAreas()

        RegisterGlobalFilters(GlobalFilters.Filters)
        RegisterRoutes(RouteTable.Routes)
        ViewEngines.Engines.Add(New VbhtmlViewEngine())
    End Sub
End Class

コントローラ(*.vb)のソースファイルを修正

/Controllers/HomeController.vb の内容を、以下を参考に修正する。

  • HomeController.vb
Public Class HomeController
    Inherits System.Web.Mvc.Controller

    Function Index() As ActionResult

        Dim list = New List(Of Person)

        list.Add(New Person())
        list.Add(New Person("二個目"))
        list.Add(New Person("三個目"))

        ViewData("Message") = "Welcome to ASP.NET MVC!"
        ViewData("Owner") = "normalian"
        ViewData("Number") = 9

        Return View(list)
    End Function

    Function About() As ActionResult
        Return View()
    End Function
End Class

Public Class Person

    Public Sub New()
        name = "デフォルト"
    End Sub
    Public Sub New(ByVal arg As String)
        Name = arg
    End Sub

    Private _name As String
    Public Property Name() As String
        Get
            Return _name
        End Get
        Set(ByVal value As String)
            _name = value
        End Set
    End Property


End Class

画面(*.aspx)のソースファイルを修正

/Views/Home/Index.aspxのファイル名を/Views/Home/Index.vbhtmlに変更し、内容を以下を参考に修正する。

@inherits System.Web.Mvc.WebViewPage

<h2>値の参照を試す</h2>
<ul>
 <li> @View.Message 、ひゃっほう!! </li>
 <li> @DateTime.Now </li>
</ul>


<h2>If文を試す</h2>

@If View.Number Mod 2 = 0 Then 
	@<p>odd</p>
Else 
	@<p>even</p>
End If

@If View.Owner = "normalian" Then 
	@<p>Yes</p>
Else 
	@<p>No</p>
End If


<h2>フォーム系を試す</h2>
@Using Html.BeginForm()
	@<div>
	divタグ表示
	</div>
	@<input type="submit" value="発射" />
End Using


<h2>ループを試す</h2>

<ul>
@For Each p in Model
	@<li>@p.Name</li>
Next
</ul>

*1:今回の記事は[http://twitter.com/#!/gtk2k:title=twitterId gtk2k]氏に多大なご尽力を頂いた