SI屋的(?)JavaScript(jQuery)活用方法

SI屋的なWebシステムの開発において、どこまでJavaScriptを使うのかについては、いくつかパターンがあると思うわけですが。

1. 機能要件では無いし、お金にならないのでいっさい使わないパターン(´д`;)
2. jQueryとdata属性なんかを使えば、宣言的にそれなりに装飾されたものをコストをかけずに作れるので、そのパターン(・ω・)
3. フルJavaScript…っというか、MVVMライブラリを使うだけで無く、GWTみたいなものを使わないとやる気が起きないパターン(・∀・;)

っで、未だに1のパターンも多かったりする一方で、jQueryの登場以降、使い方を考慮することで、SI屋的なシステムでもコストをかけずにUIをリッチにすることも可能になってきたので、その方法について書きたいと思います(`・ω・´)

そもそも

SI屋的なシステム開発における統制の観点から言うと、JavaScriptの記述は他のロジック記述に比べて個人差が出るので*1、担当者個々に処理を書かせたくないという話があるかと思います(´д`;)
また、各html(jsp、aspx)ページ内にJavaScriptの記述が混在すると、メンテナンス的に好ましくないという懸念もあるかと思います。


今回提唱する方法は、上記懸念に対する一定の回答を示しながら、JavaScriptを活用してUIをそれなりにリッチにする方法だと思います(・ω・)
その方法とは、簡単に言えば「data属性の活用と宣言的な記述」です。


なお、以降はjQuery前提での話となります。

data属性

data属性とはなんぞや?、っという人にはググって貰うとして(・∀・;)
要は、data属性を用いて、html内には処理(JavaScript)に必要なパラメータのみを記述することで、htmlからJavaScriptの完全分離をしようというのが今回提唱する案の肝になります。


簡単な例として言えば、

宣言的記述

動作を書くのでは無く、それが何をするものであり、必要なパラメータは○○であるということをに記述することで、処理(JavaScript)とhtmlを分離することが出来ます。
その場合、処理(JavaScript)の方はどう書くのかという話になりますが、まず、基本の考え方としては以下の様になります。

  • 特定のコンテキスト(id、class、タグの親子関係等)に対して適用する
  • プラグイン/ライブラリというレベルでの共通化とは別に、アプリケーション固有のコンテキストに適用する層を用意する

…っと言ってもよく分からない(´д`;)と思うので、前述の

処理パターン

コンテキスト適用層は処理毎に分離して、画面毎に必要なものをインクルードする形でメンテナンスしやすくすることが出来ます。
っということで、以下、処理パターン毎に分離する時の例をいくつか書いてみます。

非同期ロード(asyncload.js)

ポータル的な画面などで複数Widget的な要素を配置して、それを非同期にロードして見せたいような場合の処理の例です。

$(document).ready(function() {
    $(".asyncload").each(function(index, e) {
        var $this = $(e);
        $.ajax({
            type: "GET",
            url: $this.data("url"),
            context: $this,
            success: function(data, textStatus, xhr) {
                $(this).empty().append(data);
            },
            error: function() {
            },
            complete: function() {
            },
            dataType: "html"
        });
    });
});

[class=asyncload]のコンテキストに対して、data-urlから表示する先のURLを取得し、それのhtmlを非同期に取得して画面を更新する、っという処理です。
この場合の適用コンテキストの例としては、下記の様になります。

<div id="weatherWidget" class="asyncload" data-url="/widget/weather">
<img src="/images/loading.gif"/>
</div>

画面の実装者は上記の様なhtmlを記述するだけでよくなり、htmlとJavaScriptの分離、もとい、個々の画面担当者とJavaScript担当者という形での作業の分離が(SI屋的に)出来るようになります(・∀・;)

パーシャルアップデート(partial.js)

もう少し複雑なパターンとして、部分更新(ASP.NETにおけるUpdatePanel的なもの)の処理例を書いてみます。

