ASP.NET Web API'sinde Parametre Bağlama
ASP.NET Core web API'lerini kullanmayı göz önünde bulundurun. ASP.NET 4.x Web API'sine göre aşağıdaki avantajlara sahiptir:
- ASP.NET Core, Windows, macOS ve Linux üzerinde 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ştirilir.
- Test edilebilirlik için tasarlanmış.
- Windows, macOS ve Linux üzerinde geliştirme ve çalıştırma olanağı.
- Açık kaynak ve topluluk odaklı.
- Modern istemci tarafı çerçeveler ile geliştirme iş akışlarının tümleştirilmesi.
- Bulutta kullanıma hazır, 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, Apache ve Docker'da barındırabilme.
- Yan yana sürüm oluşturma.
- Modern web geliştirmeyi basitleştiren araçlar.
Bu makalede, Web API'sinin parametreleri nasıl bağladığı ve bağlama işlemini nasıl özelleştirebileceğiniz açıklanır. Web API'sinin denetleyicide bir yöntemi çağırdığında, bağlama adlı bir işlem olan parametreler için değerler ayarlaması gerekir.
Varsayılan olarak, Web API'si parametreleri bağlamak için aşağıdaki kuralları kullanır:
- Parametre "basit" bir türse, Web API değeri URI'den almaya çalışır. Basit türler .NET temel türleri (int, bool, double vb.) ile TimeSpan, DateTime, Guid, ondalık ve dizenin yanı sıra bir dizeden dönüştürebilen tür dönüştürücüsü olan herhangi bir türü içerir. (Daha sonra tür dönüştürücüleri hakkında daha fazla bilgi.)
- Karmaşık türler için Web API'si, medya türü biçimlendirici kullanarak ileti gövdesindeki değeri okumaya çalışır.
Örneğin, tipik bir Web API denetleyicisi yöntemi aşağıda verilmiştir:
HttpResponseMessage Put(int id, Product item) { ... }
Id parametresi "basit" bir tür olduğundan, Web API'si istek URI'sinden değeri almaya çalışır. Öğe parametresi karmaşık bir tür olduğundan, Web API'sinin istek gövdesindeki değeri okumak için medya türü biçimleyicisi kullanılır.
URI'den bir değer almak için Web API'si yol verilerini ve URI sorgu dizesini arar. Yönlendirme sistemi URI'yi ayrıştırdığında ve bir yolla eşleştirdiğinde yol 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ğim. Ancak karmaşık türler için mümkün olduğunca medya türü biçimlendiricileri kullanmayı göz önünde bulundurun. HTTP'nin temel ilkelerinden biri, kaynakların ileti gövdesinde, kaynağın gösterimini belirtmek için içerik anlaşması kullanılarak gönderilmesidir. Medya türü biçimlendiriciler tam olarak bu amaçla tasarlanmıştır.
[FromUri] kullanma
Web API'sini URI'den karmaşık bir türü okumaya zorlamak için parametresine [FromUri] özniteliğini ekleyin. Aşağıdaki örnek, URI'den öğesini alan GeoPoint
bir denetleyici yöntemiyle birlikte bir GeoPoint
türü tanımlar.
public class GeoPoint
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
public ValuesController : ApiController
{
public HttpResponseMessage Get([FromUri] GeoPoint location) { ... }
}
İstemci Sorgu dizesine Enlem ve Boylam değerlerini koyabilir ve Web API'si bunları kullanarak bir GeoPoint
oluşturur. Örneğin:
http://localhost/api/values/?Latitude=47.678558&Longitude=-122.130989
[FromBody] kullanma
Web API'sini istek gövdesinden basit bir türü okumaya zorlamak için parametresine [FromBody] özniteliğini ekleyin:
public HttpResponseMessage Post([FromBody] string name) { ... }
Bu örnekte, Web API'si istek gövdesinden ad değerini okumak için medya türü bir biçimlendirici kullanacaktır. Aşağıda örnek bir istemci isteği 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 bir biçimlendirici seçmek için İçerik Türü üst bilgisini kullanır. Bu örnekte, içerik türü "application/json" ve istek gövdesi ham bir JSON dizesidir (JSON nesnesi değildir).
en fazla bir parametrenin ileti gövdesinden okumasına izin verilir. Bu nedenle bu işlem ç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 okunabilen arabelleğe alınamayan bir akışta depolanabilmesidir.
Tür Dönüştürücüleri
TypeConverter oluşturup bir dize dönüştürmesi sağlayarak Web API'sinin bir sınıfı basit bir tür olarak (web API'sinin URI'den bağlamaya çalışması için) yapmasını sağlayabilirsiniz.
Aşağıdaki kod, coğrafi bir noktayı temsil eden bir GeoPoint
sınıfın yanı sıra dizelerden örneklere GeoPoint
dönüştüren bir TypeConverter'ı gösterir. sınıfı GeoPoint
, tür dönüştürücüsünü belirtmek için [ TypeConverter] özniteliğiyle dekore edilmiştir. (Bu örnekte Mike Stall'ın blog gönderisi ilham alınmıştıMVC/WebAPI'de eylem imzalarındaki özel nesnelere bağlanma.)
[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);
}
}
Şimdi Web API'sinin basit bir tür olarak ele GeoPoint
alınacağı, yani URI'den parametreleri bağlamaya GeoPoint
çalışacağı anlamına gelir. Parametresine [FromUri] eklemeniz gerekmez.
public HttpResponseMessage Get(GeoPoint location) { ... }
İstemci aşağıdaki gibi bir URI ile yöntemini çağırabilir:
http://localhost/api/values/?location=47.678558,-122.130989
Model Bağlayıcıları
Tür dönüştürücüden daha esnek bir seçenek, özel model bağlayıcısı oluşturmaktır. Model bağlayıcısı ile HTTP isteği, eylem açıklaması ve yol verilerinden ham değerler gibi şeylere erişebilirsiniz.
Model bağlayıcısı oluşturmak için IModelBinder arabirimini uygulayın. Bu arabirim, BindModel adlı tek bir yöntemi tanımlar:
bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext);
Nesneler için GeoPoint
bir model bağlayıcısı aşağıdadır.
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 bağlayıcısı, bir değer sağlayıcısından ham giriş değerleri alır. Bu tasarım iki farklı işlevi birbirinden ayırır:
- Değer sağlayıcısı HTTP isteğini alır ve anahtar-değer çiftleri sözlüğü doldurur.
- Model bağlayıcısı, 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ısı aşağıdaki anahtar-değer çiftlerini oluşturur:
- id = "1"
- location = "48,-122"
(Varsayılan yol şablonunun "api/{controller}/{id}" olduğunu varsayıyorum.)
Bağlanacak parametrenin adı ModelBindingContext.ModelName özelliğinde depolanır. Model bağlayıcısı sözlükte bu değere sahip bir anahtar arar. Değer varsa ve bir GeoPoint
içine dönüştürülebiliyorsa, model bağlayıcısı bağlı değeri ModelBindingContext.Model özelliğine atar.
Model bağlayıcısının basit bir tür dönüştürme ile sınırlı olmadığını fark edin. Bu örnekte model bağlayıcısı önce bilinen konumlardan oluşan bir tabloya bakar ve bu başarısız olursa tür dönüştürmeyi kullanır.
Model Bağlayıcısı'nı ayarlama
Model bağlayıcısı ayarlamanın çeşitli yolları vardır. İlk olarak, parametresine bir [ModelBinder] özniteliği ekleyebilirsiniz.
public HttpResponseMessage Get([ModelBinder(typeof(GeoPointModelBinder))] GeoPoint location)
Türüne [ ModelBinder] özniteliği de ekleyebilirsiniz. Web API'sinde bu türdeki tüm parametreler için belirtilen model bağlayıcısı kullanılır.
[ModelBinder(typeof(GeoPointModelBinder))]
public class GeoPoint
{
// ....
}
Son olarak, HttpConfiguration'a bir model bağlayıcı sağlayıcısı ekleyebilirsiniz. Model bağlayıcı sağlayıcısı, model bağlayıcısı oluşturan bir fabrika sınıfıdır. ModelBinderProvider sınıfından türeterek bir sağlayıcı oluşturabilirsiniz. Ancak, model bağlayıcınız tek bir türü işlerse, bu amaç için tasarlanmış yerleşik SimpleModelBinderProvider'ı kullanmak daha kolaydır. 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ında, Web API'sine medya türü biçimlendirici değil model bağlayıcısı kullanması gerektiğini bildirmek için parametresine [ModelBinder] özniteliğini eklemeniz gerekir. Ancak artık özniteliğinde model bağlayıcısının türünü belirtmeniz gerekmez:
public HttpResponseMessage Get([ModelBinder] GeoPoint location) { ... }
Değer Sağlayıcıları
Model bağlayıcının değer sağlayıcısından değer aldığından bahsettim. Özel bir değer sağlayıcısı yazmak için IValueProvider arabirimini uygulayın. burada, istekteki tanımlama bilgilerinden değer çeken bir örnek verilmiştir:
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;
}
}
ValueProviderFactory sınıfından türeterek bir değer sağlayıcısı fabrikası da oluşturmanız gerekir.
public class CookieValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(HttpActionContext actionContext)
{
return new CookieValueProvider(actionContext);
}
}
HttpConfiguration'a aşağıdaki gibi değer sağlayıcısı fabrikasını ekleyin.
public static void Register(HttpConfiguration config)
{
config.Services.Add(typeof(ValueProviderFactory), new CookieValueProviderFactory());
// ...
}
Web API tüm değer sağlayıcılarını oluşturur, bu nedenle model bağlayıcısı ValueProvider.GetValue'u çağırdığında, model bağlayıcısı değeri üretebilen ilk değer sağlayıcısından alır.
Alternatif olarak, ValueProvider özniteliğini kullanarak değer sağlayıcısı fabrikasını parametre düzeyinde aşağıdaki gibi ayarlayabilirsiniz:
public HttpResponseMessage Get(
[ValueProvider(typeof(CookieValueProviderFactory))] GeoPoint location)
Bu, Web API'sine belirtilen değer sağlayıcısı fabrikasıyla model bağlamayı kullanmasını ve diğer kayıtlı değer sağlayıcılarından hiçbirini kullanmamalarını söyler.
HttpParameterBinding
Model bağlayıcıları daha genel bir mekanizmanın belirli bir örneğidir. [ModelBinder] özniteliğine bakarsanız, bunun abstract ParameterBindingAttribute sınıfından türetildiğini görürsünüz. Bu sınıf, HttpParameterBinding nesnesi döndüren getBinding adlı tek bir yöntemi tanımlar:
public abstract class ParameterBindingAttribute : Attribute
{
public abstract HttpParameterBinding GetBinding(HttpParameterDescriptor parameter);
}
HttpParameterBinding, parametreyi bir değere bağlamakla sorumludur. [ModelBinder] durumunda özniteliği, gerçek bağlamayı gerçekleştirmek için IModelBinder kullanan bir HttpParameterBinding uygulaması döndürür. Kendi HttpParameterBinding'inizi de uygulayabilirsiniz.
Örneğin, istekte if-match
ETag'leri ve if-none-match
üst bilgileri almak istediğinizi varsayalım. ETag'leri temsil eden bir sınıf tanımlayarak başlayacağız.
public class ETag
{
public string Tag { get; set; }
}
Ayrıca üst bilgiden mi if-none-match
yoksa üst bilgiden if-match
mi ETag alınıp alınmayacağını belirtmek için bir numaralandırma tanımlayacağız.
public enum ETagMatch
{
IfMatch,
IfNoneMatch
}
ETag'i istenen üst bilgiden alan ve ETag türünde bir parametreye bağlayan bir HttpParameterBinding aşağıdadı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, bağlı parametre değerini HttpActionContext içindeki ActionArgument sözlüğüne ekleyin.
Not
ExecuteBindingAsync yönteminiz istek iletisinin gövdesini okursa WillReadBody özelliğini geçersiz kılarak true değerini döndürün. İstek gövdesi yalnızca bir kez okunabilen, kaldırılmamış bir akış olabilir, bu nedenle Web API'sinin ileti gövdesini en çok bir bağlamanın okuyabileceği bir kural uygular.
Özel bir HttpParameterBinding uygulamak için ParameterBindingAttribute'tan türetilen bir öznitelik tanımlayabilirsiniz. için ETagParameterBinding
biri üst bilgiler, diğeri üst bilgiler için if-match
if-none-match
iki öznitelik tanımlayacağız. Her ikisi de soyut bir temel sınıftan türetilir.
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ğini kullanan bir denetleyici yöntemi aşağıdadır [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, (HttpParameterDescriptor ->HttpParameterBinding) türündeki anonim işlevlerin koleksiyonudur. Örneğin, get yöntemindeki herhangi bir ETag parametresinin ile if-none-match
kullandığı ETagParameterBinding
bir kural ekleyebilirsiniz:
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, bağlamanın geçerli olmadığı parametreler için döndürmelidir null
.
IActionValueBinder
Parametre bağlama işleminin tamamı takılabilir bir hizmet olan IActionValueBinder tarafından denetlenir. IActionValueBinder'ın varsayılan uygulaması aşağıdakileri yapar:
Parametresinde ParameterBindingAttribute arayın. Buna [FromBody], [FromUri] ve [ModelBinder] veya özel öznitelikler dahildir.
Aksi takdirde, null olmayan bir HttpParameterBinding döndüren bir işlev için HttpConfiguration.ParameterBindingRules bölümüne bakın.
Aksi takdirde, daha önce açıkladığım varsayılan kuralları kullanın.
- Parametre türü "basit" ise veya bir tür dönüştürücüsü varsa, URI'den bağlayın. Bu, [FromUri] özniteliğini parametresine yerleştirmeye eşdeğerdir.
- Aksi takdirde, ileti gövdesinden parametresini okumayı deneyin. Bu, parametreye [FromBody] koymakla eşdeğerdir.
İsterseniz, IActionValueBinder hizmetinin tamamını özel bir uygulamayla değiştirebilirsiniz.
Ek Kaynaklar
Mike Stall, Web API parametre bağlaması hakkında iyi bir blog gönderileri serisi yazdı: