Pemeriksa Pesan
Sampel MessageInspectors menunjukkan cara mengimplementasikan dan mengonfigurasi pemeriksa pesan klien dan layanan.
Pemeriksa pesan adalah objek ekstensibilitas yang dapat digunakan dalam runtime bahasa umum klien model layanan dan mengirimkan runtime bahasa umum secara terprogram atau melalui konfigurasi dan yang dapat memeriksa dan mengubah pesan setelah diterima atau sebelum dikirim.
Sampel ini mengimplementasikan mekanisme validasi pesan klien dan layanan dasar yang memvalidasi pesan masuk terhadap sekumpulan dokumen Skema XML yang dapat dikonfigurasi. Perhatikan bahwa sampel ini tidak memvalidasi pesan untuk setiap operasi. Hal ini adalah penyederhanaan yang disengaja.
Pemeriksa Pesan
Pemeriksa pesan klien mengimplementasikan antarmuka IClientMessageInspector dan pemeriksa pesan layanan mengimplementasikan antarmuka IDispatchMessageInspector. Implementasi dapat digabungkan menjadi satu kelas untuk membentuk pemeriksa pesan yang berfungsi untuk kedua belah pihak. Sampel ini mengimplementasikan pemeriksa pesan gabungan tersebut. Inspektur dibangun melewati serangkaian skema di mana pesan masuk dan keluar divalidasi dan memungkinkan pengembang untuk menentukan apakah pesan masuk atau keluar divalidasi dan apakah pemeriksa berada dalam mode pengiriman atau klien, yang memengaruhi penanganan kesalahan seperti yang dibahas nanti dalam topik ini.
public class SchemaValidationMessageInspector : IClientMessageInspector, IDispatchMessageInspector
{
XmlSchemaSet schemaSet;
bool validateRequest;
bool validateReply;
bool isClientSide;
[ThreadStatic]
bool isRequest;
public SchemaValidationMessageInspector(XmlSchemaSet schemaSet,
bool validateRequest, bool validateReply, bool isClientSide)
{
this.schemaSet = schemaSet;
this.validateReply = validateReply;
this.validateRequest = validateRequest;
this.isClientSide = isClientSide;
}
Setiap pemeriksa pesan layanan (dispatcher) harus menerapkan dua metode IDispatchMessageInspector yaitu AfterReceiveRequest dan BeforeSendReply(Message, Object).
AfterReceiveRequest dipanggil oleh dispatcher ketika pesan telah diterima, diproses oleh tumpukan saluran dan ditetapkan ke layanan, tetapi sebelum dideserialisasi dan dikirim ke operasi. Jika pesan masuk dienkripsi, pesan sudah didekripsi saat mencapai pemeriksa pesan. Metode ini mendapatkan pesan request
yang diteruskan sebagai parameter referensi, yang memungkinkan pesan diperiksa, dimanipulasi, atau diganti sesuai kebutuhan. Nilai yang dikembalikan dapat berupa objek apa pun dan digunakan sebagai objek status korelasi yang diteruskan ke BeforeSendReply ketika layanan mengembalikan balasan ke pesan saat ini. Dalam sampel ini, AfterReceiveRequest mendelegasikan inspeksi (validasi) pesan ke metode privat, lokal ValidateMessageBody
, dan tidak mengembalikan objek status korelasi. Metode ini memastikan bahwa tidak ada pesan yang tidak valid yang masuk ke layanan.
object IDispatchMessageInspector.AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
if (validateRequest)
{
// inspect the message. If a validation error occurs,
// the thrown fault exception bubbles up.
ValidateMessageBody(ref request, true);
}
return null;
}
BeforeSendReply(Message, Object) dipanggil setiap kali balasan siap dikirim kembali ke klien, atau dalam kasus pesan satu arah, ketika pesan masuk telah diproses. Hal ini memungkinkan ekstensi untuk mengandalkan dipanggil secara simetris, terlepas dari MEP. Seperti halnya AfterReceiveRequest, pesan diteruskan sebagai parameter referensi dan dapat diperiksa, dimodifikasi, atau diganti. Validasi pesan yang dilakukan dalam sampel ini kembali didelegasikan ke metode ValidMessageBody
, tetapi penanganan kesalahan validasi sedikit berbeda dalam hal ini.
Jika terjadi kesalahan validasi pada layanan, metode ValidateMessageBody
akan menampilkan pengecualian yang diturunkan ke FaultException. Dalam AfterReceiveRequest, pengecualian ini dapat dimasukkan ke dalam infrastruktur model layanan di mana mereka secara otomatis diubah menjadi kesalahan SOAP dan disampaikan ke klien. Dalam BeforeSendReply, pengecualian FaultException tidak boleh dimasukkan ke dalam infrastruktur, karena transformasi pengecualian kesalahan yang ditampilkan oleh layanan terjadi sebelum pemeriksa pesan dipanggil. Oleh karena itu implementasi berikut menangkap pengecualian ReplyValidationFault
yang diketahui dan mengganti pesan balasan dengan pesan kesalahan eksplisit. Metode ini memastikan bahwa tidak ada pesan yang tidak valid yang dikembalikan oleh implementasi layanan.
void IDispatchMessageInspector.BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
if (validateReply)
{
// Inspect the reply, catch a possible validation error
try
{
ValidateMessageBody(ref reply, false);
}
catch (ReplyValidationFault fault)
{
// if a validation error occurred, the message is replaced
// with the validation fault.
reply = Message.CreateMessage(reply.Version,
fault.CreateMessageFault(), reply.Headers.Action);
}
}
Pemeriksa pesan klien sangat mirip. Dua metode yang harus diimplementasikan dari IClientMessageInspector adalah AfterReceiveReply dan BeforeSendRequest.
BeforeSendRequest dipanggil ketika pesan telah disusun baik oleh aplikasi klien atau oleh pemformat operasi. Seperti halnya pemeriksa pesan dispatcher, pesan hanya dapat diperiksa atau diganti sepenuhnya. Dalam sampel ini, inspektur mendelegasikan ke metode pembantu lokal ValidateMessageBody
yang sama yang juga digunakan untuk pemeriksa pesan pengiriman.
Perbedaan perilaku antara validasi klien dan layanan (seperti yang ditentukan dalam konstruktor) adalah bahwa validasi klien menampilkan pengecualian lokal yang dimasukkan ke dalam kode pengguna karena terjadi secara lokal dan bukan karena kegagalan layanan. Umumnya, aturannya adalah bahwa pemeriksa dispatcher layanan menampilkan kesalahan dan bahwa pemeriksa klien menampilkan pengecualian.
Implementasi BeforeSendRequest ini memastikan bahwa tidak ada pesan yang tidak valid yang dikirim ke layanan.
object IClientMessageInspector.BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
if (validateRequest)
{
ValidateMessageBody(ref request, true);
}
return null;
}
Implementasi AfterReceiveReply
memastikan bahwa tidak ada pesan yang tidak valid yang diterima dari layanan yang disampaikan ke kode pengguna klien.
void IClientMessageInspector.AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
if (validateReply)
{
ValidateMessageBody(ref reply, false);
}
}
Inti dari pemeriksa pesan khusus ini adalah metode ValidateMessageBody
. Untuk melakukan pekerjaannya, hal ini membungkus validasi XmlReader di sekitar sub-pohon konten isi dari pesan yang diteruskan. Pembaca diisi dengan sekumpulan skema yang dipegang pemeriksa pesan dan panggilan balik validasi diatur ke delegasi yang mengacu pada InspectionValidationHandler
yang ditentukan bersama metode ini. Untuk melakukan validasi, pesan kemudian dibaca dan ditampung ke dalam aliran memori yang didukung XmlDictionaryWriter. Jika kesalahan validasi atau peringatan terjadi dalam proses, metode panggilan balik dipanggil.
Jika tidak ada kesalahan yang terjadi, pesan baru dibuat yang menyalin properti dan header dari pesan asli dan menggunakan infoset yang sekarang divalidasi dalam aliran memori, yang dibungkus dengan XmlDictionaryReader dan ditambahkan ke pesan pengganti.
void ValidateMessageBody(ref System.ServiceModel.Channels.Message message, bool isRequest)
{
if (!message.IsFault)
{
XmlDictionaryReaderQuotas quotas =
new XmlDictionaryReaderQuotas();
XmlReader bodyReader =
message.GetReaderAtBodyContents().ReadSubtree();
XmlReaderSettings wrapperSettings =
new XmlReaderSettings();
wrapperSettings.CloseInput = true;
wrapperSettings.Schemas = schemaSet;
wrapperSettings.ValidationFlags =
XmlSchemaValidationFlags.None;
wrapperSettings.ValidationType = ValidationType.Schema;
wrapperSettings.ValidationEventHandler += new
ValidationEventHandler(InspectionValidationHandler);
XmlReader wrappedReader = XmlReader.Create(bodyReader,
wrapperSettings);
// pull body into a memory backed writer to validate
this.isRequest = isRequest;
MemoryStream memStream = new MemoryStream();
XmlDictionaryWriter xdw =
XmlDictionaryWriter.CreateBinaryWriter(memStream);
xdw.WriteNode(wrappedReader, false);
xdw.Flush(); memStream.Position = 0;
XmlDictionaryReader xdr =
XmlDictionaryReader.CreateBinaryReader(memStream, quotas);
// reconstruct the message with the validated body
Message replacedMessage =
Message.CreateMessage(message.Version, null, xdr);
replacedMessage.Headers.CopyHeadersFrom(message.Headers);
replacedMessage.Properties.CopyProperties(message.Properties);
message = replacedMessage;
}
}
Metode InspectionValidationHandler
ini dipanggil dengan memvalidasi XmlReader setiap kali terjadi kesalahan validasi skema atau peringatan. Implementasi berikut hanya berfungsi dengan kesalahan dan mengabaikan semua peringatan.
Pada pertimbangan pertama, tampaknya mungkin untuk menyuntikkan validasi XmlReader ke dalam pesan dengan pemeriksa pesan dan membiarkan validasi terjadi saat pesan diproses dan tanpa buffer pesan. Namun, itu berarti bahwa panggilan balik ini menampilkan pengecualian validasi di suatu tempat ke infrastruktur model layanan atau kode pengguna karena node XML yang tidak valid dideteksi, menghasilkan perilaku yang tidak dapat diprediksi. Pendekatan buffering melindungi kode pengguna dari pesan yang tidak valid, sepenuhnya.
Seperti yang dibahas sebelumnya, pengecualian yang ditampilkan oleh handler berbeda antara klien dan layanan. Pada layanan, pengecualian berasal dari FaultException, pada klien pengecualian adalah pengecualian kustom reguler.
void InspectionValidationHandler(object sender, ValidationEventArgs e)
{
if (e.Severity == XmlSeverityType.Error)
{
// We are treating client and service side validation errors
// differently here. Client side errors cause exceptions
// and are thrown straight up to the user code. Service side
// validations cause faults.
if (isClientSide)
{
if (isRequest)
{
throw new RequestClientValidationException(e.Message);
}
else
{
throw new ReplyClientValidationException(e.Message);
}
}
else
{
if (isRequest)
{
// this fault is caught by the ServiceModel
// infrastructure and turned into a fault reply.
throw new RequestValidationFault(e.Message);
}
else
{
// this fault is caught and turned into a fault message
// in BeforeSendReply in this class
throw new ReplyValidationFault(e.Message);
}
}
}
}
Perilaku
Pemeriksa pesan adalah ekstensi ke runtime bahasa umum klien atau runtime bahasa umum pengiriman. Ekstensi tersebut dikonfigurasi menggunakan perilaku. Perilaku adalah kelas yang mengubah perilaku runtime bahasa umum model layanan dengan mengubah konfigurasi default atau menambahkan ekstensi (seperti pemeriksa pesan) ke dalamnya.
Kelas SchemaValidationBehavior
berikut adalah perilaku yang digunakan untuk menambahkan pemeriksa pesan sampel ini ke klien atau mengirim runtime bahasa umum. Implementasinya agak mendasar dalam kedua kasus. Di ApplyClientBehavior dan ApplyDispatchBehavior, pemeriksa pesan dibuat dan ditambahkan ke koleksi MessageInspectors runtime bahasa umum masing-masing.
public class SchemaValidationBehavior : IEndpointBehavior
{
XmlSchemaSet schemaSet;
bool validateRequest;
bool validateReply;
public SchemaValidationBehavior(XmlSchemaSet schemaSet, bool
inspectRequest, bool inspectReply)
{
this.schemaSet = schemaSet;
this.validateReply = inspectReply;
this.validateRequest = inspectRequest;
}
#region IEndpointBehavior Members
public void AddBindingParameters(ServiceEndpoint endpoint,
System.ServiceModel.Channels.BindingParameterCollection
bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint,
System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
SchemaValidationMessageInspector inspector =
new SchemaValidationMessageInspector(schemaSet,
validateRequest, validateReply, true);
clientRuntime.MessageInspectors.Add(inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
System.ServiceModel.Dispatcher.EndpointDispatcher
endpointDispatcher)
{
SchemaValidationMessageInspector inspector =
new SchemaValidationMessageInspector(schemaSet,
validateRequest, validateReply, false);
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
}
public void Validate(ServiceEndpoint endpoint)
{
}
#endregion
}
Catatan
Perilaku khusus ini tidak ganda sebagai atribut dan oleh karena itu tidak dapat ditambahkan secara deklaratif ke jenis kontrak jenis layanan. Hal ini adalah keputusan berdasarkan desain yang dibuat karena koleksi skema tidak dapat dimuat dalam deklarasi atribut dan mengacu pada lokasi konfigurasi tambahan (misalnya ke pengaturan aplikasi) dalam atribut ini berarti membuat elemen konfigurasi yang tidak konsisten dengan konfigurasi model layanan lainnya. Oleh karena itu, perilaku ini hanya dapat ditambahkan secara imperatif melalui kode dan melalui ekstensi konfigurasi model layanan.
Menambahkan Pemeriksa Pesan melalui Konfigurasi
Untuk mengonfigurasi perilaku kustom pada titik akhir dalam file konfigurasi aplikasi, model layanan mengharuskan pelaksana untuk membuat elemen ekstensi konfigurasi yang diwakili oleh kelas yang berasal dari BehaviorExtensionElement. Ekstensi ini kemudian harus ditambahkan ke bagian konfigurasi model layanan untuk ekstensi seperti yang ditunjukkan untuk ekstensi berikut yang dibahas di bagian ini.
<system.serviceModel>
…
<extensions>
<behaviorExtensions>
<add name="schemaValidator" type="Microsoft.ServiceModel.Samples.SchemaValidationBehaviorExtensionElement, MessageInspectors, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
…
</system.serviceModel>
Ekstensi dapat ditambahkan dalam aplikasi atau file konfigurasi ASP.NET, yang merupakan pilihan paling umum, atau dalam file konfigurasi komputer.
Saat ekstensi ditambahkan ke cakupan konfigurasi, perilaku dapat ditambahkan ke konfigurasi perilaku seperti yang ditunjukkan dalam kode berikut. Konfigurasi perilaku adalah elemen yang dapat digunakan kembali yang dapat diterapkan ke beberapa titik akhir sesuai kebutuhan. Karena perilaku tertentu yang akan dikonfigurasi di sini mengimplementasikan IEndpointBehavior, perilaku tersebut hanya valid di bagian konfigurasi masing-masing dalam file konfigurasi.
<system.serviceModel>
<behaviors>
…
<endpointBehaviors>
<behavior name="HelloServiceEndpointBehavior">
<schemaValidator validateRequest="True" validateReply="True">
<schemas>
<add location="messages.xsd" />
</schemas>
</schemaValidator>
</behavior>
</endpointBehaviors>
…
</behaviors>
</system.serviceModel>
Elemen <schemaValidator>
yang mengonfigurasi pemeriksa pesan didukung oleh kelas SchemaValidationBehaviorExtensionElement
. Kelas ini mengekspos dua properti publik Boolean bernama ValidateRequest
dan ValidateReply
. Kedua hal ini ditandai dengan ConfigurationPropertyAttribute. Atribut ini merupakan tautan antara properti kode dan atribut XML yang dapat dilihat pada elemen konfigurasi XML sebelumnya. Kelas ini juga memiliki properti Schemas
yang juga ditandai dengan ConfigurationCollectionAttribute dan merupakan jenis SchemaCollection
, yang juga merupakan bagian dari sampel ini tetapi dihilangkan dari dokumen ini untuk singkatnya. Properti ini bersama dengan koleksi dan kelas elemen koleksi SchemaConfigElement
mendukung elemen <schemas>
dalam cuplikan konfigurasi sebelumnya dan memungkinkan penambahan koleksi skema ke set validasi.
Metode CreateBehavior
yang diambil alih mengubah data konfigurasi menjadi objek perilaku saat runtime bahasa umum mengevaluasi data konfigurasi saat membangun klien atau titik akhir.
public class SchemaValidationBehaviorExtensionElement : BehaviorExtensionElement
{
public SchemaValidationBehaviorExtensionElement()
{
}
public override Type BehaviorType
{
get
{
return typeof(SchemaValidationBehavior);
}
}
protected override object CreateBehavior()
{
XmlSchemaSet schemaSet = new XmlSchemaSet();
foreach (SchemaConfigElement schemaCfg in this.Schemas)
{
Uri baseSchema = new
Uri(AppDomain.CurrentDomain.BaseDirectory);
string location = new
Uri(baseSchema,schemaCfg.Location).ToString();
XmlSchema schema =
XmlSchema.Read(new XmlTextReader(location), null);
schemaSet.Add(schema);
}
return new
SchemaValidationBehavior(schemaSet,ValidateRequest,ValidateReply);
}
[ConfigurationProperty("validateRequest",DefaultValue=false,IsRequired=false)]
public bool ValidateRequest
{
get { return (bool)base["validateRequest"]; }
set { base["validateRequest"] = value; }
}
[ConfigurationProperty("validateReply", DefaultValue = false, IsRequired = false)]
public bool ValidateReply
{
get { return (bool)base["validateReply"]; }
set { base["validateReply"] = value; }
}
//Declare the Schema collection property.
//Note: the "IsDefaultCollection = false" instructs
//.NET Framework to build a nested section of
//the kind <Schema> ...</Schema>.
[ConfigurationProperty("schemas", IsDefaultCollection = true)]
[ConfigurationCollection(typeof(SchemasCollection),
AddItemName = "add",
ClearItemsName = "clear",
RemoveItemName = "remove")]
public SchemasCollection Schemas
{
get
{
SchemasCollection SchemasCollection =
(SchemasCollection)base["schemas"];
return SchemasCollection;
}
}
}
Menambahkan Pemeriksa Pesan Secara Imperatif
Kecuali melalui atribut (yang tidak didukung dalam sampel ini karena alasan yang dikutip sebelumnya) dan konfigurasi, perilaku dapat dengan mudah ditambahkan ke klien dan runtime bahasa umum layanan menggunakan kode imperatif. Dalam sampel ini, ini dilakukan dalam aplikasi klien untuk menguji pemeriksa pesan klien. Kelas GenericClient
berasal dari ClientBase<TChannel>, yang mengekspos konfigurasi titik akhir ke kode pengguna. Sebelum klien secara implisit dibuka, konfigurasi titik akhir dapat diubah, misalnya dengan menambahkan perilaku seperti yang ditunjukkan dalam kode berikut. Menambahkan perilaku pada layanan sebagian besar setara dengan teknik klien yang ditunjukkan di sini dan harus dilakukan sebelum host layanan dibuka.
try
{
Console.WriteLine("*** Call 'Hello' with generic client, with client behavior");
GenericClient client = new GenericClient();
// Configure client programmatically, adding behavior
XmlSchema schema = XmlSchema.Read(new StreamReader("messages.xsd"),
null);
XmlSchemaSet schemaSet = new XmlSchemaSet();
schemaSet.Add(schema);
client.Endpoint.Behaviors.Add(new
SchemaValidationBehavior(schemaSet, true, true));
Console.WriteLine("--- Sending valid client request:");
GenericCallValid(client, helloAction);
Console.WriteLine("--- Sending invalid client request:");
GenericCallInvalid(client, helloAction);
client.Close();
}
catch (Exception e)
{
DumpException(e);
}
Untuk menyiapkan, membangun, dan menjalankan sampel
Pastikan Anda telah melakukan Prosedur Penyiapan Satu Kali untuk Sampel Windows Communication Foundation.
Untuk membangun solusi, ikuti instruksi dalam Membangun Sampel Windows Communication Foundation.
Untuk menjalankan sampel dalam konfigurasi satu atau lintas komputer, ikuti instruksi pada Menjalankan Sampel WCF.