$(document).ready(function() {
    var partialUpdate = function(e, name) {
        var form = e.parents("form");

        var url = e.data("url");
        if (!url) {
            url = form.attr("action");
        }

        var target = e.data("target");
        if (!target) {
            target = form.data("target");
        }

        var data = form.serializeArray();
        data.push({ name: "_partial", value: name });

        $.ajax({
            type: "POST",
            url: url,
            data: data,
            context: target,
            success: function(data, textStatus, xhr) {
                $(this).empty().append(data);
            },
            error: function() {
            },
            complete: function() {
            },
            dataType: "html"
        });
    };

    $("select.partial").live("change", function() {
        var $this = $(this);
        partialUpdate($this, $this.attr("name"));
    });

    $("input[type='button'].partial").live("click", function() {
        var $this = $(this);
        partialUpdate($this, $this.attr("name"));
    });

    $("a.partial").live("click", function() {
        var $this = $(this);
        var name = $this.attr("href")
        if (name.indexOf("#") == 0) {
            name = name.slice(1);
        }
        partialUpdate($this, name);
        return false;
    });
});

解説の前にhtmlも。

<div id="updatePanel">

<form action="/partial" method="POST" data-target="#updatePanel">

<input type="text" name="data1" value=""/>

<select name="data2" class="partial">
<option value="1">データ1</option>
<option value="2">データ2</option>
<option value="3">データ3</option>
</select>

<input type="button" name="trigger" value="トリガー" class="partial"/>

<a href="#link" class="partial">パーシャルリンク</a>

<input type="submit" value="更新"/>

</form>

</div>

この例は、[class=partial]なselect、button、リンクに対する操作に対して、formのaction先にAjaxでデータをPOSTする(応答では

内を返して貰う)ことにより、画面全体の更新無しで、同じ画面(from)の値を書き換えながら処理を行うという、ASP.NETにおけるUpdatePanel的な処理を実現するものです。
サーバ側のハンドラでは、_partialパラメータがdata2、trigger、link及び無しの何れかを判別することで、どの要素による部分更新なのか、あるいはsubmit処理なのかを判別できます。


画面担当者は、上記のルールに従って実装すれば、後は既存の作りと変わらずにAjax的なformを作れるようになる、っと言うわけです(・ω・)

まとめ

data属性と宣言的な記述により、htmlとJavaScriptの完全分離が出来るようになりました(・∀・)
また、このことによりJavaScriptの実装は少数のコンテキスト適用層を作る人に任せることが出来るようになりました。


っということで、SI屋的な開発でも、コストをあまりかけず、品質面での統制も問題無く、Ajax/JavaScriptを使ったそれなりにリッチなWebシステム開発が出来るようになりますね、っという話ですが、どうでしょう(・ω・)?

おまけ

自分の出自はWindowsプログラマ〜バックエンド/サーバ側なので、JavaScript自体それほど好きというわけでも無いですが、なんだかんだ言いつつ、去年はJSのコードを結構書いていたたり、本も読んだりしたので、これも時代でしょうかね(・ω・)?

コアjQuery+プラグイン/jQuery UI 開発実践技法 (Programmer’s SELECTION)

コアjQuery+プラグイン/jQuery UI 開発実践技法 (Programmer’s SELECTION)

ちなみにこれ、jQuery in Actionの邦訳ね。*2


っで、SI屋的なWebシステム開発におけるJavaScriptの課題としては、自分としては以下のようなものが残っていると思っています。

  • JavaScriptコードの静的テストツール、及びCIとの組み合わせ
  • jQueryはデフォとして、ユーティリティ、オブジェクトクエリ、バインディング*3、モバイル(スマフォ対応)、テストあたりは何かしらライブラリを使用することになると思うので、自分達なりのそのセットの評価とメンテ

*1:正しくは、Controller/ServiceやSQLのコピペバインダー的な作業よりも、UI制御の記述の方が頭を使う、という意味だと思いますが(・∀・;)

*2:余談だけど、元がin Actionシリーズの邦訳だっていうのが分かりにくい書籍が最近多いよね(・∀・;)

