Pengendali Pesan HTTP pada Web API ASP.NET

Handler pesan adalah kelas yang menerima permintaan HTTP dan mengembalikan respons HTTP. Penangan pesan berasal dari kelas HttpMessageHandler abstrak.

Biasanya, serangkaian penanganan pesan ditautkan bersama-sama. Handler pertama menerima permintaan HTTP, melakukan beberapa pemrosesan, dan memberikan permintaan ke handler berikutnya. Pada titik tertentu, respons dibuat dan kembali ke atas rantai. Pola ini disebut penangan pendelegasian .

Diagram penanganan pesan yang dirangkai, mengilustrasikan proses menerima permintaan H T T P dan mengembalikan respons H T T P.

Penanganan Pesan Sisi Server

Di sisi server, alur API Web menggunakan beberapa penangan pesan bawaan:

  • HttpServer mendapatkan permintaan dari host.
  • HttpRoutingDispatcher mengirimkan permintaan berdasarkan rute.
  • HttpControllerDispatcher mengirimkan permintaan ke pengontrol API Web.

Anda dapat menambahkan handler kustom ke alur. Penangan pesan cocok untuk kekhawatiran lintas aspek yang beroperasi pada tingkat pesan HTTP (bukan pada tindakan pengontrol). Misalnya, handler pesan mungkin:

  • Membaca atau mengubah header permintaan.
  • Tambahkan header respons pada setiap respons.
  • Validasi permintaan sebelum mencapai pengontrol.

Diagram ini menunjukkan dua handler kustom yang dimasukkan ke dalam alur:

Diagram penangan pesan sisi server, menampilkan dua handler kustom yang dimasukkan ke dalam alur Web A P I.

Nota

Di sisi klien, HttpClient juga menggunakan handler pesan. Untuk informasi selengkapnya, lihat Penanganan Pesan HttpClient.

Pengelola Pesan Kustom

Untuk menulis handler pesan kustom, turunkan dari System.Net.Http.DelegatingHandler dan override metode SendAsync. Metode ini memiliki tanda tangan berikut:

Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken);

Metode ini mengambil HttpRequestMessage sebagai input dan secara asinkron mengembalikan HttpResponseMessage. Implementasi umum melakukan hal berikut:

  1. Proses pesan permintaan.
  2. Panggil base.SendAsync untuk mengirim permintaan ke pengendali internal.
  3. Handler bagian dalam mengembalikan pesan respons. (Langkah ini asinkron.)
  4. Proses respons dan kembalikan ke pemanggil.

Berikut adalah contoh sepele:

public class MessageHandler1 : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Debug.WriteLine("Process request");
        // Call the inner handler.
        var response = await base.SendAsync(request, cancellationToken);
        Debug.WriteLine("Process response");
        return response;
    }
}

Nota

Panggilan ke base.SendAsync asinkron. Jika handler melakukan pekerjaan apa pun setelah panggilan ini, gunakan kata kunci await, seperti yang ditunjukkan.

Handler yang mendelegasikan juga dapat melewati handler internal dan langsung membuat respons.

public class MessageHandler2 : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // Create the response.
        var response = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent("Hello!")
        };

        // Note: TaskCompletionSource creates a task that does not contain a delegate.
        var tsc = new TaskCompletionSource<HttpResponseMessage>();
        tsc.SetResult(response);   // Also sets the task state to "RanToCompletion"
        return tsc.Task;
    }
}

Jika penangan pendelegasian membuat respons tanpa memanggil base.SendAsync, permintaan melewati sisa alur. Ini dapat berguna untuk handler yang memvalidasi permintaan (membuat respons kesalahan).

Diagram penanganan pesan kustom, menunjukkan proses untuk membuat respons tanpa memanggil base.SendAsync.

Menambahkan Handler ke Alur

Untuk menambahkan handler pesan di sisi server, tambahkan handler ke koleksi HttpConfiguration.MessageHandlers . Jika Anda menggunakan templat "ASP.NET MVC 4 Web Application" untuk membuat proyek, Anda dapat melakukan ini di dalam kelas WebApiConfig :

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MessageHandlers.Add(new MessageHandler1());
        config.MessageHandlers.Add(new MessageHandler2());

        // Other code not shown...
    }
}

Penangan pesan dipanggil dalam urutan yang sama dengan yang muncul di koleksi MessageHandlers . Karena strukturnya bersarang, pesan respons bergerak ke arah yang berlawanan. Artinya, handler terakhir adalah yang pertama mendapatkan pesan respons.

Perhatikan bahwa Anda tidak perlu mengatur penangan internal; kerangka API Web secara otomatis menyambungkan penangan pesan.

Jika Anda menghost sendiri, buat instans kelas HttpSelfHostConfiguration dan tambahkan handler ke koleksi MessageHandlers .

var config = new HttpSelfHostConfiguration("http://localhost");
config.MessageHandlers.Add(new MessageHandler1());
config.MessageHandlers.Add(new MessageHandler2());

Sekarang mari kita lihat beberapa contoh penangan pesan kustom.

Contoh: X-HTTP-Method-Override

X-HTTP-Method-Override adalah header HTTP yang tidak standar. Ini dirancang untuk klien yang tidak dapat mengirim jenis permintaan HTTP tertentu, seperti PUT atau DELETE. Sebagai gantinya, klien mengirim permintaan POST dan mengatur header X-HTTP-Method-Override ke metode yang diinginkan. Contohnya:

X-HTTP-Method-Override: PUT

Berikut adalah handler pesan yang menambahkan dukungan untuk X-HTTP-Method-Override:

public class MethodOverrideHandler : DelegatingHandler      
{
    readonly string[] _methods = { "DELETE", "HEAD", "PUT" };
    const string _header = "X-HTTP-Method-Override";

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // Check for HTTP POST with the X-HTTP-Method-Override header.
        if (request.Method == HttpMethod.Post && request.Headers.Contains(_header))
        {
            // Check if the header value is in our methods list.
            var method = request.Headers.GetValues(_header).FirstOrDefault();
            if (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase))
            {
                // Change the request method.
                request.Method = new HttpMethod(method);
            }
        }
        return base.SendAsync(request, cancellationToken);
    }
}

Dalam metode SendAsync , handler memeriksa apakah pesan permintaan adalah permintaan POST, dan apakah berisi header X-HTTP-Method-Override. Jika demikian, ini memvalidasi nilai header, lalu memodifikasi metode permintaan. Terakhir, handler memanggil base.SendAsync untuk meneruskan pesan ke handler berikutnya.

Ketika permintaan mencapai kelas HttpControllerDispatcher , HttpControllerDispatcher akan merutekan permintaan berdasarkan metode permintaan yang diperbarui.

Contoh: Menambahkan Header Respons Kustom

Berikut adalah handler pesan yang menambahkan header kustom ke setiap pesan respons:

// .Net 4.5
public class CustomHeaderHandler : DelegatingHandler
{
    async protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
        response.Headers.Add("X-Custom-Header", "This is my custom header.");
        return response;
    }
}

Pertama, pengendali memanggil base.SendAsync untuk meneruskan permintaan ke pengendali pesan internal. Handler bagian dalam mengembalikan pesan balasan secara asinkron menggunakan objek Task<T>. Pesan respons tidak tersedia sampai base.SendAsync selesai secara asinkron.

Contoh ini menggunakan kata kunci tunggu untuk melakukan pekerjaan secara asinkron setelah SendAsync selesai. Jika Anda menargetkan .NET Framework 4.0, gunakan metode Task<T>.ContinueWith:

public class CustomHeaderHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return base.SendAsync(request, cancellationToken).ContinueWith(
            (task) =>
            {
                HttpResponseMessage response = task.Result;
                response.Headers.Add("X-Custom-Header", "This is my custom header.");
                return response;
            }
        );
    }
}

