ASP.NET Web API'sindeki HTTP İleti İşleyicileri

İleti işleyicisi, HTTP isteği alan ve HTTP yanıtı döndüren bir sınıftır. İleti işleyicileri soyut HttpMessageHandler sınıfından türetilir.

Genellikle bir dizi ileti işleyicisi birbirine zincirlenir. İlk işleyici bir HTTP isteği alır, biraz işlem yapar ve isteği bir sonraki işleyiciye verir. Yanıt bir noktada oluşturulur ve zincirin yukarısına geri döner. Bu desene delegasyon işleyicisi adı verilir.

Bir H T T P isteği alma ve H T T P yanıtı döndürme işlemini gösteren, zincirlenmiş ileti işleyicilerinin diyagramı.

Sunucu Tarafı İleti İşleyicileri

Sunucu tarafında, Web API işlem hattı bazı yerleşik ileti işleyicileri kullanır:

  • HttpServer , isteği konaktan alır.
  • HttpRoutingDispatcher , isteği yola göre gönderir.
  • HttpControllerDispatcher isteği bir Web API denetleyicisine gönderir.

İşlem hattına özel işleyiciler ekleyebilirsiniz. İleti işleyicileri, HTTP iletileri düzeyinde (denetleyici eylemleri yerine) çalışan çapraz kesme endişeleri için iyidir. Örneğin, bir ileti işleyicisi şunları içerebilir:

  • İstek üst bilgilerini okuyun veya değiştirin.
  • Yanıtlara bir yanıt üst bilgisi ekleyin.
  • İstekleri denetleyiciye ulaşmadan önce doğrulayın.

Bu diyagram, işlem hattına eklenen iki özel işleyiciyi gösterir:

Web A P I işlem hattına eklenen iki özel işleyiciyi görüntüleyen sunucu tarafı ileti işleyicilerinin diyagramı.

Uyarı

İstemci tarafında, HttpClient ileti işleyicilerini de kullanır. Daha fazla bilgi için bkz. HttpClient İleti İşleyicileri.

Özel İleti İşleyicileri

Özel ileti işleyicisi yazmak için System.Net.Http.DelegatingHandler'dan türetin ve SendAsync yöntemini geçersiz kılın. Bu yöntem aşağıdaki imzaya sahiptir:

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

yöntemi giriş olarak bir HttpRequestMessage alır ve zaman uyumsuz olarak bir HttpResponseMessage döndürür. Tipik bir uygulama aşağıdakileri yapar:

  1. İstek iletisini işleme.
  2. İsteği iç işleyiciye göndermek için base.SendAsync çağrısı yap.
  3. İç işleyici bir yanıt iletisi döndürür. (Bu adım zaman uyumsuzdur.)
  4. Yanıtı işleyin ve çağırana geri gönderin.

Aşağıda önemsiz bir örnek verilmiştir:

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;
    }
}

Uyarı

base.SendAsync çağrısı zaman uyumsuzdur. İşleyici bu çağrıdan sonra herhangi bir iş yaparsa, gösterildiği gibi await anahtar sözcüğünü kullanın.

Temsilci belirleme işleyicisi ayrıca iç işleyiciyi atlayabilir ve yanıtı doğrudan oluşturabilir:

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;
    }
}

Temsilci atama işleyicisi base.SendAsync'yi çağırmadan yanıt oluşturursa, istek işlem hattının geri kalanını atlar. Bu, isteği doğrulayan bir işleyici için yararlı olabilir (hata yanıtı oluşturma).

Temel nokta Send Async çağrılmadan yanıt oluşturma işlemini gösteren özel ileti işleyicileri diyagramı.

İşlem Hattına İşleyici Ekleme

Sunucu tarafına bir ileti işleyicisi eklemek için işleyiciyi HttpConfiguration.MessageHandlers koleksiyonuna ekleyin. Projeyi oluşturmak için "ASP.NET MVC 4 Web Uygulaması" şablonunu kullandıysanız, bunu WebApiConfig sınıfı içinde yapabilirsiniz:

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

        // Other code not shown...
    }
}

İleti işleyicileri , MessageHandlers koleksiyonunda göründükleri sırayla çağrılır. İç içe oldukları için yanıt iletisi diğer yöne doğru ilerler. Yani, son işleyici yanıt iletisini alan ilk işleyicidir.

İç işleyicileri ayarlamanıza gerek olmadığını fark edin; Web API çerçevesi, ileti işleyicilerini otomatik olarak bağlar.

Kendi kendine barındırıyorsanız, HttpSelfHostConfiguration sınıfının bir örneğini oluşturun ve işleyicileri MessageHandlers koleksiyonuna ekleyin.

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

Şimdi bazı özel ileti işleyici örneklerine göz atalım.

Örnek: X-HTTP-Method-Override