*3:単純にMVxパターンのJS版というだけでなく、サーバコードからの自動生成/自動連携的なものも欲しい、っというのは贅沢ですか(・∀・)?

JSPでお手軽マスターページ

穴埋め発掘ネタ。

Smart Web Layout

JSPでもASP.NETのマスターページのように、共通部分はテンプレートを使用してレイアウトしたいよね、っということで。
Javaの場合、この用途にはTilesやSitemeshがあるわけですが(・ω・)
でも、設定ファイルとか使いたくねーし、JSPだけで完結してーし( ゚д゚)、ッ…っというニーズのための、お手軽レイアウトライブラリを作ったので下記で公開。
https://github.com/usausa/Java-Smart-WebLayout

使い方

公開しているサンプルはTomcatプロジェクトになっているので、とりあえず動作確認はできると思いますが。
使用例として見るのは、個別ページ部分のindex.jspとテンプレートのmaster.jsp


まずindex.jspについて。

<layout:master template="master">

<layout:parameter key="title" value="ホーム"/>

<layout:content name="body">
ページ固有のコンテンツ
</layout:content>

</layout:master>

テンプレートで置換する部分をで囲み、テンプレート中で置き換えを行う部分はで記述します。
また、個別ページからテンプレートにパラメータとして渡したい項目はで指定します。


次にマスターページのmaster.jspはこんな感じで。

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">

<title><layout:value key="title"/></title>

</head>

<body id="body">

<h1><layout:value key="title"/></h1>

<div id="main">
<layout:place name="body"/>
</div>

</body>
</html>

個別ページのコンテンツで置換したい部分はプレースホルダを指定します。
で設定された項目については、を使用するとその内容が出力されます。
また、についてはvar属性を指定すると、その名称でページスコープの変数として設定されます。


っで、実際にindex.jspの処理が行われると、ページが合成されて出力は下記の様になりますだ(・ω・)

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">

<title>ホーム</title>

</head>

<body id="body">

<h1>ホーム</h1>

<div id="main">

ページ固有のコンテンツ

</div>

</body>
</html>

共通テンプレートを使ったレイアウトが出来ていますね(・∀・)

タグ一覧

タグの一覧を簡単に解説。

master

個別ページで使用。
このタグ部分がマスターページで置換される。
templateで属性マスターページとして使うJSPを指定。

content

個別ページで使用。
マスターページ内に合成されるコンテンツを記述。
name属性でマスターページにおけるプレースホルダの場所を指定。

parameter

個別ページで使用。
マスターページに引き渡すパラメータをkey属性とvalue属性で指定。

place

マスターページで使用。
個別ページで記述したcontentの内容が合成される部分のプレースホルダを指定。

value

マスターページで使用。
個別ページで指定したparameterの内容を使用。
key属性で指定した内容の出力、またはvar属性が指定された場合は変数への設定。

partial

どこでも使用。
Smart Web Layoutライブラリではweb.xml中で下記の様なパラメータを指定した場合、ビューの記述の省略が可能。

<context-param>
  <param-name>smart.web.layout.PREFIX</param-name>
  <param-value>/WEB-INF/views/</param-value>
</context-param>

<context-param>
  <param-name>smart.web.layout.SUFFIX</param-name>
  <param-value>.jsp</param-value>
</context-param>

この設定であれば、template="master"のような記述で実際に使用されるjspは/WEB-INF/views/master.jspとなる。
partialタグは上記に対応したinclude処理のSmart Web Layout版。



っというわけで、このレイアウトタグライブラリはお手軽&ピュアJSPなのでその他のライブラリと組み合わせ自由という感じなのですが、どうでしょう(・ω・)?

Spring MVCでグループヴァリデーションしたい時

JSR-303のjavax.validation.Validatorヴァリデーションのグループ化に対応していて、例えば同じモデルに対して新規作成時と更新時に違う検証を行う事が可能ですが(`・ω・´)
しかし、javax.validation.Validアノテーションにはグループ化の指定方法がないという…。
なので、Spring MVCを使ってControllerメソッドの引数に対してヴァリデーションを行うときに、グループの指定が出来ないという結果に(´・ω・`)


本家ではjavax.validation.Validとは別のアノテーションを用意してこの問題に対応するっぽいですが、とりあえずそれまでの対応方法について(・ω・)

基本方針

javax.validation.Validとは別にグループの指定ができるアノテーションを用意します。
次に、Spring MVCで使用する際のMethodInterceptorとヴァリデーション用のコンポーネントを用意します。

アノテーション

こんな感じでjavax.validation.Validにグループを追加してものを用意します。

@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
public @interface ValidGroups {

    Class<?>[] value();
}

ヴァリデーション用コンポーネント

javax.validation.Validatorを使ってヴァリデーションを行い、org.springframework.validation.Errorsを設定するコンポーネントとして以下のようなものを用意します。

import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Path;
import javax.validation.Validator;
import javax.validation.Path.Node;
import javax.validation.groups.Default;

import org.springframework.validation.Errors;

public class GroupValidator {

    private static final Class<?>[] DEFAULT_CLASSES = new Class<?>[] {Default.class};

    private Validator validator;

    public void setValidator(final Validator validator) {
        this.validator = validator;
    }

    public boolean isValid(final Errors result, final Object object, final Class<?>... classes) {
        Set<ConstraintViolation<Object>> violations = validator.validate(object, isDefaultValidation(classes) ? DEFAULT_CLASSES : classes);
        for (ConstraintViolation<Object> v : violations) {
            Path path = v.getPropertyPath();
            String propertyName = "";
            if (path != null) {
                StringBuilder buffer = new StringBuilder();
                for (Node n : path) {
                    buffer.append(n.getName());
                    buffer.append(".");
                }
                propertyName = buffer.toString();
                propertyName = propertyName.substring(0, propertyName.length() - 1);
            }
            String constraintName = v.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName();
            if (propertyName == null || "".equals(propertyName)) {
                result.reject(constraintName, v.getMessage());
            } else {
                result.rejectValue(propertyName, constraintName, v.getMessage());
            }
        }
        return violations.isEmpty();
    }

    private boolean isDefaultValidation(final Class<?>[] classes) {
        if ((classes == null) || (classes.length == 0) || (classes[0] == null)) {
            return true;
        }
        return false;
    }
}

ヴァリデーション用MethodInterceptor

Controllerのメソッドでjavax.validation.Validによるヴァリデーションと同じように書けるように、以下の様なMethodInterceptorを用意。

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.validation.Errors;

public class ValidGroupsInterceptor implements MethodInterceptor {

    private GroupValidator groupValidator;

    public void setGroupValidator(final GroupValidator groupValidator) {
        this.groupValidator = groupValidator;
    }

    @Override
    public Object invoke(final MethodInvocation invocation) throws Throwable {

        Method method = invocation.getMethod();
        Object[] arguments = invocation.getArguments();
        Class<?>[] parameterTypes = method.getParameterTypes();
        Annotation[][] annotations = method.getParameterAnnotations();

        for (int i = 0; i < arguments.length; i++) {
            if (annotations[i] == null) {
                continue;
            }
            for (Annotation annotation : annotations[i]) {
                if (!(annotation instanceof ValidGroups)) {
                    continue;
                }
                if ((i < arguments.length - 1) && (Errors.class.isAssignableFrom(parameterTypes[i + 1]))) {
                    groupValidator.isValid((Errors)arguments[i + 1], arguments[i], ((ValidGroups)annotation).value());
                }
            }
        }

        return invocation.proceed();
    }
}

設定

っで、Springの設定としてはこんな感じで、Controllerのメソッドに対してValidGroupsInterceptorを適用するように設定。

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
</bean>

<bean id="groupValidator" class="smart.extension.spring.validator.GroupValidator">
    <property name="validator" ref="validator" />
</bean>

<bean id="validGroupsInterceptor" class="smart.extension.spring.validator.ValidGroupsInterceptor">
    <property name="groupValidator" ref="groupValidator" />
</bean>

<aop:config proxy-target-class="true">
    <aop:advisor pointcut="execution(* sample..*Controller.*(..))" advice-ref="validGroupsInterceptor" />
</aop:config>

サンプル

使用例としては以下の様なモデルとController、jspになります。

public class MultiViewModel implements Serializable {

    private static final long serialVersionUID = 1L;

    public interface Rule1 {}

    public interface Rule2 {}

    @NotEmpty(groups = {Rule1.class})
    private String data1;

    @NotEmpty(groups = {Rule2.class})
    private String data2;

    public String getData1() {
        return data1;
    }

    public void setData1(final String data1) {
        this.data1 = data1;
    }

    public String getData2() {
        return data2;
    }

    public void setData2(final String data2) {
        this.data2 = data2;
    }
}
@Controller
@RequestMapping(value = "/sample/basic/multi")
public class MultiController extends SampleControllerBase {

    @RequestMapping(method = RequestMethod.GET)
    public String index(final ModelMap map) {
        map.put("model", new MultiViewModel());
        return "sample/basic/multi/index";
    }

    @RequestMapping(method = RequestMethod.POST, params = "rule1")
    public String rule1(@ModelAttribute("model") @ValidGroups(MultiViewModel.Rule1.class) final MultiViewModel model,
            final BindingResult binding, final ModelMap map) {
        return "sample/basic/multi/index";
    }

    @RequestMapping(method = RequestMethod.POST, params = "rule2")
    public String rule2(@ModelAttribute("model") @ValidGroups(MultiViewModel.Rule2.class) final MultiViewModel model,
            final BindingResult binding, final ModelMap map) {
        return "sample/basic/multi/index";
    }
}
<form:form method="post" modelAttribute="model">

<table>

<tr>
<th>data1</th>
<td>
<form:input path="data1"/>
<form:errors path="data1" cssClass="error" />
</td>
</tr>

<tr>
<th>data2</th>
<td>
<form:input path="data2"/>
<form:errors path="data2" cssClass="error" />
</td>
</tr>

</table>

<input type="submit" name="rule1" value="ルール1で更新"/>
<input type="submit" name="rule2" value="ルール2で更新"/>

</form:form>

これで、画面の[ルール1で更新]ボタンを押下するとdata1に対するチェックのみが、[ルール2で更新]ボタンを押下するとdata2に対するチェックのみが実行されることを確認できます(・∀・)


…などと、書きかけのネタを発掘したのでリサイクル投稿(・ω・;)
Spring MVCも、3.1になると色々改善されるっぽいんだけどね〜。

Spring MVCというか、JSR-303でメソッドを使用したヴァリデーション

メンバaの値が条件Aでメンバbの値が条件Bだったら、メンバcの値は条件Cの時のみ許可する、みたいな複雑な条件を行う時の話について。
メソッドで判定処理を書けば簡単に済む場合の、メソッドを使ったヴァリデーターの作成方法。

実装

アノテーションと実装はこんな感じのものを用意。

