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. Bir noktada yanıt oluşturulur ve zincire geri gider. Bu desene temsilci 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, birbirine zincirlenmiş ileti işleyicilerinin diyagramı.

Server-Side İleti İşleyicileri

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

  • HttpServer , isteği konaktan alır.
  • HttpRoutingDispatcher , isteği yola göre dağıtır.
  • 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ı verebilir:

  • İ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ı.

Not

İ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 çağrısı base.SendAsync .
  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;
    }
}

Not

çağrısı base.SendAsync 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 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 çağırmadan base.SendAsyncyanı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şleyicilerinin 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önde 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 bakalı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. Örnek:

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 post isteği olup olmadığını ve X-HTTP-Method-Override üst bilgisini içerip içermediğini denetler. Bu durumda, üst bilgi değerini doğrular ve ardından istek yöntemini değiştirir. Son olarak işleyici, iletiyi bir sonraki işleyiciye geçirmek için çağrısında base.SendAsync bulunur.

İ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ısında base.SendAsync bulunur. İç 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 SendAsync çalışmayı zaman uyumsuz olarak gerçekleştirmek için await anahtar sözcüğü kullanılır. .NET Framework 4.0'ı hedefliyorsanız Görev<T'sini> kullanın. ContinueWith yöntemi:

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ına yönelik 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.

İsteğin geçerli bir anahtarı yoksa, işleyici 403, Yasak durumunda bir yanıt iletisi oluşturur. Bu durumda, işleyici çağrısı base.SendAsyncyapmaz, bu nedenle iç işleyici isteği hiçbir zaman almaz ve denetleyici almaz. Bu nedenle, denetleyici tüm gelen isteklerin geçerli bir API anahtarına sahip olduğunu varsayabilir.

Not

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ıştırılır.

Per-Route İleti İşleyicileri

HttpConfiguration.MessageHandlers koleksiyonundaki işleyiciler genel olarak uygulanı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 yolun işlem hattı gösterilmektedir:

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

MessageHandler2 Varsayılan HttpControllerDispatcher değerinin yerini alır. Bu örnekte, MessageHandler2 yanıtı oluşturur ve "Route2" ile eşleşen istekler hiçbir zaman bir denetleyiciye gitmez. Bu, Web API denetleyicisi mekanizmasının tamamını kendi özel uç noktanızla değiştirmenizi sağlar.

Alternatif olarak, yol başına ileti işleyicisi HttpControllerDispatcher'a temsilci atayabilir ve ardından bir denetleyiciye gönderilir.

Yol başına ileti işleyicileri işlem hattının diyagramı, h t t p Denetleyici Dağıtıcısı'na temsilci atama işlemini gösterir ve ardından bir denetleyiciye gönderilir.

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