カテゴリー別アーカイブ: ASP.NET MVC

【F# + ASP.NET MVC】 例外発生時にカスタム・エラーページを表示するには

◆ Web.config

<configuration>
  <system.web>
    <!-- 
    カスタム・エラーページの設定
    defaultRedirect属性を指定することにより、
      1./Views/コントローラ名/defaultRedirect属性.cshtml
      2./Views/コントローラ名/defaultRedirect属性.cshtml
      3./Views/Shared/defaultRedirect属性.cshtml
      4./Views/Shared/defaultRedirect属性.cshtml
    順にビューを検索する。
    -->
    <customErrors mode="On" defaultRedirect="Error">
      <!-- カスタム・エラーページ で例外が発生した場合、ErrorLast.htm を表示-->
      <error statusCode="500" redirect="/ErrorLast.htm" />
    </customErrors>
  </system.web>

◆ Controller

namespace Sample.Controllers

open System.Web
open System.Web.Mvc
open System.Collections.Generic

type HomeController() =
    inherit Controller()

    // カスタム・エラーを有効化
    [<HandleError>]
    member this.Index () =
        // 例外を発生させる
        raise (System.Exception("例外が発生"))
        this.View() :> ActionResult

◆ /Views/Shared/Error.cshtml

<!DOCTYPE html>
<html>
<head>
    <title>Error</title>
</head>
<body>
    <div>
        <ul>
            <li>【例外名】
                @Html.Encode(Model.Exception.GetType().Name)</li>
            <li>【メッセージ】
                @Html.Encode(Model.Exception.Message)</li>
            <li>【発生元】
                @Html.Encode(Model.ControllerName)
                /@Html.Encode(Model.ActionName) アクション</li>
        </ul>
    </div>
</body>

【ASP.NET MVC】 HTMLヘルパー一覧

◆ View

@{ Layout = null; }

<!DOCTYPE html>
<html>
<head>
    <title>Index</title>    
</head>
<body>
    <div>
        <!--*********************************************************-->
        <!--form タグ ***********************************************-->
        <!--*********************************************************-->
        <!-- 開始 <form> タグを書き込みます。 -->
        @using (@Html.BeginForm() ) { }
        <form action="/" method="post"></form> 
        <!-- 指定したルートの開始 <form> タグを書き込みます。 -->
        @using (@Html.BeginRouteForm("Default") ) { }
        <form action="/" method="post"></form>
        

        <!--*********************************************************-->
        <!--input タグ **********************************************-->
        <!--*********************************************************-->
        <!-- テキストの input 要素を返します。 -->
        @Html.TextBox("txt1")
        <input id="txt1" name="txt1" type="text" value="" />

        <!-- HTML input 要素を返します。 -->
        @Html.Editor("edt1")
        <input class="text-box single-line" id="edt1" name="edt1"
            type="text" value="" />

        <!-- チェック ボックスの input 要素を返します。 -->
        @Html.CheckBox("chk1")
        <input id="chk1" name="name" type="checkbox" value="true" />
        <input name="chk1" type="hidden" value="false" />

        <!-- オプション ボタンの input 要素を返します。 -->
        @Html.RadioButton("rdb1", "item1")
        <input id="rdb1" name="rdb1" type="radio" value="item1" />

        <!-- 非表示の input 要素を返します。 -->
        @Html.Hidden("hdn1")
        <input id="hdn1" name="hdn1" type="hidden" value="" />

        <!-- パスワードの input 要素を返します。 -->
        @Html.Password("pwd1")
        <input id="pwd1" name="pwd1" type="password" />


        <!--*********************************************************-->
        <!--textarea タグ ******************************************-->
        <!--*********************************************************-->
        <!-- textarea 要素を返します。 -->
        @Html.TextArea("txa1")
        <textarea cols="20" id="txa1" name="txa1" rows="2"></textarea>


        <!--*********************************************************-->
        <!--select タグ *********************************************-->
        <!--*********************************************************-->
        <!-- 単一選択の select 要素を返します。 -->
        @Html.DropDownList("dlp1",
            (IEnumerable<SelectListItem>)ViewData["selectList"])
        <select id="dlp1" name="dlp1">
            <option value="itemValue1">item1</option>
            <option value="itemValue2">item2</option>
        </select>


        <!--*********************************************************-->
        <!--label タグ **********************************************-->
        <!--*********************************************************-->
        <!-- HTML label 要素を返します。 -->
        @Html.Label("lbl1")
        <label for="lbl1">lbl1</label>


        <!--*********************************************************-->
        <!--a タグ **************************************************-->
        <!--*********************************************************--> 
        <!-- アンカー要素 (a 要素) を返します。 -->
        @Html.ActionLink("linkText", "actionName")
        <a href="/Home/actionName">linkText</a>

        <!-- ルートのアンカー要素 (a 要素) を返します。 -->
        @Html.RouteLink("rtl1", "Default")
        <a href="/">rtl1</a>


        <!--*********************************************************-->
        <!--検証 ****************************************************-->
        <!--*********************************************************-->
        <!-- フィールドにエラーが存在する場合、検証メッセージを表示します。 -->
        @Html.ValidationMessage("Body")
        <span class="field-validation-valid" data-valmsg-for="Body"
             data-valmsg-replace="true"></span>

        <!-- 検証メッセージの順序なしのリスト (ul 要素) を返します。 -->
        @Html.ValidationSummary()
        <div class="validation-summary-valid" data-valmsg-summary="true">
            <ul><li style="display:none"></li></ul></div>


        <!--*********************************************************-->
        <!--etc. ****************************************************-->
        <!--*********************************************************-->
        <!-- 指定された子アクション メソッドを呼び出し、
             結果を HTML 文字列として返します。 -->
        @Html.Action("Test")
        Testメソッド結果

        <!-- ViewDataのプロパティを表示します。 -->
        @Html.Display("Message")
         "Hello F# + ASP.NET MVC!"

    </div>