X-HTTP-Method-Override standart olmayan bir HTTP üst bilgisidir. PUT veya DELETE gibi belirli HTTP istek türlerini gönderemeyen istemciler için tasarlanmıştır. Bunun yerine istemci bir POST isteği gönderir ve X-HTTP-Method-Override üst bilgisini istenen yönteme ayarlar. Örneğin:

X-HTTP-Method-Override: PUT

X-HTTP-Method-Override için destek ekleyen bir ileti işleyicisi aşağıdadır:

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);
    }
}

SendAsync yönteminde işleyici, istek iletisinin bir POST isteği olup olmadığını ve X-HTTP-Method-Override üst bilgisini içerip içermediğini denetler. Öyleyse, üst bilgi değerini doğrular ve ardından istek yöntemini değiştirir. Son olarak, işleyici iletiyi sonraki işleyiciye geçirmek için çağırır base.SendAsync .

İstek HttpControllerDispatcher sınıfına ulaştığında, HttpControllerDispatcher isteği güncelleştirilmiş istek yöntemine göre yönlendirir.

Örnek: Özel Yanıt Üst Bilgisi Ekleme

Her yanıt iletisine özel üst bilgi ekleyen bir ileti işleyicisi aşağıdadır:

// .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;
    }
}

İlk olarak, işleyici isteği iç ileti işleyicisine geçirmek için çağırır base.SendAsync . İç işleyici bir yanıt iletisi döndürür, ancak bunu bir Görev<T> nesnesi kullanarak zaman uyumsuz olarak yapar. Yanıt iletisi zaman uyumsuz olarak tamamlanana kadar base.SendAsync kullanılamaz.

Bu örnekte, tamamlandıktan sonra işi zaman uyumsuz olarak gerçekleştirmek için SendAsync anahtar sözcüğü kullanılır. .NET Framework 4.0'ı hedefliyorsanız Task<T>.ContinueWith yöntemini kullanın.

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;
            }
        );
    }
}

Örnek: API Anahtarını denetleme

Bazı web hizmetleri, istemcilerin isteklerine bir API anahtarı eklemesini gerektirir. Aşağıdaki örnekte, bir ileti işleyicisinin geçerli bir API anahtarı için istekleri nasıl denetleyebileceği gösterilmektedir:

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);
    }
}

Bu işleyici URI sorgu dizesinde API anahtarını arar. (Bu örnekte, anahtarın statik bir dize olduğunu varsayarız. Gerçek bir uygulama büyük olasılıkla daha karmaşık doğrulama kullanır.) Sorgu dizesi anahtarı içeriyorsa, işleyici isteği iç işleyiciye geçirir.

İstek geçerli bir anahtara sahip değilse, işleyici durum 403, Yasak olan bir yanıt iletisi oluşturur. Bu durumda işleyici base.SendAsync çağrısı yapmaz, bu nedenle iç işleyici isteği almaz ve denetleyici de almaz. Bu nedenle denetleyici, tüm gelen isteklerin geçerli bir API anahtarına sahip olduğunu varsayabilir.

Uyarı

API anahtarı yalnızca belirli denetleyici eylemleri için geçerliyse, ileti işleyicisi yerine eylem filtresi kullanmayı göz önünde bulundurun. Eylem filtreleri, URI yönlendirmesi gerçekleştirildikten sonra çalışır.

Per-Route İleti İşleyicileri

HttpConfiguration.MessageHandlers koleksiyonundaki işleyiciler genel olarak uygulanmaktadır.

Alternatif olarak, yolu tanımlarken belirli bir yola ileti işleyicisi ekleyebilirsiniz:

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
    }
}

Bu örnekte, istek URI'sinin "Route2" ile eşleşmesi durumunda istek adresine MessageHandler2gönderilir. Aşağıdaki diyagramda bu iki yol için işlem hattı gösterilmektedir:

Yol başına ileti işleyicileri işlem hattının diyagramı, yolu tanımlayarak belirli bir yola ileti işleyicisi ekleme işlemini gösterir.

Varsayılan HttpControllerDispatcher değerinin MessageHandler2 ile değiştirildiğini fark edin. Bu örnekte yanıt MessageHandler2 oluşturulur ve "Route2" ile eşleşen istekler hiçbir zaman denetleyiciye gitmez. Bu, Web API denetleyicisi mekanizmasının tamamını kendi özel uç noktanızla değiştirmenize olanak tanır.

Alternatif olarak, her rota için bir ileti işleyicisi, HttpControllerDispatcher'a devredebilir ve ardından bir denetleyiciye yönlendirir.

Her rota için ileti işleyicileri işlem hattını gösteren ve işlemi HTTP Denetleyici Dağıtıcısı'na devredip ardından bir denetleyiciye aktaran diyagram.

Aşağıdaki kodda bu yolun nasıl yapılandırılır gösterilmektedir:

// 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
);