IDispatchMessageFormatter でフォーマットする前に、Operation Behavior を使用して基になるメッセージを変更することで、これをクラックしたと思います。
次のコードは、WCF ファイルレス アクティベーションに基づくサービスに対するソリューションを提供します。
IOperationBehavior を Attribute クラスの形式でライブにしたかったのです。次に、各サービス操作を新しい属性で装飾するだけで、その操作の IOperationBehavior が開始されます。これは、エンド ユーザーにとって非常に便利でシンプルです。
重要な問題は、動作を適用する場所です。これは重要です。属性を介して動作を適用するときに WCF によって呼び出される操作動作の順序は、サービス ホストで適用する場合とは異なります。属性に基づく順序は次のとおりです。
- System.ServiceModel.Dispatcher.OperationInvokerBehavior
- MyOperationBehaviorAttribute
- System.ServiceModel.OperationBehaviorAttribute
- System.ServiceModel.Description.DataContractSerializerOperationBehavior
- System.ServiceModel.Description.DataContractSerializerOperationGenerator
何らかの理由で、DataContractSerializerOperationBehavior の前に操作動作 (属性を使用して適用された場合のみ) が呼び出されます。メッセージを調整した後 (コードを参照)、フォーマッタ内の DataContractSerializerOperationBehavior フォーマッタ (内部フォーマッタとして動作に渡される) に逆シリアル化を委任したいので、これは問題です。Microsoft が完全に優れたデシリアライザーを既に提供している場合、デシリアライゼーション ルーチンを書き直す必要はありません。最初のインスタンスで XML を修正するだけで、空白が XML 内で正しく表現される null に変換され、DataContractSerializer がそれらをサービス インターフェイスで null 許容型に結び付けることができます。
したがって、これは属性ベースの動作を意図したとおりに使用できないことを意味します。なぜなら、この現象の理由が見当たらないため、ここではかなり微妙な方法で WCF が壊れている可能性があるからです。そのため、IOperationBehavior を操作に追加することもできます。サービス ホストの作成段階で手動で割り当てる必要があるだけです。これは、IOperationBehavior が「正しい」シーケンスに挿入されるためです。つまり、DataContractSerializerOperationBehavior が作成された後です。内部フォーマッタへの参照を取得できますか。
// This operation behaviour changes the formatter for a specific set of operations in a web service.
[System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = false)]
public class NullifyEmptyElementsAttribute : Attribute
{
// just a marker, does nothing
}
public class NullifyEmptyElementsBahavior : IOperationBehavior
{
#region IOperationBehavior Members
public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) { }
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
// we are the server, we need to accept client message that omit the xsi:nill on empty elements
dispatchOperation.Formatter = new NullifyEmptyElementsFormatter(dispatchOperation.Formatter);
}
public void Validate(OperationDescription operationDescription) { }
#endregion IOperationBehavior Members
}
/// <summary>
/// This customized formatter intercepts the deserialization process to perform extra processing.
/// </summary>
public class NullifyEmptyElementsFormatter : IDispatchMessageFormatter
{
// Hold on to the original formatter so we can use it to return values for method calls we don't need.
private IDispatchMessageFormatter _innerFormatter;
public NullifyEmptyElementsFormatter(IDispatchMessageFormatter innerFormatter)
{
// Save the original formatter
_innerFormatter = innerFormatter;
}
/// <summary>
/// Check each node and add the xsi{namespace}:nil declaration if the inner text is blank
/// </summary>
public static void MakeNillable(XElement element)
{
XName _nillableAttributeName = "{http://www.w3.org/2001/XMLSchema-instance}nil"; // don't worry, the namespace is what matters, not the alias, it will work
if (!element.HasElements) // only end nodes
{
var hasNillableAttribute = element.Attribute(_nillableAttributeName) != null;
if (string.IsNullOrEmpty(element.Value))
{
if (!hasNillableAttribute)
element.Add(new XAttribute(_nillableAttributeName, true));
}
else
{
if (hasNillableAttribute)
element.Attribute(_nillableAttributeName).Remove();
}
}
}
public void DeserializeRequest(System.ServiceModel.Channels.Message message, object[] parameters)
{
var buffer = message.CreateBufferedCopy(int.MaxValue);
var messageSource = buffer.CreateMessage(); // don't affect the underlying stream
XDocument doc = null;
using (var messageReader = messageSource.GetReaderAtBodyContents())
{
doc = XDocument.Parse(messageReader.ReadOuterXml()); // few issues with encoding here (strange bytes at start of string), this technique resolves that
}
foreach (var element in doc.Descendants())
{
MakeNillable(element);
}
// create a new message with our corrected XML
var messageTarget = Message.CreateMessage(messageSource.Version, null, doc.CreateReader());
messageTarget.Headers.CopyHeadersFrom(messageSource.Headers);
// now delegate the work to the inner formatter against our modified message, its the parameters were after
_innerFormatter.DeserializeRequest(messageTarget, parameters);
}
public System.ServiceModel.Channels.Message SerializeReply(System.ServiceModel.Channels.MessageVersion messageVersion, object[] parameters, object result)
{
// Just delegate this to the inner formatter, we don't want to do anything with this.
return _innerFormatter.SerializeReply(messageVersion, parameters, result);
}
}
public class MyServiceHost : ServiceHost
{
public MyServiceHost(Type serviceType, params Uri[] baseAddresses)
: base(serviceType, baseAddresses) { }
protected override void OnOpening()
{
base.OnOpening();
foreach (var endpoint in this.Description.Endpoints)
{
foreach (var operation in endpoint.Contract.Operations)
{
if ((operation.BeginMethod != null && operation.BeginMethod.GetCustomAttributes(_NullifyEmptyElementsBahaviorAttributeType, false).Length > 0)
||
(operation.SyncMethod != null && operation.SyncMethod.GetCustomAttributes(_NullifyEmptyElementsBahaviorAttributeType, false).Length > 0)
||
(operation.EndMethod != null && operation.EndMethod.GetCustomAttributes(_NullifyEmptyElementsBahaviorAttributeType, false).Length > 0))
{
operation.Behaviors.Add(new NullifyEmptyElementsBahavior());
}
}
}
}
}
おそらく、受信メッセージを変更しているだけなので、IDispatchMessageFormatter のアクティブ化順序への依存を削除する IDispatchMessageInspector を代わりに使用できます。しかし、これは今のところ機能します;)
使用法:
- オペレーションに追加
[ServiceContract(Namespace = Namespaces.MyNamespace)]
public interface IMyServiceContrct
{
[OperationContract]
[NullifyEmptyElements]
void MyDoSomthingMethod(string someIneteger);
}
- サービスに結びつける
A. .svc がある場合は、単に MyServiceHost を参照してください
<%@ ServiceHost
Language="C#"
Debug="true"
Service="MyNameSpace.MyService"
Factory="MyNameSpace.MyServiceHost" %>
B. ファイルレス アクティベーション サービスを使用している場合は、これを web.config ファイルに追加します。
<system.serviceModel>
... stuff
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" >
<!-- WCF File-less service activation - there is no need to use .svc files anymore, WAS in IIS7 creates a host dynamically - less config needed-->
<serviceActivations >
<!-- Full access to Internal services -->
<add relativeAddress="MyService.svc"
service="MyNameSpace.MyService"
factory="MyNameSpace.MyServiceHost" />
</serviceActivations>
</serviceHostingEnvironment>
... stuff
</system.serviceModel>