2009年10月26日月曜日

LinqToSqlでの開発の注意点 その1

LinqToSqlは.net framework3.5より導入されたORM(Object-relational mapping)だ。LinqToSql以前はORMといえばNHibernateなどが主流だったと思う。いまだにいくつかの点でNHibernateに譲るけれど(2nd Level Cacheやクエリーベースの取得など)、LinqToSqlのほうがよりシンプルな印象を持った。私見だがLinqToSqlを使用してのプロジェクト開発は、ORMを使用しないプロジェクトに比して段違いに開発スピードが速い。これから複数回に分けてLinqToSqlの注意点について解説していきたいと思う。

まず一番初めに直面するであろう問題はデータの更新時に訪れる。プロジェクトへdbmlファイルを追加し、必要なTableも追加し、データを取得、挿入までトントンと進むと思われるが、dbmlへ何も対処を施していないと挿入したデータを更新する際に下記の例外に直面する。

An entity can only be attached as modified without original state if it declares a version member or does not have an update check policy
(注意:実際に発生する例外は若干違うかもしれない。今回は実地に試す環境がないので思い出しながら解説しています)

ソースコードはこのようなものを想定している。

void Update(Cilp clip)
{
 var db = new MyClipDataContext();
 db.Clips.Attach(clip, true);
 db.SubmitChanges();
}

この例外は並列処理時の上書き更新を防ぐための処理が初期状態でdbmlに施されているために発生する。これを避けるためには各テーブルにTimeStamp列を追加し、更新を下記のように変更する必要がある。

void Update(Cilp clip)
{
 var db = new MyClipDataContext();
 var dbClip = db.Clips.Single(x => x.Id==clip.Id);
 dbClip.Value = clip.Value;
 db.SubmitChanges();
}

これでひとまず例外が発生することは無くなった。ただ上記の例では上書き更新を防いでいるとはいえないだろう。実際には下記のように使うのが正しい。ちなみにこのソースコードは一つ目の例と同じだ。ただClipsテーブル上にTimeStamp列が追加されているものとし、さらに(ここが重要なのだが)引数clipは新規にnewしたものではなく、DBから取得したデータをSerialize, Deserializeしたものとする。

void Update(Cilp clip)
{
 var db = new MyClipDataContext();
 db.Clips.Attach(clip, true);
 db.SubmitChanges();
}

これにより、データ取得時のTimeStampを損なうことなく正確な上書き防止処理が期待できる。

ただ、上書き防止などを必要としない処理を行っている場合は、このような実装を行う必要はなく、下記のようにdbml上で各列ごとにUpdateCheckをNeverに設定すればよい。



これでTimeStampなどを気にすることなく更新処理が行えるようになる。ここで注意しなければならないのは、SQL Server上でテーブルを更新した場合、その更新をdbmlに反映するには再度テーブルをdbmlにD&Dする必要がある(信じられないことにVS2008にはそれ以外にSQL Server上の変更をdbmlへ反映する手段が無い)。このときにUpdateCheckをNeverに変更する処理を忘れずに行うようにしよう。

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月17日土曜日

散歩道機能のリリース

My-Clip.netに散歩道を登録できる機能を追加した。ここから機能を確認できるので体験してもらえたらと思う。

画面的には下記のようになる。



これでお気に入りの散歩コースなどを登録できるし、それらのコースのおおまかな距離をはかったりもできるので色々と活用していただけたらと思う。

2009年10月16日金曜日

MSDTCの例外を直す方法

System.Threading.TransactionScopeを使用しTransactionを一元管理しようとする際に「MSDTCが”サーバ名”上で無効です」というような例外が発生する場合がある。
(上記エラーメッセージが日本語でどのように表示されるのかは不明だが英文だと次のように表示される。「MSDTC on server ’server name’ is unavailable」)

この問題は下記の手順で修正できる。

1、管理ツール → サービス
2、Distributed Transaction Cordinatorを右クリック → 開始

またプロパティからスタートアップの種類を自動に変更しておいてもよいだろう。

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を解説したいと思う。

2009年10月3日土曜日

Googleストリートビューの実装方法

