月別アーカイブ: 2012年3月

【ASP.NET SPA】 入門サイト

★最近気になっているので、個人的メモ。MVVMパターンなので、Silverlight感覚で作れるのが良い感じ♪

【F# + ASP.NET MVC】 FParsec を使用してCSVファイルを読み込むには

◆ 参考

・モナディックなパーサ・コンビネータFParsecを使おう。
・F# でパーサーコンビネータを使った CSV ファイルのパース処理

◆ CSVファイル読み込み

namespace FShapLibrary.Util

open System
open System.IO

// #r "FParsec.dll"
// #r "FParsecCS.dll"
open FParsec

type ParseUtil() = 
  static member ParseCsv(path : string) =
    let quotedChar = noneOf "\"" <|> attempt (pstring "\"\"" >>. preturn '"')
    let quotedCell = pchar '"' >>. manyChars quotedChar .>> pchar '"'
    let cell = quotedCell <|> manyChars (noneOf ",\n")
    let line = sepBy cell (pchar ',')
    let csvFile = sepEndBy line newline

    let cs = 
      use tr = new StreamReader(path)
      tr.ReadToEnd()
      
    let res = run csvFile cs

    match res with
      | Success (v, _, _) -> v
      | Failure (msg, _, _) -> failwith msg

◆ Controller

namespace Sample.Controllers

open System.Web
open System.Web.Mvc

open FShapLibrary.Util

type HomeController() =
    inherit Controller()
    member this.Index() =
        // CSVファイルの読み込み
        let csvFile = base.Server.MapPath("/App_Data/test.csv")
        let csv = ParseUtil.ParseCsvFile(csvFile)
        // View用に [string list list]型
            => [IEnumerable<IEnumerable<string>>]型 へ変換 
        let csv = csv |> Seq.ofList |> Seq.map(fun s -> s |> Seq.ofList)
        this.View(csv) :> ActionResult

◆ View

@model IEnumerable<IEnumerable<string>>

@{ Layout = null; }

<!DOCTYPE html>
<html>
<head>
    <title>F# + ASP.NET</title>
</head>
<body>
    <div>
        <table>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Password</th>
            </tr>
            @foreach (var items in Model)
            {
            <tr>
                @foreach (var item in items)
                {
                <td>@Html.DisplayFor(modelItem => item)</td>
                }
            </tr>
            }
        </table>
    </div>
</body>
</html>

【F# + ASP.NET】 承認フィルターによるHTTPS通信に制限するには

◆ Controller

namespace FsWeb.Controllers

open System.Web
open System.Web.Mvc

// HTTPSに制限する承認フィルター
// ※ Visual StudioでSSLを有効にするには、Webプロジェクトのプロパティで、
//     [SSL有効化]にするを "True" に設定。 
[<RequireHttps>]
type HomeController() =
    inherit Controller()
    member this.Index () =
        this.View() :> ActionResult

【F# + ASP.NET MVC】 リポジトリパターンを適用するには

◆参考

 "ASP.NET MVCの開発応用編1 ~リポジトリパターンをマスターする~ "  "テストが容易な ASP.NET MVC アプリケーションを構築する" 

◆ Entity

namespace Sample.Entities

type Employee() = 
    member val ID = "" with get, set
    member val Name = "" with get, set
    member val Password = "" with get, set

◆ リポジトリ用インターフェース

namespace Sample.Interface 

open System.Collections.Generic
open System.ComponentModel.Composition

type IRepository<'T> =
    abstract GetAll: unit -> IList<'T>
    abstract Add: 'T -> unit
    abstract Remove: 'T -> unit
    abstract Save: unit -> unit

◆ リポジトリ

namespace Sample.Repository

open System
open System.Collections.Generic
open System.Linq

open Sample.Interface
open Sample.Entities

type EmployeeRepository() =
    member val private Context = new EduContext() with get, set
    
    interface IRepository<Employee> with
        member x.GetAll() =
            x.Context.Employees.ToList() :> IList<Employee>
        member x.Add(entity : Employee) =
            x.Context.Employees.Add(entity) |> ignore
        member x.Remove(entity : Employee) =
            x.Context.Employees.Remove(entity) |> ignore
        member x.Save() =
            x.Context.SaveChanges() |> ignore
            

◆ Controller(リポジトリパターン)

namespace Sample.Controllers

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

open Sample.Entities
open Sample.Interface
open Sample.Repository

