ラベル ASP.NET MVC の投稿を表示しています。 すべての投稿を表示
ラベル ASP.NET MVC の投稿を表示しています。 すべての投稿を表示

2011年3月23日水曜日

ASP.NET MVCでCaptcha(キャプチャ)を使う

My Clipでもキャプチャを実装していたのだが、今回チヌかかり釣りMEGAでも同様のことをやりたかったので、昔のソースコードを引っ張り出してきて再確認したので、それをまとめておく。

使用しているのは下記ブログポストで紹介されているライブラリ。
ASP.NET MVC CAPTCHA

※最新のソースコードは下記ブログポストらしいのだが、ダウンロードしたバイナリをMVC 3の環境に組み込んでも参照しているアセンブリバージョンの兼ね合いでコンパイルができないので、今回はMy Clip実装時の上記ブログポストのソースコードで実装方法を紹介する。
MVC CAPTCHA for Preview Release 3

  1. 上記のリンクからソースコードをダウンロードし、ManagedFusion.Web のプロジェクトをコンパイルする
  2. コンパイルしてできた ManagedFusion.dll と ManagedFusion.Web.dll を ASP.NET MVC 3 のプロジェクトで参照する

3, 下記を web.config に追加する
<httpHandlers>
 <add verb="GET" path="captcha.ashx" validate="false" type="ManagedFusion.Web.Mvc.Handlers.CaptchaImageHandler, ManagedFusion.Web"/>
</httpHandlers>

4, 下記を Global.asax に追加する
public static void RegisterRoutes(RouteCollection routes)
{
 routes.IgnoreRoute("{handler}.ashx");
}

5, cshtml に下記を追加する
<div>
    @Html.Raw(Html.CaptchaImage(50, 180))
</div>
<div>
    @Html.Raw(Html.CaptchaTextBox("captcha"))
</div>
Html.Raw で囲んでいるのは CaptchaImage, CaptchaTextBox ともに文字列を返却するようになっているため。ソースコードを書き換えて各関数ともに文字列ではなく MvcHtmlString を返却するようにすれば Raw で囲む必要はなくなる。

6, コントローラーアクションに下記属性を追加する
 [CaptchaValidation("captcha")]
 [HttpPost]
 public ActionResult Register(RegisterModel model, bool captchaValid)
 {
   // Do something
 }
captchaValid にキャプチャでの検証結果が代入されている。

以上で5でコードを追加した cshtml を表示するばキャプチャが表示されているはずだ。

2010年3月30日火曜日

ASP.NET MVCでELMAHを使う

エラーのログをどうするか、というのはアプリケーションの永遠の課題だが、ASP.NETには優れたソリューションがある。それがELMAHだ。ELMAHはError Logging Modules and Handlersの略称だ。公式サイトはこちら

で、Elmahを使うと何ができるの?というのは下図をみてもらえればわかると思う。





ELMAHを設定した後にhttp://localhost:****/elmah.axdへアクセスすると上図のページが参照できる。一つ目はELMAHにハンドルされたエラーの一覧だ。二つ目の画像はそのエラーリストのうちの詳細情報になる。そう、ELMAHはASP.NET開発者が開発中に良く見かける黄色いエラーページ(いわゆる「Yellow screen of death」だ)の内容をそのまま保持管理してくれるのだ。

他にも、エラーが発生したことを通知してくれるメール機能やエラーの種類をフィルターする機能もある。また、エラーログの保存先として、SQL Serverはもちろん、Oracle, SQLite, Microsoft Access, VistaDB, XML, RAM(メモリ上)が選択できる。

さらに嬉しいことに導入はいたって簡単。Web.configにぺぺっといくつかの項目を記述して、ELMAHのDLLをbinへコピーするだけで(最もシンプルな構成にした場合は)すぐに動作する。

それではここから導入方法を解説する。今回はログのバックエンドとしてSQL Server 2008, サーバはIIS7, メール機能の有効化を行う。
(注意!これはASP.NET MVC用の導入手順なのでASP.NETと微妙に違います)

  1. まず前述の公式サイトからELMAHの最新版をダウンロードしElmah.dllを導入したいアプリケーションのbinフォルダへコピーする。
  2. 続いてweb.configを開き(下方にあるsample.configを参照) sample.configの内容をコピーする。
  3. このDTDをELMAHのログをインストールしたいDatabase上で実行する(実行時に互換性がうんたらかんたらとメッセージがでるが無視して構わない)。


-sample.config-(必要な箇所以外は省略してあります)
<configuration>
 <configSections>
  <sectionGroup name="elmah">
   <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah"/>
   <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah"/>
 </sectionGroup>
</configSections>

<elmah>
 <errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="ApplicationServices" />
  <errorMail from="***"
   to="***"
   subject="***"
   async="false"
   smtpPort="***"
   smtpServer="***"
   userName="***"
   password="***" />
</elmah>
<connectionStrings>
 <add name="ApplicationServices" connectionString="***"/>
</connectionStrings>
<system.web>
<httpHandlers>
 <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah"/>
</httpHandlers>
<httpModules>
 <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah"/>
 <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
 <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah"/>
</httpModules>
</system.web>
<system.webServer>
 <handlers>
 <add name="Elmah" verb="POST,GET,HEAD" path="elmah.axd" preCondition="integratedMode" type="Elmah.ErrorLogPageFactory, Elmah"/>
 </handlers>
 <modules runAllManagedModulesForAllRequests="true">
  <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah"/>
  <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
  <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah"/>
 </modules>
</system.webServer>
</configuration>


ここまでできたらhttp://localhost:****/elmah.axdへアクセスしてみよう。一つ目の画像の画面が表示され、「No errors found.」と表示されていたら成功だ。さらに何か適当なURLをたたくなどしてエラーを起こしてから、elmah.axdをリフレッシュし、それがログされているのを確認しよう。

また、HandleErrorAttributeと一緒にELMAHを使いたい場合はこちらのページを見てもらいたい。開発者であるAtif Aziz氏みずから解決策をコメントしてくれている。

今回解説していない機能の詳細についてはこちらを参照して欲しい。MSDNにもELMAHの解説が日本語であるのでそちらを参照するのも良いだろう。

2010年3月16日火曜日

複数選択を行える便利なjQueryのリストプラグイン ~ multiselect

複数の項目を選択できる便利なjQueryのプラグインを以前使ったので、ASP.NET MVC上でどのように実装するかを解説する。実際に動作しているものは下図の通り。


公式サイトはこちらなのだが、今回は機能がより豊富なYanick Rochon氏作成のこちら(リンク死亡確認 by 王大人:2011/4/23。対応リンクは下記参照のこと)のバージョンを使用する。Referenceも前述のサイトにあるので必要に応じて参照して欲しい。

更新:2011/4/23
こちらのGitからAjax検索対応のMultiselect pluginが取得できる。対応の説明サイトはこちら
Ajax未対応のMultiselect pluginはこちらから取得できる。
新しく解説したのが下記。
複数選択を行える便利なjQueryのリストプラグイン その2
更新ここまで

あわせて下記のライブラリも必要なのでそれぞれ取得して欲しい。
jquery-ui
jquery.tmpl
jquery.blockUI

aspxの実装は下記の通り(省略してあります)。

<link href="../../Content/Multiselect/ui.multiselect.css" rel="stylesheet" type="text/css" />
<script src="../../Scripts/Multiselect/jquery-ui-1.7.1.custom.min.js" type="text/javascript"></script>
<script src="../../Scripts/Multiselect/jquery.blockUI.js" type="text/javascript"></script>
<script src="../../Scripts/Multiselect/jquery.tmpl.1.1.1.js" type="text/javascript"></script>
<script src="../../Scripts/Multiselect/ui.multiselect.js" type="text/javascript"></script>