下記の画像のようにMyClipにGoogleストリートビューの機能を盛り込んだので、その開発手順をここで紹介する。動作確認は実際にMyClip上で行って欲しい。今回の記事はストリートビューを使うにあたってすでにGoogle Maps APIのGMap2クラスに多少なりとも親しんでいるものとして話を進める。
APIの詳細はGoogle Maps API Referenceを参照してほしい。



-概要-
クリックされた地図上のストリートビューのデータ取得のためにGStreetviewPanorama.getNearestPanorama()を使用する。その際に問題になるのがクリックされた場所。そこが道路上ならば問題ないけれど建物上であればgetNearestPanorama()でデータを取得することができない。それなのでまず最初にGDirections.loadFromWaypoints()で最寄の道路座標データを取得する。

以下のコードは必要なところだけを抜き出した簡易版なので、手順だけでも大まかに掴んでもらえたらと思う。
(MyClip上ではストリートビュー関連の処理をクラスとしてまとめてあるので、thisはそのクラスを表すもの)

//マップの準備
this._map = new GMap2(
  document.getElementById("map"));
this._map.setUIToDefault();
this._map.setCenter(
  new GLatLng(lat, lng), 17);

//ビューの地図上の場所を表すアイコンの準備
this._guyIcon = new GIcon(G_DEFAULT_ICON);
this._guyIcon.image = 
  "http://maps.gstatic.com/mapfiles/cb/man_arrow-0.png";
this._guyIcon.transparent = 
  "http://maps.gstatic.com/mapfiles/cb/man-pick.png";
this._guyIcon.imageMap = [
  26, 13, 30, 14, 32, 28, 27, 28, 
  28, 36, 18, 35, 18, 27, 16, 26,
  16, 20, 16, 14, 19, 13, 22, 8];
this._guyIcon.iconSize = new GSize(49, 52);
this._guyIcon.iconAnchor = new GPoint(25, 35);

//ストリートビューのレイヤーをマップ上に載せる
this._svOverlay = new GStreetviewOverlay();
this._map.addOverlay(this._svOverlay);

//ストリートビューを準備
this._streetViewClient = new GStreetviewClient();
this._streetViewer = new GStreetviewPanorama(
  document.getElementById("stview"));

//最寄の道路座標を取得の準備
//loadFromWaypointsは非同期でデータ取得を行うので、callbackメソッドを設定する必要がある。ここではGEventにGDirectionsのloadイベントを登録している
this._direction = new GDirections();
GEvent.addListener(
 this._direction, "load", function() {
  //これで最寄のデータ取得完了
  var p = this._direction.getPolyline().getVertex(0); 
  var mystv = this;

  // 取得した道路上のストリートビューデータを取得
  this._streetViewClient.getNearestPanorama(p, 
   function(panoData) { mystv._showPanoData(panoData); });
});

// getNearestPanorama()のcallbackメソッド
_showPanoData: function (panoData) {
 this._streetViewer.setLocationAndPOV(
   panoData.location.latlng);
 if (!this._guyMarker) {
  this._guyMarker = 
   new GMarker(panoData.location.latlng, 
    { icon: this._guyIcon, draggable: true });
  this._map.addOverlay(this._guyMarker);
  var mystv = this;
  GEvent.addListener(this._guyMarker, "dragend", 
   function() { mystv._onDragEnd(); });
 }
 else {
  this._guyMarker.setLatLng(panoData.location.latlng);
 }
}

// 地図上の人型アイコンのドラッグ終了イベントハンドラ
_onDragEnd: function() {
 var latlng = this._guyMarker.getLatLng();
 this._direction.loadFromWaypoints(
  [latlng.toUrlValue(6), latlng.toUrlValue(6)], 
  { getPolyline:  true });
}

これで地図上にストリートビューのレイヤーと人型アイコンが表示され、人型アイコンをドラッグ&ドロップすれば最寄の道路上のストリートビューが表示される。

このほかにもGStreetviewPanoramaのinitialized, yawchangedイベントを実装すれば、ストリートビュー上のユーザーアクションが拾える。そうすれば地図からストリートビュー、またその逆へ、という双方向な挙動が実現できるのでよりユーザーフレンドリーな地図サービスが提供できる。デモを漁れば前述のイベント処理を実装したサンプルが見つかるので興味がある方はそちらへ。