ASP.NET Web API 'sinde parametre bağlama

, Mike te son

ASP.NET Core Web API 'sinikullanmayı düşünün. ASP.NET 4. x Web API 'sine göre aşağıdaki avantajları vardır:

  • ASP.NET Core, Windows, macOS ve Linux 'ta modern, bulut tabanlı Web uygulamaları oluşturmaya yönelik açık kaynaklı, platformlar arası bir çerçevedir.
  • ASP.NET Core MVC denetleyicileri ve Web API denetleyicileri birleştirilmiştir.
  • , Test edilebilirlik için tasarlanmıştır.
  • Windows, macOS ve Linux 'ta geliştirme ve çalıştırma özelliği.
  • Açık kaynak ve topluluk odaklı.
  • Modern, istemci tarafı çerçeveleri ve geliştirme iş akışlarının tümleştirilmesi.
  • Buluta hazırlanma, ortam tabanlı bir yapılandırma sistemi.
  • Yerleşik bağımlılık ekleme.
  • Basit, yüksek performanslı ve modüler bir HTTP istek işlem hattı.
  • Kestrel, IIS, http. sys, NGINX, Apacheve Dockerüzerinde barındırma özelliği.
  • Yan yana sürüm oluşturma.
  • Modern web geliştirmeyi basitleştiren araçlar.

Bu makale, Web API 'sinin parametreleri nasıl bağlamakta olduğunu ve bağlama işlemini nasıl özelleştirebileceğinizi açıklamaktadır. Web API 'SI bir denetleyicide bir yöntemi çağırdığında,, bağlama adlı bir işlem olan parametrelerin değerlerini ayarlaması gerekir.

Varsayılan olarak, Web API parametreleri bağlamak için aşağıdaki kuralları kullanır:

  • Parametre "basit" bir tür ise, Web API 'SI URI 'den değeri almaya çalışır. Basit türler, .NET ilkel türlerini (int, bool, Double, vb.), artı TimeSpan, DateTime, Guid, Decimal, ve String ve bir dizeden dönüştürebileceğiniz tür dönüştürücüsü olan herhangi bir türü içerir. (Daha sonra tür dönüştürücüler hakkında daha fazla bilgi.)
  • Karmaşık türler için Web API 'SI, bir medya türü biçimlendiricikullanarak ileti gövdesinden değeri okumaya çalışır.

Örneğin, tipik bir Web API denetleyici yöntemi aşağıda verilmiştir:

HttpResponseMessage Put(int id, Product item) { ... }

ID parametresi " basit bir " türdür, bu nedenle Web API 'si istek URI 'sinden değeri almaya çalışır. Öğe parametresi karmaşık bir türdür, bu nedenle Web API 'si, istek gövdesinden değeri okumak için bir medya türü biçimlendirici kullanır.

URI 'den bir değer almak için Web API 'si yol verilerine ve URI sorgu dizesine bakar. Yönlendirme sistemi URI 'yi ayrıştırdığında ve bir rota ile eşleştiğinde rota verileri doldurulur. Daha fazla bilgi için bkz. Yönlendirme ve eylem seçimi.

Bu makalenin geri kalanında, model bağlama işlemini nasıl özelleştirebileceğinizi göstereceğiz. Ancak karmaşık türler için, mümkün olduğunda medya türü formatlayıcıları kullanmayı düşünün. HTTP 'nin önemli prensibi, kaynak gösterimini belirtmek için içerik görüşmesi kullanılarak ileti gövdesinde kaynakların gönderilmektir. Medya türü formatlayıcıları tam olarak bu amaçla tasarlanmıştı.

[FromUri] kullanma

Web API 'sini URI 'den karmaşık bir tür okuyacak şekilde zorlamak için, parametresine [Fromuri] özniteliğini ekleyin. Aşağıdaki örnek, bir GeoPoint türü URI 'den alan bir denetleyici yöntemiyle birlikte tanımlar GeoPoint .

public class GeoPoint
{
    public double Latitude { get; set; } 
    public double Longitude { get; set; }
}

public ValuesController : ApiController
{
    public HttpResponseMessage Get([FromUri] GeoPoint location) { ... }
}

İstemci, enlem ve boylam değerlerini sorgu dizesine yerleştirebilir ve Web API 'SI bunları oluşturmak için kullanır GeoPoint . Örneğin:

http://localhost/api/values/?Latitude=47.678558&Longitude=-122.130989

[FromBody] kullanma