<script type="text/javascript">
$(document).ready(function() {
    $("#clubs").multiselect({ remoteUrl: '/Admin/GetClubs/' });
});
</script>

<select id="clubs" class="multiselectForClubs" multiple="multiple" name="clubs">
</select>

画像を参照してもらえば分かると思うが、プラグイン右上に検索用のテキストボックスがある。今回は何か入力された際にサーバサイドで検索を行いたいのでmultiselectのオプションにremoteUrlを設定している。remoteUrlを指定しない場合はクライアント上でのフィルタリングが有効になる。サーバサイドで呼び出されるのはremoteUrlで指定したAdminControllerのGetClubsメソッドだ。

AdminControllerの実装は下記の通り。

public ContentResult GetClubs(string q){
 var clubs = _service.Clubs.Where( Logic is here ).ToArray();
 var results = clubs.Length > 0 ? string.Join("\n", clubs) : string.Empty;
 return Content(results);
}

検索テキストボックスに入力された内容は引数qとして取得できる。初期状態でのremoteUrlの戻り値はValue=Textで一行ごとにひとつのoption要素を想定しているので、それに当てはまるように整形している。独自のデータ構成で返却したい場合はdataParserオプションを使用すれば可能だ。今回は文字列をそのまま返却するためにContentResultを使用している。

2009年11月18日水曜日

IModelBinderの話

今回はカスタムModelBinderを解説する。ModelBinderとはリクエストパラメータとControllerのActionの引数になっている型を見て、リクエストパラメータがその引数に変換可能であれば値を変換・設定してくれるという何とも素晴らしい機能のことだ。

ModelBinderの動作は下記のようになる。

-Model-
class Clip
{
 public string Description { get; set; }
}

-View-
<%Html.TextBox("Description")%>

-Controller-
pubilc ActionResult AddClip(Clip clip)
{
 var description = clip.Description;   // You can directly retrieve Description here!!
 // code here...
}

上記の例でModelBinderは次の処理を行っている。Controller.AddClipの引数にClipクラスがあり、そのクラスのプロパティにDescriptionがあるのでリクエストパラメータのDescriptionという名前の値をClip.Descriptionに代入している。

と、このようにModelBinderは値の変換、代入という煩雑な処理を一手に引き受けてくれる大変便利でシンプルな構造になっているのが理解できたかと思う。しかし、Viewが複雑になってくると独自にバインド処理を記述したくなる場面がでてくる。その方法をここから解説しよう。

ここではViewから配列情報を格納したJsonを文字列として受け取り、そのJsonをListクラスに変換・代入するという男気あふれる処理を行う。

-Model.cs-
[ModelBinder(typeof(PathModelBinder))]
public class PathModel
{
 public string Title { get; set; }
 public List<PathItemModel> Paths { get; set; }
 public string PathsJson { get; set; }

 public void SetModelValue(ModelStateDictionary modelState)
 {
  modelState.SetModelValue("Title", new ValueProviderResult(this.Title, this.Title, null));
  modelState.SetModelValue("PathsJson", new ValueProviderResult(this.PathsJson, this.PathsJson, null));
 }
}

public class PathModelBinder : IModelBinder
{
 public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
 {            
  var list = JsonConvert.DeserializeObject<List<PathItemModel>>(bindingContext.ValueProvider["PathsJson"].AttemptedValue);
  var model = new PathModel()
  {
   Paths = list,
   Title = bindingContext.ValueProvider["Title"].AttemptedValue,
   Description = bindingContext.ValueProvider["Description"].AttemptedValue,
   PathsJson = bindingContext.ValueProvider["PathsJson"].AttemptedValue
  };
  return model;
 }
}

-Controller.cs-
public ActionResult AddPath(PathModel path)
{
  if (!_service.AddPath(path))
  {
      path.SetModelValue(this.ModelState);
      return View("AddPath", path);
  }
  return RedirectToAction("Index");
}

上記の例では、Controller.AddPathは引数としてPathModelを受け取る。このPathModelはModelBinder属性が指定してあり、その指定でバインド処理をPathModelBinderで行うと明示してある。そのPathModelにはList<PathItemModel>というプロパティがあるのだが、その値はポストされたPathsJsonを解析して生成されるものなのでその処理をPathModelBinderで行っている。実際の処理はPathModelBinderのBindModelを見てもらえば一目瞭然なので省略する。

以上がカスタムModelBinderを使用するために必要な実装だ。どうだろう、かなりシンプルにできているのが実感できたと思う。これでController内に「値の変換を行う」という処理がなくなり、Controller本来の役目であるフローの制御に集中できるようなった。より関心の分離が進んだ状態というわけだ。

最後に一点注意が必要なのがPathModelのようにカスタムModelBinderを使用しているModelをViewへ渡す場合だ。その場合は、PathModelのSetModelValueで行っているようにModelStateへ値を設定してやらないとViewで値の取得ができないので忘れずに実装してほしい。

2009年10月23日金曜日

ViewからControllerへ配列を渡す方法

前回はViewから任意のClassを渡す方法を解説したが、今回はViewからControllerへ配列を渡す方法を解説する。これは下記の図のように、複数の選択項目があり、選択された内容を配列として取得したい場合にかなり有効だ。



実際のコードを見てみよう(簡易化しているのでこのままでは動かない恐れがあるので適時修正してもらいたい)。

-Import.aspx-
<%foreach( var contact in Model.ContactList)
{%>
 <input type='checkbox' name='contacts' value='<%=contact.Id%>'/> <%=contact.Name%>
<%}%>

-ImportController.cs-
public ActionResult Import(int[] contacts)
{
 // code here...
}

ポイントはcheckboxのname属性の値を固定にしていることだ。これで実際のHtmlにはContactListのアイテム分だけname='contacts'というcheckboxが描画される。そしてFormのポスト時にASP.NET MVCフレームワークが選択されたcontactsのvalueをひとまとめにしてImportControllerのImportActionの引数int[]として設定してくれる。

2009年10月20日火曜日

ViewからControllerへ任意のclassを渡す方法

前回Viewに任意のclassを渡す方法を解説したが、今回はViewからControllerへ値を渡す際に任意のclassを渡す方法を解説する。

といってもASP.NET MVCフレームワークが提供するModelBinderのおかげで実際のコードは至極簡単だ。

-HomeController.cs-
[AcceptVerbs(Http.Post)]
public ActionResult Index(Clip clip)
{
 // code here...
}

ControllerのActionの引数をclassにしておくだけで、フレームワークがReflectionなどを駆使してポストされた値とActionの引数classのパラメータ名を比較してデータを格納していってくれる。

さらに、値を格納してくれるついでにclassのプロパティとポストされた内容を比較し、ポストされた内容に明らかな間違い(intプロパティに文字列が入力されていたり)や不足分があるようだとModelStateにErrorとして追加してくれるわけだ。これはかなりの親切設計なので大体は役に立つのだが、たまに邪魔になるときがある。そんなときのために、値の格納・検証を行わないようActionの引数classへ指定する方法がある。それが下記の属性だ。

-HomeController.cs-
[AcceptVerbs(Http.Post)]
public ActionResult Index([Bind(Exclude = "Id,Position")]Clip clip)
{
 // code here...
}

Clipクラスに指定されているBind属性を見て欲しい。上記の例は、Clip.IdとClip.Positionの二つのプロパティの格納・検証を無視するように指定している。これでポスト内容にIdとPositionがなくともModelStateにErrorが追加されることはなくなった。

2009年10月18日日曜日

Viewに任意のClassを渡す方法

今回はASP.NET MVCのViewへ任意のClassを渡す方法を解説する。

初期のASP.NET MVCプロジェクトに配置されている/Views/Home/Index.aspxを見てみよう。Index.aspxの初期の基本クラスはSystem.Web.Mvc.ViewPageとなる。このままでもViewDataを使用すればデータをControllerからViewへ渡すことは可能だ。

-HomeController.cs-
public ActionResult Index()
{
 ViewData["Message"] = "Hello";
 return View("Index");
}

-Index.aspx-
<%=(string)ViewData["Message"]%>

しかしこれではC#の原則である強固に型付けされたプログラミングが行えない。そのためデータを渡すたびになんらかのキャストが必要になってしまう。

なので基本クラスにGenericを使用し任意のClassを渡すように変更する。Index.aspxの冒頭の一行は下記のようになる。

-Index.aspx-
<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MyClip.Models.ClipModel>" %>

これでMyClip.Models.ClipModelをIndex.aspx上で参照することができるので、下記のようなプログラミングができる。

-HomeController.cs-
public ActionResult Index()
{
 var clip = new ClipModel(){ Name="MyClip.net" };
 return View("Index", clip);
}

-Index.aspx-
<%=Model.Name%>

これで型付けされたプログラミングが行える。C#はJavaScriptとは違い強固に型付けされたプログラミングがフレームワークの根底にあるのでModelを使用するほうが好ましいだろう。また、ViewDataの値を参照するたびにキャストが必要なプログラミングをするよりも型付けされたModelを参照するほうが可読性も保守性も高まる。

2009年10月13日火曜日

カスタムActionFilter その2

前回は認証を必要とするActionFilterを解説したが、今回はロールに準拠した動作をするカスタムActionFilterを解説する。

例として求人サイトを挙げる。求人サイトには求職者=Candidateと雇用者=Employerの2種類のユーザーロールが存在するものとする。そして、両者に共通の複数のページ(例:ホームページ、会社情報ページなど)があるものとし、それらのMasterPageをそれぞれのロールに準拠したものに変更したい場合の処理を下記に実装する。

-RoleBaseMasterPageAttribute.cs-
public class RoleBaseMasterPageAttribute : ActionFilterAttribute
{
public RoleBaseMasterPageAttribute()
  : base()
{
   EmployerMasterPage = "~/Views/Shared/Employer.Master";
   CandidateMasterPage = "~/Views/Shared/Candidate.Master";
   AnonymousMasterPage = "~/Views/Shared/Site.Master";
}

public string EmployerMasterPage { get; set; }
public string CandidateMasterPage { get; set; }
public string AnonymousMasterPage { get; set; }

public override void OnResultExecuting(ResultExecutingContext filterContext)
{
   ViewResult viewResult = filterContext.Result as ViewResult;
   if (viewResult == null)
     return;

   if (!filterContext.HttpContext.Request.IsAuthenticated)
  {
     viewResult.MasterName = AnonymousMasterPage;
  }
   else if (filterContext.HttpContext.User.IsInRole("Candidate"))
  {
     viewResult.MasterName = CandidateMasterPage;
  }
   else
  {
     viewResult.MasterName = EmployerMasterPage;
  }
}
}

これをActionまたはController全体に付与すれば、ログインしているユーザーのロールに準拠したマスターページを表示することができる。

2009年10月12日月曜日

カスタムActionFilter その1

ASP.NET MVCにはActionFilterという属性がある。これはController全体かまたは個々のActionに対して設定できるようになっている属性で、出力内容を指定の期間キャッシュするOutputCacheAttributeなどがある。今回はこれを使って便利なActionFilterを作ってみようと思う。

ASP.NET MVCのフレームワークにはすでにAuthorizeAttributeという認証していないユーザーのリクエストは無視するActionFilterがある。これはこれで役に立つ機能なのだが、リクエストをまったく無視されてもユーザーフレンドリーとは呼べないので、もっと便利に、認証していないユーザーのリクエストはログインを促すように変更したいと思う。

-RequiredAuthAttribute.cs-
[AttributeUsage(AttributeTargets.All, 
  AllowMultiple = true)]
public class RequiredAuthAttribute : ActionFilterAttribute
{
  public string Route{ get; set; }
  public string Action { get; set; }

public RequiredAuthAttribute()
{
  Route = "Account";
  Action = "Login";
}

private string RedirectUrl()
{
  if (Action.CompareTo("") != 0)
    return "/" + Route + "/" + Action;
  return "/" + Route;
}

public override void OnActionExecuting(
  ActionExecutingContext filterContext)
{
  if (filterContext == null)
  {
    throw new ArgumentNullException(
  "filterContext is null in the RequiredAuth attribute");
  }

  if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
  {
    string loginUrl = RedirectUrl();
    filterContext.HttpContext
     .Response.Redirect(loginUrl, true);
  }
}
}

ActionFilterAttributeを継承したRequiredAuthAttributeクラスのOnActionExecutingで、認証されていないリクエストの場合はリクエスト先のURLを変更している。ここでは/Account/Login/に転送している。

実際の使用方法は下記の通りだ。

-ClipController.cs-
[RequiredAuth]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Comment(int clipId, string commentText)
{
  // code here...
}

上記の例はCommentというActionに対するFilterになる。ActionFilterはController全体に対しても付与できる。

以上で既存のコード部分に手を加えるでなく、簡単に認証が必要なActionを実装することができた。このほかにもRoleベースのフィルタリングなども可能なので、ネタが続くだけ紹介していきたいと思う。

2009年10月9日金曜日

Routingの話 その3

以前カスタムRouteの話をしたけれど、今回はカスタムRoute用のURLを出力する方法を解説する。

ASP.NET MVCのフレームワークとしてあるアクション用のURLを出力するのにSystem.Web.Mvc.Html.ActionLink()メソッドが用意されている。しかし、複数のパラメータを渡す場合のカスタムRouteのためにHtml.ActionLink()メソッドでURLを生成すると、下記のような場合は予期せぬURLを出力する。
(実際にはActionLikでも期待したとおりのURLを出力できました:追記コメント)
(RouteLinkの紹介用にこのエントリは残しておきます:追記コメント)

Html.ActionLink("Next", 
  "Category", "Clip", 
  new { nameKey="futsal", time = "week", page="2" }, null)

ここではaのようなURLを期待しているのだが、実際にはbのURLが出力される。


a. http://www.my-clip.net/Clip/Category/futsal/week/2

b. http://www.my-clip.net/Clip/Category?nameKey=futsal&time=week&page=2


これでは期待したアクションを呼び出せないのでASP.NET MVCのソースコードを解析してみたところ、どうやらActionLinkメソッドに2つか3つ以上の引数を渡すと上記bのような形でしか出力してくれないようだ。そこで変わりにHtml.RouteLink()メソッドを使用する。記述方法は下記の通りだ。

Html.RouteLink("Next", 
  "CategoryWithPage", 
  new { nameKey = "futsal", time = "week", page = "2" })

ちなみに第二引数はGlobal.asaxでMapRouteメソッドで指定した名称を設定する。これで期待したURLが出力される。

ASP.NET MVC Ver1.0のソースコードはここからダウンロードできる。

-追記(2009-11-01)-
こちら等閑に付さないようにさんがこのエントリの間違いを指摘してくださっていたので間違いを修正。

2009年10月8日木曜日

jQueryとASP.NET MVCで非同期通信 その2

前回は非同期通信のサーバサイドをControllerクラスとしたけれど、今回はWeb Serviceをサーバサイドとして場合を解説する。

まずコメントを入力するtextareaと保存ボタンを用意する。

-details.aspx-
<div>
  <div>
    <textarea id="commentArea"></textarea>
  </div>
  <input id="saveButton" type="button" value="Save"/>
</div>

ついで、JavaScriptでsaveButtonのClickイベントをハンドルし、jQueryのajaxメソッドでサーバサイドと非同期通信を行う。ここまでは前回同様だが今回は接続先のurlが異なる。

-JavaScript-
$(document).ready(function() {
 $('#saveButton').click(function(){
  try {
   $.ajax({
              type: "POST",
              url: "http://www.my-clip.net/WebServices/Clip.asmx/Comment",
              data: { clipId: _clipId, commentText: $('#commentArea').val() },
              contentType: "application/json",
              dataType: "json",
              success: function(msg) {
                  // code here...
              },
              error: function(e) {
                  // code here...
              }
          });
  }
  catch (e) {
   // code here...
  }
 });
});

ここでUrlを指定する場合に注意が必要だ。上記の例はMyClipアプリケーションと同一ドメイン上にWeb Serviceを設置しているので正常に動作する。しかし、異なるドメイン(例:http://www.yahoo.co.jp/)へのリクエストはスクリプト上からはできない。これはほとんどのブラウザでドメインをまたいだリクエストが禁止されているからである。対処として、上記の例のようにWeb Serviceを同一ドメイン上に設置するか、またはいったん同一ドメインへリクエストをし、その後リクエストされた先から外部ドメインへリクエストする(ブリッジする、という)という手法があげられる。ちなみに、リクエスト先のWeb Serviceを運用しているアプリケーション上に設置すれば、http://www.my-clip.net/の部分は必要なく、/WebServices/Clip.asmx/Commentの部分のみでリクエストできる。

もう一点、ContentTypeをjsonに変更している。これはWeb Serviceのメソッドを記述する際にResponseFormatをJsonで指定しているからである。Web Serviceのメソッドは以下の通りだ。

-Clip.asmx-
[WebMethod(CacheDuration = 10)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public CommentModel Comment(
  int clipId, 
  string commentText)
{
  // code here...
}

以上で一連の流れは終了だ。サーバサイドとしてWeb Serviceを使用してもクライアント側のコードにほぼ違いは無い。ただWeb Serviceを使用する場合は設置場所に注意が必要だ。

2009年10月7日水曜日

jQueryとASP.NET MVCで非同期通信 その1

ASP.NET MVCは非同期通信を行うためのフレームワークとしてAjax.BeginForm()とPartialView()が用意されている。これらはDavid HaydenのBlogに詳しく載っているのでそちらを参照して欲しい。

今回はjQueryを使った非同期通信を解説する。また例によってここで記載するサンプルコードはMyClipで実装したコメント投稿を簡易化したものになる。

まずコメントを入力するtextareaと保存ボタンを用意する。

-details.aspx-
<div>
  <div>
    <textarea id="commentArea"></textarea>
  </div>
  <input id="saveButton" type="button" value="Save"/>
</div>

ついで、JavaScriptでsaveButtonのClickイベントをハンドルし、jQueryのajaxメソッドでサーバサイドと非同期通信を行う。

-JavaScript-
$(document).ready(function() {
 $('#saveButton').click(function(){
  try {
   $.ajax({
              type: "POST",
              url: "/Clip/Comment",
              data: { clipId: _clipId, commentText: $('#commentArea').val() },
              contentType: "application/x-www-form-urlencoded",
              dataType: "json",
              success: function(msg) {
                  // code here...
              },
              error: function(e) {
                  // code here...
              }
          });
  }
  catch (e) {
   // code here...
  }
 });
});

ここで注意が必要なのがcontentTypeの指定だ。application/jsonを指定するとなぜだか引数が常にnullになる。

ClipControllerの処理は以下の通りだ。

-ClipController.cs-
public ActionResult Comment(
   int clipId, 
   string commentText)
{
 // saving data code here...

 return Json(new
 {
  // return data here...
 });
}

以上で一連の流れは終了だ。いたって簡単に非同期通信を行えるのが分かるだろう。またController側も特別な記述をとくに必要としない。次回はControllerのActionではなく、WebServiceを呼び出す方法を解説する。

2009年10月5日月曜日

Routingの話 その2

前回Routingの基礎を解説したので今回はカスタムRoutingを解説する。

初期のRoutingとしてGlobal.asaxに以下のRouteが登録されている。

routes.MapRoute(
 "Default", 
 "{controller}/{action}/{id}",
 new { controller = "Home", action = "Index", id = ""}
);

MapRouteメソッドの第二パラメータに注目して欲しい。これは下記のようなリクエストを想定している。

http://www.my-clip.net/Clip/Edit/2

ClipControllerのEditメソッド、Id=2というリクエストになる。ちなみに複数パラメータを渡す場合はどのようなURLになるかというのは前回説明した通りだ。では、複数のパラメータを渡す場合に下記のようにするにはどうしたらよいだろうか。

http://www.my-clip.net/Clip/Category/futsal/week/2

Global.asaxに次のコードを追加すればよい。

routes.MapRoute(
 "CategoryWithPage", 
 "Clip/Category/{nameKey}/{time}/{page}", 
 new { controller = "Clip", 
  action = "Category", 
  time = "week", 
  page = 1 }
);

そしてClipControllerの記述は下記のようになる。

public ActionResult Category(
 string time, 
 string nameKey, 
 int page)
{
  // Code here...
}

これで読みやすいURLをユーザーに提供することができる。次の二つのURLを比べれば後者のほうが理解しやすいのは一目瞭然だろう。

http://www.my-clip.net/Clip/Category?nameKey=futsal&time=week&page=2

http://www.my-clip.net/Clip/Category/futsal/week/2

ちなみにMapRouteメソッドの第三パラメータはデフォルト値なので、http://www.my-clip.net/というようにController、Action部分が省略されてリクエストされた場合は第三パラメータが使われる。なのでhttp://www.my-clip.net/はhttp://www.my-clip.net/Home/Index/と等価だ。

2009年10月4日日曜日

Routingの話 その1

今回はASP.NET MVCのRoutingの基本的な話。Routingとは何ぞやというと、リクエストされたURLと実際の処理をマッピングしてくれる処理になる。ASP.NET MVCはControllerとViewが完全に分離されているので、Routingが必要になるわけだ。実際に例を見たほうが理解しやすいので一緒に見ていこう。

http://www.my-clip.net/Account/Login/

このURLはAccountControllerのLoginというActionを要求している。ActionというのはControllerの実際の動作にあたる。ControllerのpublicメソッドはすべてActionになる。つまり外部から呼び出し可能ということになるので注意が必要だ。

では実際にAccountControllerのLoginメソッドを見てみよう。

public ActionResult Login()
{
   return View("Login");
}

Loginメソッドの戻り値としてLoginViewを返却している。このLoginViewというのは、/Views/Account/Login.aspxを指している。ちなみにLoginメソッドの返却値を以下のように変更してもASP.NET MVC的にはまったく問題が無い。

public ActionResult Login()
{
   return View("AnotherLogin");
}

ただし、/Views/Account/AnotherLogin.aspxが存在することが必須だ。

このようにASP.NET MVCは実際のaspxを隠蔽しているのでエンドユーザーはその存在をうかがい知れない。ここがMVCと銘打たれた所以だろう。ちなみにIdなどのパラメータを渡したい場合は下記のようになる。

http://www.my-clip.net/Clip/Details/23

23の部分がIdにあたる。上記の例は、ClipControllerのDetailsActionをId=23で要求している。複数のパラメータを渡したい場合は下記のようになる。

http://www.my-clip.net/Account/Login?username=yokyo&preference=japanese

これで大体Routingがどういうものか理解できたかと思う。次回はカスタムRoutingを解説したいと思う。