taediumの日記

2010-10-09

[][][][] jQueryを使ってWCFのサービスにアクセス

Using jQuery to directly call ASP.NET AJAX page methodsで紹介されているようにPageMethodを使うのがとてもシンプルでいいと思うのですが、PageMethodはstaticメソッドじゃないといけないのでMEFで管理されたインスタンスを取得するの自分でルックアップしないといけないんですよね。それもひとつの手ですが、WCFを使えば前のエントリでつくったMefServiceBehaviorを使って依存性を解決できます。

サンプル

かんたんなサンプルで動作を確認してみました。

サービスコントラクト

Visual StudioのItemからAJAX-enabled WCF Serviceを追加します。そして、次のように記述します。前のエントリで作ったMefServiceBehaviorを追加しているのとプロパティでGreetingをImportしているのがポイント。

namespace WebApplication2
{
    [MefServiceBehavior]
    [ServiceContract(Namespace = "")]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class GreetingService
    {
        [Import]
        public Greeting Greeting { get; set; }

        [OperationContract]
        public string Hello()
        {
            return Greeting.Hello();
        }
    }
}

web.config

AJAX-enabled WCF Serviceを追加するとweb.configに自動で次のような記述が追加されます。

  <system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="WebApplication2.GreetingServiceAspNetAjaxBehavior">
          <enableWebScript />
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
      multipleSiteBindingsEnabled="true" />
    <services>
      <service name="WebApplication2.GreetingService">
        <endpoint address="" behaviorConfiguration="WebApplication2.GreetingServiceAspNetAjaxBehavior"
          binding="webHttpBinding" contract="WebApplication2.GreetingService" />
      </service>
    </services>
  </system.serviceModel>

MEFでExportされるクラス

クラスにはExport属性をつけておきます。

namespace WebApplication2
{
    [Export(typeof(Greeting))]
    public class Greeting
    {
        public string Hello()
        {
            return "Hello!!!";
        }
    }
}

サーバー側は以上。


Default.aspx

aspxやhtmlではjQueryを使ってアクセスできます。

<div id="result">result</div>
<script type="text/javascript">
    $(document).ready(function () {
        $("#result").click(function () {
            $.ajax({
                type: "POST",
                url: "GreetingService.svc/Hello",
                data: "{}",
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                success: function (msg) {
                    $("#result").text(msg.d);
                }
            });
        });
    });
</script>


WCFから返されるmsgオブジェクトのdというプロパティが気になる場合はこちらに説明があります。

ASP.NETによるJsonハイジャック対策のようです。

まとめ

  • WCFに対してjQueryでアクセスできる。
  • PageMethodを使う方法のほうがシンプルだが、WCFはMEFなどのDIコンテナと統合させやすいのがメリット。
  • WebFormを使わない場合でもこの方法が利用できる。

WebFormやMVCなくてもjQueryとWCFでWebアプリつくれそう。でも、数が増えるとweb.configの設定が面倒くさそうな気がします。

追記

web.configに設定しない方法も提供されていました。

マークアップのコードを開いて以下の設定でためしたところOKでした(GreetingService2は上記のGreetingServiceのweb.configの設定がないバージョン)。

<%@ ServiceHost 
Language="C#" 
Debug="true" 
Service="WebApplication2.GreetingService2"
CodeBehind="GreetingService2.svc.cs"
Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory" %>


[][][] WCFのサービスにMEFを使って依存性注入

以下の記事を参考にしました。

この記事はMEFではなくPIABを使っていますが、好きなDIコンテナに置き換えて読めると思います。

簡単なサンプル

簡単なサンプルを作って実際に動かしてみました。

サービスコントラクト

[ServiceContract]
public interface IService1
{
    [OperationContract]
    string Hello();
}

サービスコントラクトの実装

MEFがフックできるようにMefServiceBehavior属性がついているのがポイント。Import属性がついたプロパティはMEFによって設定されます。

[MefServiceBehavior]
public class Service1 : IService1
{
    [Import]
    public Greeting Greeting { get; set; }

    public string Hello()
    {
        return Greeting.Hello();
    }
}

MEFによってエキスポートされるクラス

エキスポートされてサービスコントラクトの実装にインポートされます。

[Export(typeof(Greeting))]
public class Greeting
{
    public string Hello()
    {
        return "Hello!!!";
    }
}

Service1でGreetingインスタンスを呼び出したいわけですが、デフォルトのままではGreetingはServic1にImporされないので、そのために必要なクラスを以下に作ります。


サービスのインスタンスを提供するクラス。

WCFの拡張ポイント。

public class MefInstanceProvider : IInstanceProvider
{
    public CompositionContainer Container { get; private set; }

    public MefInstanceProvider()
    {
        Container = new CompositionContainer(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
    }

    public object GetInstance(InstanceContext instanceContext)
    {
        return GetInstance(instanceContext, null);
    }

    public object GetInstance(InstanceContext instanceContext, Message message)
    {
        var type = instanceContext.Host.Description.ServiceType;
        if (type == null)
        {
            throw new InvalidOperationException();
        }
        var instance = Activator.CreateInstance(type);
        Container.SatisfyImportsOnce(instance);
        return instance;
    }

    public void ReleaseInstance(InstanceContext instanceContext, object instance)
    {
        var disposable = instance as IDisposable;
        if (disposable != null)
        {
            disposable.Dispose();
        }
    }
}

サービスコントラクトを制御する属性。

WCFの拡張ポイント。サービスコントラクトもしくはサービスコントラクトの実装クラスに指定できます。

[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class)]
public class MefServiceBehavior : Attribute, IContractBehavior, IContractBehaviorAttribute
{
    public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
    {
    }

    public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
    {
        dispatchRuntime.InstanceProvider = new MefInstanceProvider();
    }

    public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
    }

    public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public Type TargetContract
    {
        get
        {
            return null;
        }
    }
}

クライアント

たとえばコンソールアプリからIISにホストされたWCFを呼び出せます。

class Program
{
    static void Main(string[] args)
    {
        var client = new ServiceReference1.Service1Client();
        var result = client.Hello();  // Hello!!!
        Console.WriteLine(result);
        Console.ReadKey();
    }
}

コンソールには Hello!!! と出力されます。

まとめ

意外と簡単に実現できることがわかりました。

MefServiceBehaviorみたいな属性をわざわざすべてのサービスコントラクトの実装に記述したくないという場合はServiceHostFactoryを使えるかも。ServiceHostFactoryを使ってUnityとWCFを組み合わせているわかりやすい例がありました。

でも、これ全部のsvcファイルに記述しないといけないんでしょうか?どこか一箇所だけ修正すればいいなら使えますが、全部書くなら属性で指定するのとそんなに変わんないですね。