Web API 'sini istek gövdesinden basit bir tür okuyacak şekilde zorlamak için, [Frombody] özniteliğini parametresine ekleyin:

public HttpResponseMessage Post([FromBody] string name) { ... }

Bu örnekte, Web API 'SI, istek gövdesinden adı değerini okumak için bir medya türü biçimlendirici kullanacaktır. Örnek bir istemci isteği aşağıda verilmiştir.

POST http://localhost:5076/api/values HTTP/1.1
User-Agent: Fiddler
Host: localhost:5076
Content-Type: application/json
Content-Length: 7

"Alice"

Bir parametre [FromBody] olduğunda, Web API 'SI bir biçimlendirici seçmek için Content-Type üst bilgisini kullanır. Bu örnekte, içerik türü " Application/JSON " ve istek gövdesi BIR ham JSON DIZESIDIR (JSON nesnesi değil).

İleti gövdesinden en fazla bir parametrenin okumasına izin verilir. Bu nedenle, bu çalışmaz:

// Caution: Will not work!    
public HttpResponseMessage Post([FromBody] int id, [FromBody] string name) { ... }

Bu kuralın nedeni, istek gövdesinin yalnızca bir kez okunabilecek, arabelleğe alınmamış bir akışa depolanabileceği bir akışdır.

Tür dönüştürücüler

Bir TypeConverter 'ı oluşturarak ve bir dize dönüştürmesi sağlayarak Web API 'sinin bir sınıfı basit bir tür olarak (Web API 'sini URI 'den bağlamaya çalışacak şekilde) görmesini sağlayabilirsiniz.

Aşağıdaki kod GeoPoint , bir coğrafi noktayı temsil eden bir sınıfı ve dizelerden örneklere dönüştüren bir TypeConverter 'ı gösterir GeoPoint . GeoPointSınıf, tür dönüştürücüsünü belirtmek için bir [TypeConverter] özniteliğiyle donatılmış. (Bu örnek, Mike Stall 'ın Web günlüğü gönderisini MVC/WebAPI içindeki eylem imzalarındaki özel nesnelere bağlama.)

[TypeConverter(typeof(GeoPointConverter))]
public class GeoPoint
{
    public double Latitude { get; set; } 
    public double Longitude { get; set; }

    public static bool TryParse(string s, out GeoPoint result)
    {
        result = null;

        var parts = s.Split(',');
        if (parts.Length != 2)
        {
            return false;
        }

        double latitude, longitude;
        if (double.TryParse(parts[0], out latitude) &&
            double.TryParse(parts[1], out longitude))
        {
            result = new GeoPoint() { Longitude = longitude, Latitude = latitude };
            return true;
        }
        return false;
    }
}

class GeoPointConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (sourceType == typeof(string))
        {
            return true;
        }
        return base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, 
        CultureInfo culture, object value)
    {
        if (value is string)
        {
            GeoPoint point;
            if (GeoPoint.TryParse((string)value, out point))
            {
                return point;
            }
        }
        return base.ConvertFrom(context, culture, value);
    }
}

Artık Web API GeoPoint 'si basit bir tür olarak değerlendirilir, yani GeoPoint URI 'den parametreleri bağlamaya çalışacaktır. Parametreye [Fromuri] eklemeniz gerekmez.

public HttpResponseMessage Get(GeoPoint location) { ... }

İstemci, yöntemi aşağıdaki gibi bir URI ile çağırabilir:

http://localhost/api/values/?location=47.678558,-122.130989

Model ciltleri

Tür dönüştürücüden daha esnek bir seçenek, özel model Bağlayıcısı oluşturmaktır. Model Ciltçi ile HTTP isteği, eylem açıklaması ve rota verilerinden ham değerler gibi şeylere erişebilirsiniz.

Bir model Bağlayıcısı oluşturmak için ımodelciltçi arabirimini uygulayın. Bu arabirim tek bir yöntemi tanımlar, BindModel:

bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext);

Nesneler için bir model Bağlayıcısı aşağıda verilmiştir GeoPoint .

public class GeoPointModelBinder : IModelBinder
{
    // List of known locations.
    private static ConcurrentDictionary<string, GeoPoint> _locations
        = new ConcurrentDictionary<string, GeoPoint>(StringComparer.OrdinalIgnoreCase);