@Target({ METHOD, FIELD, TYPE, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Constraint(validatedBy = ValidationMethodValidator.class)
@Documented
public @interface ValidationMethod {

    String message() default "{smart.extension.spring.validator.ValidationMethod.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    String method();

    String target() default "";

    @Target({ METHOD, FIELD, TYPE, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
    @Retention(RUNTIME)
    @Documented
    @interface List {

        ValidationMethod[] value();
    }
}
public class ValidationMethodValidator implements ConstraintValidator<ValidationMethod, Object> {

    private String method;

    private String target;

    @Override
    public void initialize(final ValidationMethod constraintAnnotation) {
        method = constraintAnnotation.method();
        target = constraintAnnotation.target();
    }

    @Override
    public boolean isValid(final Object value, final ConstraintValidatorContext context) {
        boolean isValid = false;
        if (value != null) {
            try {
                Method m = value.getClass().getMethod(method);
                isValid = (Boolean)m.invoke(value);
            } catch (Exception e) {
                throw new WrapRuntimeException(e);
            }
        }

        if (!isValid) {
            context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).addNode(target).addConstraintViolation()
                    .disableDefaultConstraintViolation();
        }

        return isValid;
    }
}

サンプル

typeが1か2だったらdataは空以外を、typeが1か2以外だったらdataは空を要求するような場合の記述はこんな感じで。

@ValidationMethod(method = "validateData", target = "data")
public static class Data {

    private int type;

    private String data;

...

    public boolean validateData() {
        if ((type == 1) || (type == 2)) {
            return !StringUtil.isEmpty(data);
        } else {
            return StringUtil.isEmpty(data);
        }
    }
}

targetは、前回のFieldCompareValidatorの話に同じでどのメンバのエラーにするかの設定だす(・ω・)

Spring MVCというか、JSR-303で値の比較を行うヴァリデーション

ユーザ登録における、[パスワード]と[パスワードの再確認]欄の一致とかに使うやつを自分で用意する方法について(・ω・)

実装

アノテーションと実装はこんな感じのものを用意。

@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = FieldCompareValidator.class)
@Documented
public @interface FieldCompare {

    String message() default "{smart.extension.spring.validator.FieldCompare.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    String field();

    String compareTo();

    @Target({ TYPE, ANNOTATION_TYPE })
    @Retention(RUNTIME)
    @Documented
    @interface List {

        FieldCompare[] value();
    }
}
public class FieldCompareValidator implements ConstraintValidator<FieldCompare, Object> {

    private String fieldFieldName;

    private String compareToFieldName;

    @Override
    public void initialize(final FieldCompare constraintAnnotation) {
        this.fieldFieldName = constraintAnnotation.field();
        this.compareToFieldName = constraintAnnotation.compareTo();
    }

    @Override
    public boolean isValid(final Object value, final ConstraintValidatorContext context) {
        final BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(value);
        final Object fieldValue = wrapper.getPropertyValue(this.fieldFieldName);
        final Object compareToValue = wrapper.getPropertyValue(this.compareToFieldName);
        boolean isValid = ((fieldValue == null) && (compareToValue == null)) || ((fieldValue != null) && (fieldValue.equals(compareToValue)));

        if (!isValid) {
            context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).addNode(this.fieldFieldName).addConstraintViolation()
                    .disableDefaultConstraintViolation();
        }

        return isValid;
    }
}

サンプル

例えば検証対象のモデルに下記の様な感じで設定。

@FieldCompare(field = "newPasswordConfirm", compareTo = "newPassword")
public class PasswordViewModel implements Serializable {

    @NotEmpty
    private String newPassword;

    private String newPasswordConfirm;

...
}
<ul>
<li>
<form:password path="newPassword"/>
<form:errors path="newPassword" cssClass="error" />
</li>
<li>
<form:password path="newPasswordConfirm"/>
<form:errors path="newPasswordConfirm" cssClass="error" />
</li>
</ul>

ポイントは、入力欄のエラーとして扱う方のメンバをfieldで指定すること。
この場合、パスワードが一致しなかった時のエラーはnewPasswordConfirmの方に表示されます。
FieldCompareValidatorのcontext.buildConstraintViolationWithTemplate()のあたりでやっているのがこの設定です(・ω・)


これ、どっかのサイトを参考にしたものですが、出典は忘れました(・∀・;)

CentOS上でMonoを使ってRazorを動かす

最近はCentOS(RHEL)でもMonoのリポジトリを設定すれば、yumで最新版をインスコできるとか、楽に環境設定できるようになっているので、その手順メモ(・∀・)
とりあえずCentOSが用意できているところからはじめます。*1

Monoのインストール

まずはMonoのリポジトリを追加(・ω・)

> vi /etc/yum.repos.d/mono.repo

内容は下記で。

[mono]
name = novell-mono
baseurl=http://ftp.novell.com/pub/mono/download-stable/RHEL_5/
enabled=0
gpgcheck=0

ついでMonoのインスコですが、とりあえずこのあたりを入れておけば事足りる(・ω・)?

yum --enablerepo=mono install mono-addon-core mono-addon-data mono-addon-web mono-addon-devel mono-addon-winforms mono-addon-wcf mono-addon-libgdiplus0 mono-addon-extras mod_mono-addon

これだけでインスコ完了、簡単ですね(・∀・)


バージョンの確認しておきましょうか。

> /opt/novell/mono/bin/mono -V
Mono JIT compiler version 2.10.2 (tarball Mon Apr 18 18:57:39 UTC 2011)
Copyright (C) 2002-2011 Novell, Inc and Contributors. www.mono-project.com
        TLS:           __thread
        SIGSEGV:       altstack
        Notifications: epoll
        Architecture:  x86
        Disabled:      none
        Misc:          debugger softdebug
        LLVM:          supported, not enabled.
        GC:            Included Boehm (with typed GC and Parallel Mark)

RazorなWebアプリを動かす

まず、アプリケーション用のディレクトリを作成(・ω・)

> mkdir /var/www/sample

ついでApacheの設定ですが、mod_monoの設定自体は/etc/httpd/conf.d/mod_mono.confに設定済みになっているので、アプリケーションの設定だけを追加します。

> vi /etc/httpd/conf/httpd.conf

最後に下記の内容を追加。

Alias /sample/ "/var/www/sample"

AddMonoApplications sample "/sample:/var/www/sample"

MonoServerPath sample "/opt/novell/mono/bin//mod-mono-server4"
MonoSetEnv sample MONO_IOMAP=all

<Location "/sample">
    MonoSetServerAlias sample
    SetHandler mono
</Location>

環境設定はこんなんでOK。


次はアプリケーションですが、これはVisual Studio 2010で作成することにして。
ASP.NET MVC 3 Web アプリケーションを作成して、ビューエンジンにRazorを選択。

参照設定に、いかのうち足りないものを追加し、プロパティでローカルコピーをTrueに設定します。

  • System.Web.Helpers.dll
  • System.Web.Mvc.dll
  • System.Web.Razor.dll
  • System.Web.WebPages.Deployment.dll
  • System.Web.WebPages.dll
  • System.Web.WebPages.Razor.dll

あとは、プロジェクトをビルドしたものをCentOS上の/var/www/sampleにデプロイすれば動作確認できますよヽ(・∀・)ノ


ヘッダも確認してみるとこんな感じになっていて。

HTTP/1.1 200 OK
Date: Tue, 03 May 2011 08:17:02 GMT
Server: Apache/2.2.3 (CentOS)
X-AspNetMvc-Version: 3.0
X-AspNet-Version: 4.0.30319
Content-Length: 1215
Cache-Control: private
X-Powered-By: Mono
Connection: close
Content-Type: text/html; charset=utf-8

ApacheとMonoで動作していることが確認できました。


しかし、これだけ簡単に環境設定できるようになってくると、なにか実戦投入したくなりますね(・∀・;)

*1:自分は5.6 x64上で確認しました。

formに複数buttonを配置するときの話、IE6への対処編

こんな風に、formに複数のbuttonを配置したい時があり(・ω・)

<button type="submit" name="update">更新</button>
<button type="submit" name="back">戻る</button>

この場合、どちらの処理を行うか切り分けるときには、name属性の値を使いますが。
例えば、Spring MVCであればRequestMappingのparamsに値を設定して。

@RequestMapping(method = RequestMethod.POST, params = "back")
public String back(...) {
...
}

ASP.NET MVCであれば、ActionMethodSelectorAttribute派生を作って対処しますが。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class ExistParamaterAttribute : ActionMethodSelectorAttribute
{
    public string Name { get; set; }

    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
    {
        return controllerContext.RequestContext.HttpContext.Request.Form.GetValues( Name ) != null;
    }
}

しかし、IE6では両方のname属性を送ってきやがるという問題があって(#゚Д゚)
なら大丈夫なんですが、デザイン的に