</body>
</html>

◆ Controller

namespace Sample.Controllers

open System.Web
open System.Web.Mvc
open System.Collections.Generic

type HomeController() =
    inherit Controller()

    member this.Index () =
        // @Html.Display 用
        this.ViewData.["Message"] <-  "Hello F# + ASP.NET MVC!"
        
        // @Html.DropDownList 用
        let selectListItem1 = new SelectListItem();
        selectListItem1.Text <- "item1"
        selectListItem1.Value <- "itemValue1"
        let selectListItem2 = new SelectListItem();
        selectListItem2.Text <- "item2"
        selectListItem2.Value <- "itemValue2"
        // IEnumerable<SelectListItem> 形式へ変換
        this.ViewData.["selectList"]
            <- [| selectListItem1; selectListItem2 |]
        
        this.View() :> ActionResult

    // @Html.Action 用
    member this.Test() =
        "Testメソッド結果"

【F# + ASP.NET MVC】 非同期処理にタイムアウトを指定するには

◆ Controller

namespace Sample.Controllers

open System.Web
open System.Web.Mvc

type HomeController() =
    inherit AsyncController()

    // 非同期処理(3秒でタイムアウト)
    [<AsyncTimeout(3000)>]
    member this.IndexAsync() =
        this.AsyncManager.OutstandingOperations.Increment() |> ignore
        
        // 非同期処理を実行
        async {
            // テスト用に3秒待機
            do! Async.Sleep(3000)

            this.AsyncManager.Parameters.["result"] <- "タイムアウト失敗"
            this.AsyncManager.OutstandingOperations.Decrement() |> ignore
        }
        |> Async.Start

    // 完了処理
    member this.IndexCompleted(result : string) =
        this.ViewData.["Html"] <- result
        this.View() :> ActionResult

【F# + ASP.NET MVC】 カスタム非同期コントローラーを作成するには(その2)

◆ 参考

Asynchronous Workflow Controller

◆ Controller

namespace Sample.Controllers

open Unchecked
open System
open System.Web.Mvc
open System.Web.Mvc.Async
open System.Net

exception PreserveStackTraceWrapper of exn

// カスタム非同期コントローラー
// そのままコピペ。。。後で詳細を調べてみよう。
type FSharpAsyncController() = 
    inherit AsyncController()

    override __.CreateActionInvoker() = 
        upcast {   
            new AsyncControllerActionInvoker() with
                member __.GetControllerDescriptor(controllerContext) =
                    let controllerType = controllerContext.Controller.GetType()
                    upcast {
                        new ReflectedControllerDescriptor(controllerType) with
                            member ctrlDesc.FindAction(controllerContext, actionName) =
                                let forwarder = base.FindAction(controllerContext, actionName) :?> ReflectedActionDescriptor
                                if (forwarder <> null && forwarder.MethodInfo.ReturnType = typeof<Async<ActionResult>>) then 
                                    let endAsync' = ref (defaultof<IAsyncResult -> Choice<ActionResult, exn>>)
                                    upcast {
                                        new AsyncActionDescriptor() with
                                            member actionDesc.ActionName = forwarder.ActionName
                                            member actionDesc.ControllerDescriptor = upcast ctrlDesc
                                            member actionDesc.GetParameters() = forwarder.GetParameters()
                                            member actionDesc.BeginExecute(controllerContext, parameters, callback, state) =
                                                let asyncWorkflow = 
                                                    forwarder.Execute(controllerContext, parameters) :?> Async<ActionResult>
                                                    |> Async.Catch
                                                let beginAsync, endAsync, _ = Async.AsBeginEnd(fun () -> asyncWorkflow)
                                                endAsync' := endAsync
                                                beginAsync((), callback, state)
                                            member actionDesc.EndExecute(asyncResult) =
                                                match endAsync'.Value(asyncResult) with
                                                    | Choice1Of2 value -> box value
                                                    | Choice2Of2 why -> raise <| PreserveStackTraceWrapper(why)
                                    } 
                                else 
                                    upcast forwarder 
                    } 
        }