    static GeoPointModelBinder()
    {
        _locations["redmond"] = new GeoPoint() { Latitude = 47.67856, Longitude = -122.131 };
        _locations["paris"] = new GeoPoint() { Latitude = 48.856930, Longitude = 2.3412 };
        _locations["tokyo"] = new GeoPoint() { Latitude = 35.683208, Longitude = 139.80894 };
    }

    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        if (bindingContext.ModelType != typeof(GeoPoint))
        {
            return false;
        }

        ValueProviderResult val = bindingContext.ValueProvider.GetValue(
            bindingContext.ModelName);
        if (val == null)
        {
            return false;
        }

        string key = val.RawValue as string;
        if (key == null)
        {
            bindingContext.ModelState.AddModelError(
                bindingContext.ModelName, "Wrong value type");
            return false;
        }

        GeoPoint result;
        if (_locations.TryGetValue(key, out result) || GeoPoint.TryParse(key, out result))
        {
            bindingContext.Model = result;
            return true;
        }

        bindingContext.ModelState.AddModelError(
            bindingContext.ModelName, "Cannot convert value to GeoPoint");
        return false;
    }
}

Model Ciltçi, bir değer sağlayıcısından ham giriş değerleri alır. Bu tasarım iki ayrı işlevi ayırır:

  • Değer sağlayıcısı, HTTP isteğini alır ve anahtar-değer çiftlerinin bir sözlüğünü doldurur.
  • Model Ciltçi, modeli doldurmak için bu sözlüğü kullanır.

Web API 'sindeki varsayılan değer sağlayıcısı, rota verilerinden ve sorgu dizesinden değerleri alır. Örneğin, URI ise, http://localhost/api/values/1?location=48,-122 değer sağlayıcı aşağıdaki anahtar-değer çiftlerini oluşturur:

  • kimlik = " 1"
  • Konum = " 48,-122"

( " API/{Controller}/{id} olan varsayılan yol şablonunu kabul ediyorum " .)

Bağlanacak parametrenin adı, ModelBindingContext. ModelName özelliğinde depolanır. Model Ciltçi, sözlükte bu değere sahip bir anahtar arar. Değer varsa ve bir öğesine dönüştürülebiliyorsanız GeoPoint model Ciltçi, bağlantılı değeri ModelBindingContext. model özelliğine atar.

Model cildin basit bir tür dönüştürmesi ile sınırlı olmadığına dikkat edin. Bu örnekte, model cildi ilk olarak bilinen konumların bir tablosuna bakar ve bu başarısız olursa, tür dönüştürme kullanır.

Model cildi ayarlama

Model cildi ayarlamak için çeşitli yollar vardır. İlk olarak, parametresine bir [Modelciltçi] özniteliği ekleyebilirsiniz.

public HttpResponseMessage Get([ModelBinder(typeof(GeoPointModelBinder))] GeoPoint location)

Ayrıca, türüne bir [Modelciltçi] özniteliği ekleyebilirsiniz. Web API 'SI, bu türün tüm parametreleri için belirtilen model cildi kullanır.

[ModelBinder(typeof(GeoPointModelBinder))]
public class GeoPoint
{
    // ....
}

Son olarak, bir model Ciltçi sağlayıcısını HttpConfiguration'a ekleyebilirsiniz. Model Ciltçi sağlayıcısı, model cildi oluşturan bir fabrika sınıfıdır. ModelBinderProvider sınıfından türeterek bir sağlayıcı oluşturabilirsiniz. Ancak, model Ciltçi tek bir tür işlediğinde, bu amaçla tasarlanan yerleşik SimpleModelBinderProvider kullanımı daha kolay olur. Aşağıdaki kod bunun nasıl yapılacağını gösterir.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var provider = new SimpleModelBinderProvider(
            typeof(GeoPoint), new GeoPointModelBinder());
        config.Services.Insert(typeof(ModelBinderProvider), 0, provider);

        // ...
    }
}

Model bağlama sağlayıcısıyla, Web API 'sini bir medya türü biçimlendirici değil, model cildi kullanması gerektiğini söylemek için, [modelciltçi] özniteliğini parametreye eklemeniz gerekir. Ancak şimdi özniteliğinde model cildin türünü belirtmeniz gerekmez:

public HttpResponseMessage Get([ModelBinder] GeoPoint location) { ... }

Değer sağlayıcıları

Model cildin bir değer sağlayıcısından değerler aldığından bahsetdim. Özel bir değer sağlayıcısı yazmak için IValueProvider arabirimini uygulayın. İşte, istekteki tanımlama bilgilerinden değer çeken bir örnek:

public class CookieValueProvider : IValueProvider
{
    private Dictionary<string, string> _values;

    public CookieValueProvider(HttpActionContext actionContext)
    {
        if (actionContext == null)
        {
            throw new ArgumentNullException("actionContext");
        }

        _values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        foreach (var cookie in actionContext.Request.Headers.GetCookies())
        {
            foreach (CookieState state in cookie.Cookies)
            {
                _values[state.Name] = state.Value;
            }
        }
    }

    public bool ContainsPrefix(string prefix)
    {
        return _values.Keys.Contains(prefix);
    }

    public ValueProviderResult GetValue(string key)
    {
        string value;
        if (_values.TryGetValue(key, out value))
        {
            return new ValueProviderResult(value, value, CultureInfo.InvariantCulture);
        }
        return null;
    }
}

Ayrıca, ValueProviderFactory sınıfından türeterek bir değer sağlayıcısı fabrikası oluşturmanız gerekir.

public class CookieValueProviderFactory : ValueProviderFactory
{
    public override IValueProvider GetValueProvider(HttpActionContext actionContext)
    {
        return new CookieValueProvider(actionContext);
    }
}

Değer sağlayıcısı fabrikasını HttpConfiguration öğesine aşağıdaki şekilde ekleyin.

public static void Register(HttpConfiguration config)
{
    config.Services.Add(typeof(ValueProviderFactory), new CookieValueProviderFactory());

    // ...
}

Web API 'SI, tüm değer sağlayıcılarını oluşturur, bu nedenle model Ciltçi ValueProvider. GetValue' yi çağırdığında, model cildi onu üretebilecek ilk değer sağlayıcısından değeri alır.

Alternatif olarak, değer sağlayıcı fabrikasını parametre düzeyinde, ValueProvider özniteliğini kullanarak aşağıdaki gibi ayarlayabilirsiniz:

public HttpResponseMessage Get(
    [ValueProvider(typeof(CookieValueProviderFactory))] GeoPoint location)

Bu, Web API 'sinin belirtilen değer sağlayıcı fabrikası ile model bağlamayı kullanmasını söyler ve diğer kayıtlı değer sağlayıcılarının hiçbirini kullanmaz.

HttpParameterBinding

Model ciltleri, daha genel bir mekanizmanın belirli bir örneğidir. [Modelciltçi] özniteliğine bakarsanız, onun soyut ParameterBindingAttribute sınıfından türetildiğinden emin olursunuz. Bu sınıf, bir Httpparameterbinding nesnesi döndüren GetBinding tek bir yöntemini tanımlar:

public abstract class ParameterBindingAttribute : Attribute
{
    public abstract HttpParameterBinding GetBinding(HttpParameterDescriptor parameter);
}

Bir Httpparameterbinding , bir parametreyi bir değere bağlamaktan sorumludur. [Modelciltçi] söz konusu olduğunda, öznitelik gerçek bağlamayı gerçekleştirmek Için ımodelciltçi kullanan bir httpparameterbinding uygulaması döndürür. Ayrıca kendi Httpparameterbinding'nizi de uygulayabilirsiniz.

Örneğin, if-match istekteki ve üst bilgilerden ETags almak istediğinizi varsayalım if-none-match . ETags temsil eden bir sınıf tanımlayarak başlayacağız.

public class ETag
{
    public string Tag { get; set; }
}

Ayrıca, if-match üst bilgiden veya başlıktan ETag 'in mi alınacağını göstermek için bir sabit listesi tanımlayacağız if-none-match .

public enum ETagMatch
{
    IfMatch,
    IfNoneMatch
}

Burada, istenen üst bilgiden ETag 'i alan ve ETag türü bir parametreye bağlayan bir Httpparameterbinding yer alır:

public class ETagParameterBinding : HttpParameterBinding
{
    ETagMatch _match;

    public ETagParameterBinding(HttpParameterDescriptor parameter, ETagMatch match) 
        : base(parameter)
    {
        _match = match;
    }

    public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, 
        HttpActionContext actionContext, CancellationToken cancellationToken)
    {
        EntityTagHeaderValue etagHeader = null;
        switch (_match)
        {
            case ETagMatch.IfNoneMatch:
                etagHeader = actionContext.Request.Headers.IfNoneMatch.FirstOrDefault();
                break;

            case ETagMatch.IfMatch:
                etagHeader = actionContext.Request.Headers.IfMatch.FirstOrDefault();
                break;
        }

        ETag etag = null;
        if (etagHeader != null)
        {
            etag = new ETag { Tag = etagHeader.Tag };
        }
        actionContext.ActionArguments[Descriptor.ParameterName] = etag;

        var tsc = new TaskCompletionSource<object>();
        tsc.SetResult(null);
        return tsc.Task;
    }
}