type HomeController =
    inherit Controller
    // コンストラクタ:new(repository : IRepository<Employee>) の呼び出し
    new() = new HomeController(new EmployeeRepository())
    // コンストラクタ:リポジトリの初期化
    new(repository : IRepository<Employee>) as this = {} then
        this._repository <- repository
    
    [<DefaultValue>]
    val mutable private _repository : IRepository<Employee> 
     
    member this.Index() =
        this.View() :> ActionResult
        
    [<HttpPost>]
    member this.Index(entity : Employee) =
        // インターフェース経由でデータ更新
        this._repository.Add(entity)
        this._repository.Save()
        this.View(entity) :> ActionResult

◆ View

@model Sample.Entities.Employee

@{ Layout = null; }

<!DOCTYPE html>
<html>
<head>
    <title>F# + ASP.NET</title>
</head>
<body>
    <div>
        @using (Html.BeginForm()) {
            <fieldset>
                <legend>User</legend>
                <div class="editor-label">
                    @Html.LabelFor(model => model.ID)
                </div>
                <div class="editor-field">
                    @Html.EditorFor(model => model.ID)
                </div>
                <div class="editor-label">
                    @Html.LabelFor(model => model.Name)
                </div>
                <div class="editor-field">
                    @Html.EditorFor(model => model.Name)
                </div>
                <div class="editor-label">
                    @Html.LabelFor(model => model.Password)
                </div>
                <div class="editor-field">
                    @Html.EditorFor(model => model.Password)
                </div>
                <p>
                    <input type="submit" value="Create" />
                </p>
            </fieldset>
        }
    </div>
</body>
</html>

【F# + ASP.NET MVC】 結果フィルターによるキャッシュ機能を有効にするには

◆ Controller

namespace Sample.Controllers

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

type HomeController() =
    inherit Controller()
    
    // キャッシュ機能により、10秒ごとにしか更新されない
    [<OutputCache(Duration=10, VaryByParam="*")>]
    member this.Index() =
        // キャッシュ テスト
        this.ViewData.["Message"] <- System.DateTime.Now
        this.View() :> ActionResult

◆ View

@{ Layout = null; }

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
    <div>
        @ViewData["Message"]
    </div>
</body>
</html>

【F# + ASP.NET MVC】 アクション フィルターによるロギングを行うには

◆ アクション フィルター

namespace Sample.Attributes

open System
open System.Diagnostics
open System.Web.Mvc
open System.Web.Routing

type LogActionFilterAttribute() = 
    inherit ActionFilterAttribute()

    // ログ出力
    member private x.PutLog(methodName : string, routeData : RouteData) =
        let controllerName = routeData.Values.["controller"]
        let actionName = routeData.Values.["action"]
        let message = String.Format("{0} controller:{1} action:{2}"
            , methodName, controllerName, actionName)
        Trace.WriteLine(message, "アクション フィルター")

    override x.OnActionExecuting(filterContext) =
        x.PutLog("OnActionExecuting", filterContext.RouteData)

    override x.OnActionExecuted(filterContext) =
        x.PutLog("OnActionExecuted", filterContext.RouteData)

    override x.OnResultExecuting(filterContext) =
        x.PutLog("OnResultExecuting", filterContext.RouteData)

    override x.OnResultExecuted(filterContext) =
        x.PutLog("OnResultExecuted", filterContext.RouteData)

◆ Global.fs

namespace Sample.Routing

open System
open System.Web
open System.Web.Mvc
open System.Web.Routing
open System.Reflection

open Sample.Mef
open Sample.Controllers
open Sample.Attributes

type Route = { controller : string
               action : string
               id : UrlParameter }

type Global() =
    inherit System.Web.HttpApplication() 

    // フィルターを登録
    static member RegisterGlobalFilters(filters:GlobalFilterCollection) =
        // アクション フィルターをすべてのコントローラへ適用する
        filters.Add(new LogActionFilterAttribute())

    static member RegisterRoutes(routes:RouteCollection) =
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}")
        routes.MapRoute("Default", 
                        "{controller}/{action}/{id}", 
                        { controller = "Home"; action = "Index"
                          id = UrlParameter.Optional } )

    member this.Start() =
        AreaRegistration.RegisterAllAreas() 
        
        // フィルターを登録
        Global.RegisterGlobalFilters(GlobalFilters.Filters)
        Global.RegisterRoutes(RouteTable.Routes) |> ignore

◆ Controller

namespace Sample.Controllers

open System.Web
open System.Web.Mvc
open System.Diagnostics

open Sample.Attributes

type HomeController() =
    inherit Controller()
    
    member this.Index() =
        Trace.WriteLine("アクション実行")
        this.View() :> ActionResult

◆ 実行結果

アクション フィルター: OnActionExecuting controller:Home action:Index
アクション実行
アクション フィルター: OnActionExecuted controller:Home action:Index
アクション フィルター: OnResultExecuting controller:Home action:Index
アクション フィルター: OnResultExecuted controller:Home action:Index