type HomeController() = 
    // カスタム非同期コントローラーを継承
    inherit FSharpAsyncController()
    // 非同期処理
    member this.Index() = 
        let v = this.View()
        async {
            // WebページのHTMLを取得
            let wc = new WebClient()
            let! html = wc.AsyncDownloadString(
                            new Uri("http://www.google.co.jp"))
            // 非同期処理の結果をセット
            this.ViewData.["Html"] <- html
            return v :> ActionResult
        }
                

【F# + ASP.NET MVC】 カスタム非同期コントローラーを作成するには

◆ 参考

Asynchronous Controller Helper

◆ Controller

namespace Sample.Controllers

open System
open System.Web
open System.Web.Mvc
open System.Net

// 非同期ユーティリティ
type AsyncActionBuilder(asyncMgr : Async.AsyncManager) = 
    // 'async' operations
    member this.Bind(v, f) = async.Bind(v, f)
    member this.Return(v) = async.Return(v)    
    (* omit
    member this.Combine(a, b) = async.Combine(a, b)
    member this.Delay(f) = async.Delay(f)
    member this.For(s, f) = async.For(s, f)
    member this.ReturnFrom(a) = async.ReturnFrom(a)
    member this.TryFinally(a, b) = async.TryFinally(a, b)
    member this.TryWith(a, b) = async.TryWith(a, b)
    member this.Using(r, f) = async.Using(r, f)
    member this.While(c, f) = async.While(c, f)
    member this.Zero() = async.Zero()
    omit *)

    member this.Run(workflow) = 
        // 保留中の操作数を1増やす
        asyncMgr.OutstandingOperations.Increment() |> ignore
        // 非同期処理を実行
        async {        
            let! res = workflow        
            // 処理結果をコレクションに追加すると、
            // Completedメソッドの引数に自動的にバインドされる。
            asyncMgr.Parameters.["result"] <- res
            asyncMgr.OutstandingOperations.Decrement() |> ignore
        }
        |> Async.Start

// カスタム非同期コントローラー
type FSharpAsyncController() = 
    inherit AsyncController()
    member this.AsyncAction = 
        new AsyncActionBuilder(this.AsyncManager)

type HomeController() =
    // カスタム非同期コントローラーを継承
    inherit FSharpAsyncController()
    // 非同期処理    
    member this.IndexAsync() = this.AsyncAction {
        // WebページのHTMLを取得
        let wc = new WebClient()
        let! html = wc.AsyncDownloadString(
                        new Uri("http://www.google.co.jp"))
        return html
    }

    // 完了処理(AsyncManager.Parameters が自動的にバインドされる)
    member this.IndexCompleted(result : string) =
        // 非同期処理の結果をセット
        this.ViewData.["Html"] <- result
        this.View() :> ActionResult

【F# + ASP.NET MVC】 非同期処理を行うには

◆ Controller

namespace Sample.Controllers

open System
open System.Web
open System.Web.Mvc
open System.Net

type HomeController() =
    inherit AsyncController()

    // 非同期処理
    member this.IndexAsync() =
        // 保留中の操作数を1増やす
        this.AsyncManager.OutstandingOperations.Increment() |> ignore
        // 非同期処理を実行
        async {
            // WebページのHTMLを取得
            let wc = new WebClient()
            let! html = wc.AsyncDownloadString(
                            new Uri("http://www.google.co.jp"))

            // 処理結果をコレクションに追加すると、
            // Completedメソッドの引数に自動的にバインドされる。
            this.AsyncManager.Parameters.["result"] <- html
            // 保留中の操作数を1減らす
            this.AsyncManager.OutstandingOperations.Decrement() |> ignore
        }
        |> Async.Start

    // 完了処理(AsyncManager.Parameters が自動的にバインドされる)
    member this.IndexCompleted(result : string) =
        // 非同期処理の結果をセット
        this.ViewData.["Html"] <- result
        this.View() :> ActionResult