Contoh: Memeriksa Kunci API

Beberapa layanan web mengharuskan klien untuk menyertakan kunci API dalam permintaan mereka. Contoh berikut menunjukkan bagaimana handler pesan dapat memeriksa permintaan untuk kunci API yang valid:

public class ApiKeyHandler : DelegatingHandler
{
    public string Key { get; set; }

    public ApiKeyHandler(string key)
    {
        this.Key = key;
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (!ValidateKey(request))
        {
            var response = new HttpResponseMessage(HttpStatusCode.Forbidden);
            var tsc = new TaskCompletionSource<HttpResponseMessage>();
            tsc.SetResult(response);    
            return tsc.Task;
        }
        return base.SendAsync(request, cancellationToken);
    }

    private bool ValidateKey(HttpRequestMessage message)
    {
        var query = message.RequestUri.ParseQueryString();
        string key = query["key"];
        return (key == Key);
    }
}

Handler ini mencari kunci API dalam string kueri URI. (Untuk contoh ini, kami berasumsi bahwa kunci adalah string statis. Implementasi nyata mungkin akan menggunakan validasi yang lebih kompleks.) Jika string kueri berisi kunci, handler meneruskan permintaan ke handler dalam.

Jika permintaan tidak memiliki kunci yang valid, handler membuat pesan respons dengan status 403, Terlarang. Dalam kasus ini, handler tidak memanggil base.SendAsync, jadi handler internal tidak pernah menerima permintaan, begitu pula pengontrol. Oleh karena itu, pengontrol dapat mengasumsikan bahwa semua permintaan masuk memiliki kunci API yang valid.

Nota

Jika kunci API hanya berlaku untuk tindakan pengontrol tertentu, pertimbangkan untuk menggunakan filter tindakan alih-alih penangan pesan. Filter aksi dijalankan setelah perutean URI dilakukan.

Penangan Pesan Per-Route

Handler di koleksi HttpConfiguration.MessageHandlers berlaku secara global.

Atau, Anda dapat menambahkan handler pesan ke rute tertentu saat menentukan rute:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute(
            name: "Route1",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        config.Routes.MapHttpRoute(
            name: "Route2",
            routeTemplate: "api2/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional },
            constraints: null,
            handler: new MessageHandler2()  // per-route message handler
        );

        config.MessageHandlers.Add(new MessageHandler1());  // global message handler
    }
}

Dalam contoh ini, jika URI permintaan cocok dengan "Route2", permintaan dikirim ke MessageHandler2. Diagram berikut menunjukkan alur untuk dua rute ini:

Diagram alur penangan pesan per rute, mengilustrasikan proses untuk menambahkan penangan pesan ke rute tertentu dengan menentukan rute.

Perhatikan bahwa MessageHandler2 mengganti httpControllerDispatcher default. Dalam contoh ini, MessageHandler2 membuat respons, dan permintaan yang cocok dengan "Route2" tidak pernah masuk ke pengontrol. Ini memungkinkan Anda mengganti seluruh mekanisme pengontrol API Web dengan titik akhir kustom Anda sendiri.

Atau, handler pesan per rute dapat mendelegasikan ke HttpControllerDispatcher, yang kemudian dikirim ke pengontrol.

Diagram alur penangan pesan per rute, memperlihatkan proses untuk mendelegasikan ke h t t p Controller Dispatcher, yang kemudian dikirim ke pengontrol.

Kode berikut menunjukkan cara mengonfigurasi rute ini:

// List of delegating handlers.
DelegatingHandler[] handlers = new DelegatingHandler[] {
    new MessageHandler3()
};

// Create a message handler chain with an end-point.
var routeHandlers = HttpClientFactory.CreatePipeline(
    new HttpControllerDispatcher(config), handlers);

config.Routes.MapHttpRoute(
    name: "Route2",
    routeTemplate: "api2/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional },
    constraints: null,
    handler: routeHandlers
);