QA@IT

ASP.NETで、AjaxからWCFサービスを呼び出そうとしたが、IISに配置すると失敗します。

9553 PV

お世話になります。
Microsoft Visual Studio 2013 Community Edhition のWebFormsで開発しております。
開発環境は、Windows8.1,IISのバージョンは8.5です。

1)デバッグ中には(開発サーバを用いると)、LogInMeボタンを押したとき「接続成功」と表示されます。しかし、IISに配置すると「Exeption」ということで失敗してしまいます。
2)IISに配置後、http://localhost/TEST/TEST.aspx”では期待通りページが表示されますが、http://localhost/TEST/TESTWCF.SVC では「エンドポイントが見つかりません」とブラウザに表示されます。

どの部分を修正したら良いでしょうか。あるいはIISの設定等で何かしなければいけないことがあるのでしょうか。
下記は、Web.configの一部です。(関係がありそうなところのみを抜き出しました。)

  <system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="DaishinWCFAspNetAjaxBehavior">
          <enableWebScript />
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
      multipleSiteBindingsEnabled="true" />
    <services>
      <service name="Shop.DaishinWCF">

        <endpoint address="" behaviorConfiguration="DaishinWCFAspNetAjaxBehavior"
          binding="webHttpBinding" contract="Shop.DaishinWCF" />

      </service>
    </services>
  </system.serviceModel>

TESTWCF.svc.vbのView Code

Imports System.ServiceModel
Imports System.ServiceModel.Activation
Imports System.ServiceModel.Web
Imports System.Web.Providers.Entities
Imports System.Runtime.Serialization.Json
Imports System.ServiceModel.Description
Imports System.Runtime.Serialization
Imports Newtonsoft.Json

<ServiceContract()>
Public Interface IService1

    <OperationContract()> _
        <WebInvoke(Method:="GET", ResponseFormat:=WebMessageFormat.Json)> _
    Function fnVerifyID(ByVal ID As String, ByVal PASS As String)

End Interface
<DataContract()> _
Public Class csVerifyID
    Dim sID As String
    Dim iFlg As Integer
    Dim sName As String


    <DataMember()> _
    Public Property Name() As String
        Get
            Return sName
        End Get
        Set(value As String)
            sName = value
        End Set
    End Property

End Class

<ServiceContract(Namespace:="")> _
<AspNetCompatibilityRequirements(RequirementsMode:=AspNetCompatibilityRequirementsMode.Allowed)>
Public Class DaishinWCF


    <WebGet()>
    Public Function fnVerifyID(ByVal ID As String, ByVal PASS As String)

        Dim Hyouji As String = CStr(ID) & CStr(PASS)

        Dim objVeryfyID As New csVerifyID

        objVeryfyID.Name = "接続成功"

        Dim person As String = JsonConvert.SerializeObject(objVeryfyID)

        Return person

    End Function


End Class

TESTWCF.svc.vbのView Markup

viceHost Language="VB" Debug="true" Service="Shop.DaishinWCF" CodeBehind="DaishinWCF.svc.vb" Factory="System.ServiceModel.Activation.WebServiceHostFactory"%>

Test.aspx

<%@ Page Title="" Language="vb" AutoEventWireup="false" MasterPageFile="~/Site.Mobile.Master" CodeBehind="Test.aspx.vb" Inherits="Shop.Daishin" %>

<asp:Content ID="Content3" ContentPlaceHolderID="MainContent" runat="server">

<head>
    <!DOCTYPE html> 
<title>テストタイトル</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css"/>
<link rel="stylesheet" href="css/custom.css"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>
    <%--<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>--%>
<script src="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.js"></script>
<script src="js/jCarousel.min.js" type="application/x-javascript" charset="utf-8"></script>
<script src="js/custom.js"></script>


    <script type="text/javascript">

    $(function () {

        $('#LogInMe').click(function () {


            var UserName = $('#txtUserName').val()
            var Password = $('#txtPassword').val() 
            var Para = { ID: $('#txtUserName').val(), PASS: $('#txtPassword').val() };

            $.ajax({
                url: '/TestWCF.svc/fnVerifyID', // ポスト先のURL
                type: 'GET',
                dataType: 'json', // HTTPメソッドの種類
                contentType: "application/json; charset=utf-8",

                traditional: true,
                data: Para ,

                success: function (response) {
                        var obj = JSON.parse(response.d);            
                        alert(obj.Name)
                }, 

                error: function (xhr, status, err) {
                    alert('Exeption:');

                } 
            });

        });
    });

</script>

</head>
<body>

  <div data-role="page" data-add-back-btn="true" id="p-listview-thumbnail" >

        <div data-role="header" data-position="fixed" data-theme="d">
           <h1></h1>
            <a href="#Family" data-icon="gear" class="ui-btn-right" data-iconpos="notext" data-theme="d" >Options</a>   
            <input type="text" id="txtUserName" placeholder="Username" />
            <input type="password" name="passwordinput" id="txtPassword"  placeholder="Password" value=""  />
            <a href="javascript:Authenticate();" id="LogInMe" data-role="button" data-icon="check" data-iconpos="right">Log Me In!</a>
             <label for="basic_name" id="lblMessage"></label>            
        </div>

</body>
</html>

</asp:Content>

回答

structureさんのソースで試してみました。

環境: Win 8.1 pro, Visual Studio 2013 pro, IIS 8.5 (win8.1付属) と IIS開発サーバー

追記: DefaultAppPoolで実行させているので、統合(Integrated)パイプラインモードで動作させています。

プロジェクトは WCFサービスアプリケーション、 ASP.NET WebアプリケーションのWebFormだけチェックしたものの両方で試しました。

  • web.configのservicemodelやサービスのソースは変えてません(変えなくて済むようにソリューション/プロジェクトもShopにしてます)。
  • Webアプリケーションの方は、ファイル名がTESTxxxなのに、Behindのクラス名がDaishinだったりしたのでそのあたりを提示されたソースに併せたぐらいであとは貼り付けただけです。
  • WCFサービスアプリケーションの方は、上記に加えて、aspxのマスターページの部分とかタグの不整合などはなおしましたが、他はそのままです。

本筋とは別で気になるところはいろいろあるんですが、
結論から言えば、サービス側のソースはそれで動作します。
問題はjQueryのリクエストURLです。

IISに配置した際、TEST アプリケーションとして配置してる(サーバーからみて http://localhost/TEST の位置)と思いますが、scriptで指定しているURLがここを指していません。

url: '/TestWCF.svc/fnVerifyID' は「サーバーのルートディレクトリのTestWCF.svc」の意味になります。
これはつまり http://localhost/TESTWCF.svc/fnVerifyID ということです。

開発サーバーの場合は、アプリケーションはルートディレクトリに配置されているように実行されますので、
http://localhost:ランダムなポート/TESTWCF.svc/fnVerifyID でアクセスできますが、
IISの http://localhost/TESTに配置しているので、jQueryからhttp://localhost/TEST/TESTWCF.svc/fnVerifyID となるようなurlを指定しないといけません。

幸いASP.NET WebFormにも他のWebアプリケーション同様アプリケーションルートを求めるメソッドがありますので、ここではそれを利用するといいでしょう。

url: '/TestWCF.svc/fnVerifyID', // ポスト先のURL

url: '<%=ResolveUrl("~/TestWCF.svc/fnVerifyID")%>', // ポスト先のURL

に、変えてみてください。これで上手くいくんじゃないかと思います。
ブラウザで表示されたら開発サーバーでの実行時とIISでの実行時で該当箇所がどうなっているか確認してみてください。

qait9554_success.png

ちなみに、この状態で、http://localhost/TEST/TESTWCF.svchttp://localhost/TEST/TESTWCF.svc/fnVerifyID にアクセスするとそれぞれこうなります。
スクリプト直したのは関係なくもともとこういう状態だったと思います。

structureさんが見た画面も下の1枚目じゃないでしょうか。


qait9554_endpoint_not_found.png


qait9554_svc_function_with_browser.png


最後に気になった点をいくつか。これは興味があればどうぞ。

  • ~のView Markup~のView Codeという説明がみられるが、VSの日本語パックを当てていないのでは?

Visual Studioが英語のままじゃないですか?
Visual Studio Community Editionは初期状態で英語ですが、別途 公式の Language Packをダウンロード可能です。
それを適用すれば標準メニューは日本語にできます。好きで英語版をつかっているなら別ですが、日本語化したい場合は、

https://www.visualstudio.com/ja-jp/downloads/download-visual-studio-vs#DownloadFamilies_2

の左側から Visual Studio 2013 を展開して Community 2013を選び、
Visual Studio 2013 Language Pack から取得、日本語化できるはずです。
私はCommunityを使っているわけではないので参考情報としてどうぞ。

VS2013_com_lp_dl_button.png

  • TESTWCF.svcのマークアップのコードビハインドが提示ソースと異なる。

先頭が書けてるの補足してますが、 以下の様に

<%ServiceHost Language="VB" Debug="true" Service="Shop.DaishinWCF" CodeBehind="DaishinWCF.svc.vb" Factory="System.ServiceModel.Activation.WebServiceHostFactory"%>

CodeBehind="DaishinWCF.svc.vb"

となってますが、ソースのファイル名としては 「TESTWCF.svc.vbのView Code」となってますので、CodeBehind="TESTWCF.svc.vb" でしょう (少なくともここに提示したソースだと)。

  • IService1 は今回の部分では不要

多分使われてないです。Ajaxじゃない WCFサービスだとインターフェース定義して実装してというテンプレートになるんですけどね。インターフェースを使ってはいけないわけではないですが、現状は消してもコンパイル・実行できるかと。

  • VB.NETのソースで気になった点
  1. Functionの戻り値の型を指定しましょう。
  2. 関数名は先頭大文字
  3. 変数名は先頭小文字 (プロパティ Name は先頭大文字のままで大丈夫)
  4. クラス名は先頭大文字

の、方が一般的な .NETコード スタイルになると思います(あくまでざっくりとしたものです、細かくはもうちょっといろいろあります)。
プロジェクトのコーディング標準などもあるかもしれませんが、例えば以下の様なコードの方がスタイルとしては一般的かなと思いますので、ご参考までに。

<ServiceContract(Namespace:="")> _
<AspNetCompatibilityRequirements(RequirementsMode:=AspNetCompatibilityRequirementsMode.Allowed)>
Public Class DaishinWCF

    <WebGet()>
    Public Function VerifyID(ByVal id As String, ByVal pass As String) As String
        Dim hyouji As String = CStr(id) & CStr(pass) 
        Dim veryfyIdResult As New CsVerifyID()
        veryfyIdResult.Name = "接続成功"
        Dim person As String = JsonConvert.SerializeObject(veryfyIdResult)
        Return person
    End Function

End Class

(CStrも idと passが String なので無くて良い気がしますが、ひょっとしてnull(Nothing)回避のためですかね?)

長くなりましたが、以上です。

編集 履歴 (1)
  • ご丁寧にありがとうございます。できました。
    「エンドポイントが見つかりません」というメッセージと"localhost/TEST/TESTWCF.svc/fnVerifyID"の場合の表示は掲載していただいている図と同じでした。
    -
  • 「IService1 は今回の部分では不要」という部分について確認しました。
    他のご指摘についてもありがとうございます。
    -

IIS上でWCFを動かしたことがないならまずはもっとシンプルなチュートリアルなど参照して試してみた方がいいかもしれませんね。

とりあえず気になったのは

スクリプトの以下の部分。

error: function (xhr, status, err) {
     alert('Exeption:');

} 

エラー情報を握りつぶしています。エラーの内容を確認した方がいいでしょう。
ステータスコードだけでも助けになることがあります。

IISに配置すると「Exeption」ということで失敗してしまいます。

これはあなたがそういうalertを出しているだけですよね。
status, err などわかりやすい引数名ですのできちんと拾って Console.log(err)とかで確認しましょう。

もう一点は以下の部分で

http://localhost/TEST/TESTWCF.SVC では「エンドポイントが見つかりません」とブラウザに表示されます。

スクリプトでは

url: '/TestWCF.svc/fnVerifyID', // ポスト先のURL  // <= これも ポストではなくゲット(type: 'GET')ですね。
type: 'GET',

/TestWCF.svc/fnVerifyID に対するGetリクエストをしているのですから

http://localhost/TEST/TestWCF.svc/fnVerifyID

を見てみるべきなのでは?
例えば aspxファイルの時、自分で配置していないaspxファイルを指定しても確認にならないですよね?

POSTの場合はPOSTしないといけませんが、今回はGETなので試してみる価値はあるかと思います。

編集 履歴 (0)
  • ブラウザから .svc を呼び出せは、以下の URL の記事の図 4 のような応答が帰ってくるはずです。逆に /fnVerifyID を付けると HTTP 400 になるはずです。なので、質問者さんのやり方は問題ないのでは?
    http://code.msdn.microsoft.com/windowsdesktop/10-C-WCF-a3831723
    -
  • それPOSTのサービスじゃないですか?GETなら見れます。
    メタデータ非表示の場合を気にしたんですが、メタデータ隠してもsvcはアクセスできるようなので、svcでも一応確認できるのはたしかですね。
    -
  • POST or GET は関係ないです。紹介した記事を見てください。 -
  • いや、私実際に作って試したんで、そちらこそ自分で試してから言ってください。
    そもそも jQuery.ajaxのGETリクエストでアクセスできるのにブラウザでアクセスできないと言われた根拠はなんでしょうか。それこそFiddler等で確認できると思いますが。
    -
  • 話が噛み合ってないと思われ。質問者さんがやったと思われるブラウザから TestWCF.svc を呼び出すのは GET も POST も関係ないのでは? そもそも、それがダメなら、あなたが言う TestWCF.svc/fnVerifyID を呼び出して確認するという話は意味がないのでは? -
  • 私の言っていること理解いただいているでしょうか? 質問者さんが "... TESTWCF.SVC では「エンドポイントが見つかりません」とブラウザに表示されます" と言っているのに対して、あなたは "... TestWCF.svc/fnVerifyID を見てみるべきなのでは?" とレスしていますが、そうするとエンドポイントが見つからない以外に分かることがあるのか教えていただければ幸いです。 -
  • 噛みあってないですね。あなたが言ったのはbasicHttpBindingの記事を出してきて「逆に /fnVerifyID を付けると HTTP 400 になるはず」です。
    GETならば見れるというコメントに対して「POST or GET は関係ないです。紹介した記事を見てください。」です。
    -
  • 元々私が言っているのは、確認の手段として実際のアクセス先でなく「/TestWCF.svc」を使っているのが気になったと言っているだけです。jQueryで「http://localhost/TEST/TestWCF.svc/fnVerifyID」にGETリクエストしているのだから、ブラウザでも同じことをした方が良いだろうという事です。
    これも噛みあわないと思いますのでコメントしないでください。
    -
  • では、質問者さんが言われる "... TESTWCF.SVC では「エンドポイントが見つかりません」とブラウザに表示されます" に戻って話をしませんか? あなたはそれに対して "... TestWCF.svc/fnVerifyID を見てみるべきなのでは?" と言う提案をしたのですよね。その結果で何が分かるか教えてください。 -
  • 私の質問は無視して自分の都合のいいところまでしか戻られない様で、やはり話がかみ合いませんね。
    「試してみる価値はあるかと思います。」とある通り、回答時点で何か変化があれば幸いと思ったからです。GETでRESTサービスなのでブラウザでGETリクエストはできるはずなので。
    「エンドポイントが見つかりません」を盾にするなら、パケットキャプチャツールやデバッガだとどうなるんですか?
    -
  • 上コメントに回答は不要です。わたしの相手ではなく質問に答えてあげてください。ここはそういうサイトです。
    以上です。
    -
  • "質問に答えてあげてください" というのは基本的な姿勢としてその通りですが、気になる回答にコメントするというのはアリですよね。たとえば、同じ質問者さんの別スレッドで、あなたが 2 重にアップロードされるコードをお回答したの対し私がコメントしたように。 -
  • あなたは "TestWCF.svc/fnVerifyID を見てみるべきなのでは? ・・・確認にならないですよね?" と言われてますが、それは TESTWCF.SVC では確認にならないと言う意味だと思ったので指摘させてもらいました。(違った結果になるかもしれないということを期待してと言うことらしいですが、"確認にならない" というのは依然として納得できてません) -
  • 別の回答の検証結果を見ました。それを見ますと私のコメントは的外れだったようですみません。自分の検証結果(.svc を要求すると "これは Windows(R) Communication Foundation サービスです。・・・" という応答が帰ってくる)と違うのですが、それについては別途調べてみます。 -

質問者さんの方で問題の場所の切り分け等もっとできることがあるので、まずそれをやっていただけませんか?

回答者の方は頭の中での想像しかできないですが、質問者さんの方は実際にアプリを動かして、キャプチャツールやらデバッガなどを駆使して調べることができるはず。例えば、

(1) パケットキャプチャツールでブラウザ / サーバー間の応答と要求を見る。特に応答のコンテンツ。うまく行かない時と期待通りとなる時でどう違うかなど。

#前のスレッドで Fiddler2 が使えないとのことでしたが、Firefox を使っているなら firebug で可能です。Web アプリ開発でキャプチャツールが使えないというのは、デバッガが使えないと言っているのと同じなので、使う努力をしましょう。

(2) 開発マシンの IIS 上で実行して、デバッガで呼び出しているメソッドの動きを見る。

ところで、アップされているコードは実際のものとは違ってどこか省略したと言うことはないですか? 実は WCF のメソッドで DB にアクセスに行っているとか? そういうことがあると、開発サーバーと IIS のワーカープロセスのアクセス権の違いで、開発サーバーでは動いたものの IIS では動かないということはあり得ます(と言うより、よくある話です)。

アクセス権のほかにもいろいろ問題があります。詳しくは以下の記事を見てください。

ASP.NET 開発サーバーと IIS
http://surferonwww.info/BlogEngine/post/2011/11/18/ASPNET-development-server-and-IIS.aspx

開発マシンでも開発マシンの IIS を使って開発すれば、上記のようなトラブルは未然に防ぐことができますので、今後はそうすることをお勧めします。

編集 履歴 (0)
  • ありがとうございます。
    開発マシンのIIS上で実行する方法として、IISにあらかじめWebApplicationを設定→VisualStudioのファイル→OpenWebSite→表示されるペインからローカルIISを選んでやってみました。また、FirefoxのFirebugの「ネット」タブを観察してみました。
    -
  • リクエストヘッダのRefererが異なっています。また、IISの場合、NetworkErrorの404が出ています。 -
  • NetworkErrorの404 というのは HTTP 404 応答(ファイルまたはディレクトリが見つからない)のことですよね? -
  • はいそうです。404 Not Foundと出ていました。 -
  • 余談ですが、WCFの中でDBアクセスを行うコードを加えたところ、またIISではエラーになるという事象となりました。コメントとリンク先ページで述べられていたので、DBに対するアクセス権の問題ということですぐに気づきました。(500のInternalErrorでした…) -
ウォッチ

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