Categories: Asp.Net Web API

Asp.NET Web API’da IHttpRouteConstraint Implementasyonu ile Versiyonlama

Merhaba arkadaşlar. Bu makale konumda ise Asp.NET Web API’da nasıl versiyonlama yapılabileceğini görürken bir yandan da action method’lar üzerinde nasıl custom constraint’ler ekleyebileceğimizi göreceğiz. Dilerseniz fazla uzatmadan hemen konuya girelim. 🙂

Asp.NET Web API içerisinde IHttpRouteConstraint interface’ini implemente ederek, custom constraint oluşturabilmek mümkündür. “System.Web.Http.Routing” namespace’i altında bulunan IHttpRouteConstraint interface’i, aşağıdaki gibi bir method imzasına sahiptir.

namespace System.Web.Http.Routing
{
    //
    // Summary:
    //     Represents a base class route constraint.
    public interface IHttpRouteConstraint
    {
        //
        // Summary:
        //     Determines whether this instance equals a specified route.
        //
        // Parameters:
        //   request:
        //     The request.
        //
        //   route:
        //     The route to compare.
        //
        //   parameterName:
        //     The name of the parameter.
        //
        //   values:
        //     A list of parameter values.
        //
        //   routeDirection:
        //     The route direction.
        //
        // Returns:
        //     True if this instance equals a specified route; otherwise, false.
        bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection);
    }
}

Dilerseniz bir örnek üzerinde IHttpRouteConstraint interface’inin, implementasyonunu gerçekleştirelim. Gerçekleştirecek olduğumuz örnekte HTTP Header’ı kullanarak, controller üzerinde custom olarak versiyonlama işlemi gerçekleştireceğiz.

Şimdi VersionConstraint isminde yeni bir class oluşturalım ve IHttpRouteConstraint interface’ini aşağıdaki gibi implemente edelim.

using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http.Routing;

namespace RoutingExample.Constraints
{
    public class VersionConstraint : IHttpRouteConstraint
    {
        private const int DEFAULT_VERSION = 1;
        private int _AllowedVersion;

        public VersionConstraint(int allowedVersion)
        {
            _AllowedVersion = allowedVersion;
        }

        public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
        {
            if (routeDirection == HttpRouteDirection.UriResolution)
            {
                int version = GetVersionFromRequestHeader(request) ?? DEFAULT_VERSION;
                if (version == _AllowedVersion)
                {
                    return true;
                }
            }

            return false;
        }

        private int? GetVersionFromRequestHeader(HttpRequestMessage request)
        {
            IEnumerable<string> headerValues;
            string val = string.Empty;

            if (request.Headers.TryGetValues("api-version", out headerValues) && headerValues.Count() == 1)
            {
                val = headerValues.First();
            }

            int versionFromHeader;

            if (!string.IsNullOrEmpty(val) && int.TryParse(val, out versionFromHeader))
            {
                return versionFromHeader;
            }

            return null;
        }
    }
}

Implementasyona ilk baktığımızda, constructor aracılığı ile dışarıdan geçerli olmasını istediğimiz “allowedVersion” parametresini alıyoruz. IHttpRouteConstraint interface’inden gelen “Match” method’unda ise; Request’in Header’ı üzerinde custom olarak set edilmiş olan “api-version” bilgisini alarak, constructor üzerinden elde etmiş olduğumuz “allowedVersion” parametresi ile karşılaştırıyoruz.
Artık oluşturmuş olduğumuz VersionConstraint class’ını route belirtirken kullanabilmek için RouteFactoryAttribute abstract class’ından yararlanarak, aşağıdaki gibi yeni bir attribute oluşturalım ve implemente edelim.

using RoutingExample.Constraints;
using System;
using System.Collections.Generic;
using System.Web.Http.Routing;

namespace RoutingExample.Attributes
{
    public class VersionedRouteAttribute : RouteFactoryAttribute
    {
        private int _AllowedVersion;

        public VersionedRouteAttribute(string template, int allowedVersion)
            : base(template)
        {
            _AllowedVersion = allowedVersion;
        }

        public override IDictionary<String, Object> Constraints
        {
            get
            {
                var constraints = new HttpRouteValueDictionary();
                constraints.Add("version", new VersionConstraint(_AllowedVersion));

                return constraints;
            }
        }
    }
}

Implementasyona baktığımızda ilk parametrede constructor üzerinden route template’i alıp, RouteFactoryAttribute’ün construtor’ına geçiyoruz. İkinci parametrede ise geçerli olmasını istediğimiz versiyon numarasını alarak, RouteFactoryAttribute üzerinde bulunan Constraints dictionary’sine VersionConstraint’i, geçerli olmasını istediğimiz versiyon numarası ile beraber ekliyoruz.

Constraint’imiz hazır olduğuna göre şimdi yeni bir controller oluşturarak kullanımına bir bakalım.

public class Order_V1Controller : ApiController
{
    [VersionedRoute("api/Order", 1)]
    [HttpGet]
    public IEnumerable<Order> Get()
    {
        return null;
    }
}

Order_V1Controller’a baktığımızda Get method’u üzerinde VersionedRoute attribute’ünü görebilmekteyiz. İlk parametresinde geçerli olmasını istediğimiz template route’u gönderirken, ikinci parametresinde ise geçerli olmasını istediğimiz versiyon numarasını set ediyoruz.
Artık her şey hazır olduğuna göre örneğimizi deneyebilmek için Fiddler üzerinden “api/Order” URI’ına bir GET isteğinde bulunacağız. Aşağıda şekildeki gibi GET isteğinde bulunurken HTTP Request Header’ına, “api-version” parametresini ekleyerek “2” değerini verelim.

Fiddler üzerinde GET request’ini yukarıda şekildeki gibi ayarladıktan sonra execute edelim ve IHttpRouteConstraint interface’ini implemente ettiğimiz VersionConstraint class’ı içerisindeki “Match” method’una, debug anında bir bakalım.

GET isteğinde bulunduğumuz için request, öncelikle Order_V1Controller içerisindeki Get method’una düşmüştür. Get method’una eklemiş olduğumuz VersionedRoute attribute’ünden dolayı, henüz method invoke edilmeden önce constraint kontrolü için request, “Match” method’una düşmüştür.

NOT: Asp.NET Web API uygulaması ilk kez ayağa kalktığında, action method üzerine eklemiş olduğumuz “VersionedRoute” attribute’ü aracılığı ile Constraints property’si içerisine oluşturmuş olduğumuz “VersionConstraint” class’ı eklenmektedir. Bu sayede ilgili action method’a bir request geldiğinde, VersionConstraint class’ı içerisindeki “Match” method’una düşmektedir.

Match method’u içerisinde ise Request Header’ı üzerinde göndermiş olduğumuz “api-version” bilgisini elde edebilmek için GetVersionFromRequestHeader method’u çağırılıyor ve yukarıdaki gibi versiyon numarası Request Header üzerinden elde ediliyor.

Match method’u versiyon numarasını elde ettikten sonra VersionConstraint attribute’ünü kullanırken, göndermiş olduğumuz “AllowedVersion” parametresi ile karşılaştırarak, bu constraint’in match olup olmadığına karar veriyor. Asp.NET Web API bu işlemin sonucunda ise ilgili route’un geçerli olup olmayacağına karar vermektedir.
Biz örneğimizde “AllowedVersion” parmetresini “1” olarak belirlediğimiz ve Request Header üzerinden “api-version” parametresini “2” olarak gönderdiğimiz için, Asp.NET Web API bize aşağıdaki gibi bir sonuç geri dönecektir.

HTTP/1.1 404 Not Found
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/10.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcR8OWS0FMUFxEb2N1bWVudHNcVmlzdWFsIFN0dWRpbyAyMDE1XFByb2plY3RzXFJvdXRpbmdFeGFtcGxlXFJvdXRpbmdFeGFtcGxlXGFwaVxPcmRlcg==?=
X-Powered-By: ASP.NET
Date: Sat, 30 Jan 2016 21:21:16 GMT
Content-Length: 195

{"Message":"No HTTP resource was found that matches the request URI 'http://localhost:65268/api/Order'.","MessageDetail":"No action was found on the controller 'Order' that matches the request."}

Bu durumda “api-version” parametresini “1” olarak göndermiş olsaydık, Get method’u başarılı bir şekilde invoke ediliyor olacaktı. Bu şekilde action method bazında versiyonlama işlemi yapabilmek mümkün olmaktadır.

Dilerseniz sizde IHttpRouteConstraint interface’ini custom olarak implemente ederek, Match method’u içerisinde dilediğiniz işlemleri gerçekleştirebilirsiniz. Gerçekleştirmiş olduğumuz örneğin uygulamasına ekten erişebilirsiniz. Biraz bıraktığım aradan sonra umarım faydalı bir makale olmuştur.

RoutingExample

Gökhan Gökalp

View Comments

  • Api route prefix uzerinden versiyon yapmayip bu implementasyony yapmanizin ne gibi bir avantaji soz konusu?

    Tesekkurler

    • Merhaba, buradaki asıl amaç route ile birlikte versiyonlama yapmak değil aslında. Buradaki amaç IHttpRouteConstraint interface'inin implementasyonunu göstermek ve bir örnek olarak da versiyonlama gibi constraint işlemlerinin yapılabilmesini göstermek. Öte yandan versiyonlama yapmayıp, business use-case'lerinize göre istediğiniz constraint'leri implemente edebilirsiniz.

Recent Posts

Securing the Supply Chain of Containerized Applications to Reduce Security Risks (Policy Enforcement-Automated Governance with OPA Gatekeeper and Ratify) – Part 2

{:tr} Makalenin ilk bölümünde, Software Supply Chain güvenliğinin öneminden ve containerized uygulamaların güvenlik risklerini azaltabilmek…

6 months ago

Securing the Supply Chain of Containerized Applications to Reduce Security Risks (Security Scanning, SBOMs, Signing&Verifying Artifacts) – Part 1

{:tr}Bildiğimiz gibi modern yazılım geliştirme ortamında containerization'ın benimsenmesi, uygulamaların oluşturulma ve dağıtılma şekillerini oldukça değiştirdi.…

8 months ago

Delegating Identity & Access Management to Azure AD B2C and Integrating with .NET

{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…

1 year ago

How to Order Events in Microservices by Using Azure Service Bus (FIFO Consumers)

{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…

2 years ago

Providing Atomicity for Eventual Consistency with Outbox Pattern in .NET Microservices

{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…

2 years ago

Building Microservices by Using Dapr and .NET with Minimum Effort – 02 (Azure Container Apps)

{:tr}Bir önceki makale serisinde Dapr projesinden ve faydalarından bahsedip, local ortamda self-hosted mode olarak .NET…

2 years ago