QA@IT

ASP.NET MVCのsubmit時のpostをjavascriptで制御する

11134 PV

ASP.NET MVC + angularJSで開発をしています。
ログイン処理時にまずapi経由で認証結果を取得して、認証されない場合、postを発生させずエラーメッセージの表示処理のみをjavascriptから行いたいと思っています。

html

<form action="/Test/Main" method="post">
<table>
 <tr>
  <th>ユーザーID</th>
  <td><input type="text" name="UserCode" ng-model="UserCode" /></td>
 </tr>
 <tr>
  <th>パスワード</th>
  <td><input type="text" name="UserPassword" ng-model="UserPassword" /></td>
 </tr>
 <tr>
  <td colspan="2" style="text-align:right;">
    <input type="submit" value="ログイン" name="LoginSubmit" ng-click="DoAuthenticate()" /><br />
    {{loginResult}}<br />
  </td>
 </tr>
</table>
</form>

angularJS

    $scope.DoAuthenticate = function() {
     $http({
      method : 'GET',
      url : "認証用のurl"
     }).success(function(data, status, headers, config) {
      if (!data) {
       $scope.loginResult = "ログインに失敗しました";
      }
     }).error(function(data, status, headers, config) {
      console.log(status);
     });
    };

if (!data)のとき、postが発生すると画面の再描画が行われてしまい、$scope.loginResultのメッセージも消えてしまいます。
そもそも通信が非同期なので難しいのかもしれないと思ってはいるのですが、なにかいい方法はないでしょうか。

回答

※ うしろにASP.NET MVCで試したものを追記しました。

javascript部分しか確認してません。
とりあえず現状からであれば、successの中でjavascriptでsubmitして、
ボタンのイベント自体はキャンセルしてしまうという手があると思います。

イベントオブジェクト($event)をng-clickに追加

    <input type="submit" value="ログイン" name="LoginSubmit" ng-click="DoAuthenticate($event)" /><br />

イベントハンドラの修正

    $scope.DoAuthenticate = function(e) {  // 引数追加
     e.preventDefault();                   // 追加

     $http({
      method : 'GET',
      url : "認証用のurl"
     }).success(function(data, status, headers, config) {
      if (!data) {
       $scope.loginResult = "ログインに失敗しました";
       return;   // 追加
      }
      document.getElementsByTagName('form')[0].submit(); // 追加:formは 1つしかないと仮定してます
     }).error(function(data, status, headers, config) {
      console.log(status);
     });

     return false; // 追加
    };

スクリプトからsubmitしているのでユーザーアクションとみなされないので例えば target="_blank"とかだとポップアップブロックに引っかかるなど、制限がかかる場合があります。

老婆心ながら、このスクリプトからもわかるように、api呼ばずにsubmitできてしまう(
javascript:document.getElementsByTagName('form')[0].submit(); とか)
ので、そのあたりは注意してください。

具体的なコードは考えてないですが、formのng-submitで拾った方が良さそうな気はしますが、それだとsubmitしようが無くなるんですかね?

ブラウザとangularjsのバージョンは書いておいてほしいところです。特にangularはバージョン間の差分がそれなりにあるので。

jsbinでのデモ (自分が確認するために書いたもの。説明が長いので試してみたいときだけどうぞ)

※ jsbin.comの他、ボタン押下後、最後に http://posttestserver.comに実際にPOSTしてます。
(ソース見るだけならPOSTは発生しません。)
※ 実行するときは(出力画面が別画面じゃないとリフレッシュされたのわからないので)OUTPUTペインの分離ボタン押してから実行してください

qait9560.png

https://jsbin.com/yurayanaqi/edit?html,js,console,output

いろいろ:

  • 認証の$http部分は偽物のFactoryにしていて、2秒待って返すだけです。
  • .success .error スタイルの作り方が出てこなかったんで、promiseになってます。thenの第一引数がsuccessで、第二引数がerror。
  • success, errorの引数が違います(statusやdataは response.status, response.data に。判定につかっているdata以外は気にしてません)。
  • httpDummyのfailureTest とか errorTestをtrueにすると、エラーっぽい挙動とdataを返さない挙動になります。
  • consoleにちょびちょびログ吐いてます。
  • ChromeとIE11で確認しました。

追記

まず先に、ng-sugmitHtml.BeginFormng_submitとして属性を追加してあげれば使えます。
以下のサンプルにもボタンを追加して、ng-submitの動作を見ています。

Visual Studio 2013 pro, ASP.NET MVC 5.2.2, Chrome 45, AngularJS 1.3.0 公式から

ASP.NET Webアプリケーション - MVC のみチェック

で、以下の様なソースです。
ng_submit以外は JSBinの時と同じです。 2秒待ってsuccessの処理を行います。

jsBinに挙げたやつでは、関数名をsubmitFormに変えていたので、もしそっちからスクリプトだけコピペしてたら上手くいかないです。こっちに書いたのは提示ソースにそろえてるはずですが。

  • Views/Test/Index.cshtml
@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
<div ng-app="app" ng-controller="MainCtrl as app">
    @* ASP.NET MVCのバージョン確認 *@
    @ViewBag.Version

    @using (Html.BeginForm( "Main", "Test", null,
        FormMethod.Post, new {ng_submit = "angularSubmit($event)"})){
        <table>
            <tr>
                <th>ユーザーID</th>
                <td><input type="text" name="UserCode" ng-model="UserCode"/></td>
            </tr>
            <tr>
                <th>パスワード</th>
                <td><input type="text" name="UserPassword" ng-model="UserPassword"/></td>
            </tr>
            <tr>
                <td colspan="2" style="text-align: right;">
                    <input type="submit" value="ログイン" name="LoginSubmit" ng-click="DoAuthenticate($event)"/><br/>
                    {{loginResult}}<br/>
                    <input type="submit" value="ng-submit のテスト"/><br/>
                </td>
            </tr>
        </table>
    }
</div>
    <script src="~/Scripts/angular.js"></script>
    <script src="~/Scripts/app.js"></script>
</body>
</html>
  • Scripts/app.js
var app = angular.module('app', []);
app.constant('AUTH_API', 'http://example.com/auth');
app.controller('MainCtrl', function ($scope, AuthApiAlternate, AUTH_API) {

    $scope.DoAuthenticate = DoAuthenticate;
    $scope.angularSubmit = angularSubmit;
    $scope.loginResult = "angular is working";

    function DoAuthenticate(e) {
        e.preventDefault();

        var $http = AuthApiAlternate.httpDummy;
        $http({
            method: 'GET',
            url: AUTH_API
        }).then(function (response) {
            if (!response.data) {
                $scope.loginResult = "ログインに失敗しました";
            } else {
                $scope.loginResult = null;
                document.getElementsByTagName('form')[0].submit();
            }
        }, function (response) {
            console.log(response.status);
        });
        console.log('my dummy return results after 2 sec.');
        return false;
    }

    function angularSubmit() { // ng-submit用
        alert('ng-submit!');
    }
});

// $httpの代わり。jsbinの時と同じ。2 秒待って successになる。 failureTestやerrorTestをいじると挙動が変わる
app.factory('AuthApiAlternate', function ($q) {
    return { httpDummy: httpDummy };
    function httpDummy(opt) {
        var defer = $q.defer();
        var failureTest = false;
        var errorTest = false;
        console.log('url you specified :' + opt.url);
        setTimeout(function () {
            if (failureTest) {
                console.log('failure');
                defer.resolve({ status: 200 });
                return;
            }
            if (errorTest) {
                defer.reject({ status: 500, data: "something wrong happend" });
                return;
            }
            defer.resolve({ status: 200, data: 'You logged in.' });
            return;
        }, 2000);
        return defer.promise;
    }
});
  • Controllers/TestController.cs
namespace InjectSubmit.Controllers{

    /// <summary> qait 9560 </summary>
    public class TestController : Controller{
        // GET: Test
        public ActionResult Index(){
            ViewBag.Version = typeof(Controller).Assembly.GetName().Version.ToString();
            return View();
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public String Main(){
            return "Post Success :" + Request.Form["userCode"];
        }
   }
}
編集 履歴 (2)
  • angularjsは1.3を使ってます。すいません。
    $eventを引数にしてもIIS側の処理が行われるようです。
    document.getElementsByTagName('form')[0].submit();がなくても実行されるようです。
    -
  • ng-submitはrazorからうまく挿入できません。formのところの記述は、razorでHtml.BeginForm()と書いてるのですが。 -
  • 少なくとも私の環境では動きますよ。サンプルを追記しますが、あとは環境がわからないので何とも言えません。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。