【F# + ASP.NET MVC】 承認フィルターによるログイン認証を行うには

◆ ログイン認証プロバイダー

namespace Sample.Authorize

// #r "System.Web.ApplicationServices"
// #r "System.Configuration"
open System
open System.Web.Security

type CustomMembershipProvider() = 
    inherit MembershipProvider()

    override val Name = "customMembershipProvider" with get

    override x.ValidateUser(username : string, password : string) =
        // ユーザー名とパスワードが一致したら認証OKとする。
        username.Equals(password)

    override x.GetPassword(username, answer) =
        raise <| new NotImplementedException()
    
    // 省略(以下 override は、同様に NotImplementedException を実装
    // ・・・

◆ ロール プロバイダー

namespace Sample.Authorize

// #r "System.Web.ApplicationServices"
// #r "System.Configuration"
open System
open System.Web.Security

type CustomRoleProvider() = 
    inherit RoleProvider()

    override val Name = "customRoleProvider" with get

    override x.GetRolesForUser(username) =
        // ユーザー名が admin であれば管理者とする。
        if username.Equals("admin") then
            [|"Administrators"|]
        else
            [|"Gests"|]

    override x.GetUsersInRole(roleName) =
        raise <| new NotImplementedException()

    // 省略(以下 override は、同様に NotImplementedException を実装
    // ・・・

◆ Web.config

<?xml version="1.0"?>
<configuration>
  <!-- 省略 -->
  <system.web>
    <!-- フォーム認証(認証エラー時の遷移先) -->
    <authentication mode="Forms">
      <forms loginUrl="~/Login/Index" timeout="2880" />      
    </authentication>
    <!-- ログイン認証プロバイダー -->
    <membership defaultProvider="customMembershipProvider">
      <providers>
        <clear/>
        <add name="customMembershipProvider"
             type="Sample.Authorize.CustomMembershipProvider" />
      </providers>
    </membership>
    <!-- ロール プロバイダー -->
    <roleManager enabled="true" defaultProvider="customRoleProvider">
      <providers>
        <clear/>
        <add name="customRoleProvider"
             type="Sample.Authorize.CustomRoleProvider" />
      </providers>
    </roleManager>
  </system.web>
  <!-- 省略 -->
</configuration>

◆ Entity

namespace Sample.Entities

type Employee() = 
    member val Username = "" with get, set
    member val Password = "" with get, set

◆ ログイン用Controller

namespace Sample.Controllers

open System
open System.Web.Mvc
open System.Web.Security

open Sample.Entities
open Sample.Authorize

type LoginController() =
    inherit Controller()

    // ログイン認証プロバイダー
    member val Provider = new CustomMembershipProvider() with get, set
    
    member this.Index() =
        FormsAuthentication.SignOut()
        this.View() :> ActionResult

    [<HttpPost>]
    member this.Index(entity:Employee) =
        // ログイン認証
        if this.Provider.ValidateUser(entity.Username, entity.Password) then
            // 認証OK
            // 認証情報をURLへ保持
            FormsAuthentication.SetAuthCookie(entity.Username, false)
            // HOME画面へ遷移
            base.RedirectToAction("Index", "Home") :> ActionResult
        else
            // 認証NG
            this.ViewData.["Message"] <- "認証NG"             
            this.View(entity) :> ActionResult

◆ ログイン用View

@model Sample.Entities.Employee

@{ Layout = null; }

<!DOCTYPE html>
<html>
<head>
    <title>F# + ASP.NET</title>
</head>
<body>
    <div>
        @using (Html.BeginForm()) {
            <fieldset>
                <legend>User</legend>
                <div class="editor-label">
                    @Html.LabelFor(model => model.Username)
                </div>
                <div class="editor-field">
                    @Html.EditorFor(model => model.Username)
                </div>
                <div class="editor-label">
                    @Html.LabelFor(model => model.Password)
                </div>
                <div class="editor-field">
                    @Html.EditorFor(model => model.Password)
                </div>
                <p>
                    <input type="submit" value="Login" />
                </p>
            </fieldset>
        }
    </div>
    <div>
        @ViewData["Message"]
    </div>
</body>
</html>

◆承認フィルターを実装したController(HomeController)

namespace Sample.Controllers

open System.Web
open System.Web.Mvc

type HomeController() =
    inherit Controller()
    
    // 認証フィルター(ロールが administrators の場合のみアクション可能)
    [<Authorize(Roles = "administrators")>] 
    member this.Index() =
        this.ViewData.["Message"] <- "Hello World!"
        this.View() :> ActionResult

◆認証時に表示するView(HomeView)

@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
    <div>
        @ViewData["Message"]
    </div>
</body>
</html>