Executebindingasync yöntemi bağlamayı yapar. Bu yöntemde, bir bağlama parametre değerini Httpactioncontext Içindeki actionargument sözlüğüne ekleyin.

Note

Executebindingasync yönteminiz istek iletisinin gövdesini okuyorsa, true döndürecek şekilde willreadbody özelliğini geçersiz kılın. İstek gövdesi, yalnızca bir kez okunabilecek, arabelleğe alınmamış bir akış olabilir, bu nedenle Web API 'SI en çok bir bağlamanın ileti gövdesini okuyabilecek bir kuralı zorlar.

Özel bir Httpparameterbinding uygulamak Için ParameterBindingAttribute öğesinden türetilen bir öznitelik tanımlayabilirsiniz. İçin, ETagParameterBinding biri if-match üst bilgiler ve bir üst bilgi için olmak üzere iki öznitelik tanımlayacağız if-none-match . Her ikisi de soyut bir temel sınıftan türetir.

public abstract class ETagMatchAttribute : ParameterBindingAttribute
{
    private ETagMatch _match;

    public ETagMatchAttribute(ETagMatch match)
    {
        _match = match;
    }

    public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
    {
        if (parameter.ParameterType == typeof(ETag))
        {
            return new ETagParameterBinding(parameter, _match);
        }
        return parameter.BindAsError("Wrong parameter type");
    }
}

public class IfMatchAttribute : ETagMatchAttribute
{
    public IfMatchAttribute()
        : base(ETagMatch.IfMatch)
    {
    }
}

public class IfNoneMatchAttribute : ETagMatchAttribute
{
    public IfNoneMatchAttribute()
        : base(ETagMatch.IfNoneMatch)
    {
    }
}

Özniteliği kullanan bir denetleyici yöntemi aşağıda verilmiştir [IfNoneMatch] .

public HttpResponseMessage Get([IfNoneMatch] ETag etag) { ... }

ParameterBindingAttribute'un yanı sıra özel bir httpparameterbinding eklemek için başka bir kanca vardır. HttpConfiguration nesnesinde, parameterbindingrules özelliği, türündeki anonim işlevlerin (HttpParameterDescriptor - > httpparameterbinding) bir koleksiyonudur. Örneğin, GET yöntemi üzerinde herhangi bir ETag parametresinin ile kullandığı bir kural ekleyebilirsiniz ETagParameterBinding if-none-match :

config.ParameterBindingRules.Add(p =>
{
    if (p.ParameterType == typeof(ETag) && 
        p.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Get))
    {
        return new ETagParameterBinding(p, ETagMatch.IfNoneMatch);
    }
    else
    {
        return null;
    }
});

İşlev, null bağlamanın geçerli olmadığı parametreler için döndürmelidir.

Iactionvalueciltçi

Tüm parametre bağlama işlemi takılabilir bir hizmet olan ıactionvalueciltçi tarafından denetlenir. Iactionvalueciltçi 'nin varsayılan uygulaması aşağıdakileri yapar:

  1. Parametresinde bir ParameterBindingAttribute bulun. Buna [Frombody], [fromuri] ve [modelciltçi] ya da özel öznitelikler dahildir.

  2. Aksi halde, null olmayan bir Httpparameterbinding döndüren bir Işlev Için HttpConfiguration. parameterbindingrules bölümüne bakın.

  3. Aksi takdirde, daha önce açıklananlardan varsayılan kuralları kullanın.

    • Parametre türü "basittir" ise veya tür dönüştürücüsüyle, URI 'den bağlayın. Bu, [Fromuri] özniteliğini parametreye koymaya eşdeğerdir.
    • Aksi takdirde, ileti gövdesinden parametresini okumayı deneyin. Bu parametre üzerine [Frombody] koymaya eşdeğerdir.

İsterseniz, tüm ıactionvalueciltçi hizmetini özel bir uygulamayla değiştirebilirsiniz.

Ek Kaynaklar

Özel parametre bağlama örneği

Mike Stall, Web API parametresi bağlaması hakkında iyi bir blog gönderisi serisi yazdı: