2013-02-13 13 views
12

Mam wiele problemów z uzyskaniem niestandardowego wiązania modelu do pracy po opublikowaniu danych x-www-form-urlencoded. Próbowałem na każdy sposób, jaki przychodzi mi do głowy, i nic nie wydaje się przynosić pożądanego rezultatu. Uwaga podczas publikowania danych JSON, moje JsonConverters i tak dalej wszystko działa dobrze. Kiedy publikuję jako x-www-form-urlencoded, system nie może wymyślić, jak powiązać mój model.Niestandardowe powiązanie modelu ASP.Net Web API z danymi opublikowanymi przez x-www-urlencoded - nic nie wydaje się działać

Mój przypadek testowy polega na tym, że chciałbym związać obiekt TimeZoneInfo jako część mojego modelu.

Oto mój model spoiwo:

public class TimeZoneModelBinder : SystemizerModelBinder 
{ 
    protected override object BindModel(string attemptedValue, Action<string> addModelError) 
    { 
     try 
     { 
      return TimeZoneInfo.FindSystemTimeZoneById(attemptedValue); 
     } 
     catch(TimeZoneNotFoundException) 
     { 
      addModelError("The value was not a valid time zone ID. See the GetSupportedTimeZones Api call for a list of valid time zone IDs."); 
      return null; 
     } 
    } 
} 

Oto klasa bazowa Używam:

public abstract class SystemizerModelBinder : IModelBinder 
{ 
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) 
    { 
     var name = GetModelName(bindingContext.ModelName); 
     var valueProviderResult = bindingContext.ValueProvider.GetValue(name); 
     if(valueProviderResult == null || string.IsNullOrWhiteSpace(valueProviderResult.AttemptedValue)) 
      return false; 

     var success = true; 
     var value = BindModel(valueProviderResult.AttemptedValue, s => 
     { 
      success = false; 
      bindingContext.ModelState.AddModelError(name, s); 
     }); 
     bindingContext.Model = value; 
     bindingContext.ModelState.SetModelValue(name, new System.Web.Http.ValueProviders.ValueProviderResult(value, valueProviderResult.AttemptedValue, valueProviderResult.Culture)); 
     return success; 
    } 

    private string GetModelName(string name) 
    { 
     var n = name.LastIndexOf(".", StringComparison.Ordinal); 
     return n < 0 || n >= name.Length - 1 ? name : name.Substring(n + 1); 
    } 

    protected abstract object BindModel(string attemptedValue, Action<string> addModelError); 
} 

użyłem klasy bazowej tak, aby w prosty sposób tworzyć dodatkowe niestandardowy model spoiw.

Oto mój dostawca segregatorów. Zauważ, że jest to poprawnie wywoływane z mojego kontenera IoC, więc nie będę zawracał sobie głowy pokazywaniem tego aspektu mojego kodu.

public class SystemizerModelBinderProvider : ModelBinderProvider 
{ 
    public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType) 
    { 
     if(modelType == typeof(TimeZoneInfo)) 
      return new TimeZoneModelBinder(); 

     return null; 
    } 
} 

Wreszcie, oto sposób działania i model klasy:

[DataContract)] 
public class TestModel 
{ 
    [DataMember] 
    public TimeZoneInfo TimeZone { get; set; } 
} 

[HttpPost] 
public HttpResponseMessage Test(TestModel model) 
{ 
    return Request.CreateResponse(HttpStatusCode.OK, model); 
} 

Dla metody działania, próbowałem:

public HttpResponseMessage Test([FromBody] TestModel model) 

wywoływany jest FormUrlEncodedMediaFormatter, który wydaje się ignorować moje niestandardowy model spoiwa w ogóle.

public HttpResponseMessage Test([ModelBinder] TestModel model) 

Stawia to pod moim model niestandardowy spoiwem, jak się spodziewano, ale to tylko zapewnia ValueProviders dla RouteData i QueryString iz jakiegoś powodu nie zapewnia niczego dla zawartości ciała. Patrz poniżej:

Value Providers

Próbowałem zostały również dekorowanie samej klasy z ModelBinder(typeof(SystemizerModelBinderProvider))

Dlaczego wiążący jedyny model wystąpić podczas korzystania z [ModelBinder] atrybut, a dlaczego to jedynie próbować odczytać trasa i wartości zapytania oraz ignorowanie treści ciała? Dlaczego FromBody ignoruje mojego dostawcę niestandardowego modelu wiązania?

Jak utworzyć scenariusz, w którym mogę odbierać dane POSTED x-www-form-urlencoded i pomyślnie powiązać właściwości modelu za pomocą niestandardowej logiki?

+0

prostu ciekawi, używacie ReSharper? –

Odpowiedz

26

Polecam Ci czytanie following blog post w którym Mike Stall wyjaśnia w szczegółach jak wiązanie modelu pracuje w API Web:

Istnieją 2 techniki parametrów modelu Oprawa: Oprawa i formatujących. W praktyce interfejs WebAPI używa wiązania modelu do odczytu z ciągu zapytania i formaterów w celu odczytania z treści.

Oto podstawowe zasady w celu określenia, czy dany parametr jest odczytywany z modelu wiązania lub formater:

  1. Jeśli parametr ma atrybut na nim, a następnie zostanie podjęta decyzja wyłącznie na parametr jest. Typ NET. "Typy proste" używa wiązania modelu . Złożone typy używają formaterów. "Typ prosty" obejmuje: prymitywy, TimeSpan, DateTime, Guid, Decimal, String, lub coś w rodzaju z TypeConverter, który konwertuje z łańcuchów.
  2. Można użyć atrybutu [FromBody], aby określić, że parametr należy odczytać z treści .
  3. Można użyć atrybutu [ModelBinder] dla parametru lub parametru , aby określić, że parametr powinien być powiązany z modelem. Ten atrybut umożliwia również skonfigurowanie modułu wiążącego model. [FromUri] to pochodząca instancja o numerze [ModelBinder], która specyficznie konfiguruje segregator modelu , aby wyglądać tylko w identyfikatorze URI.
  4. Ciało można odczytać tylko raz. Więc jeśli masz 2 złożone typy w podpisie, przynajmniej jeden z nich musi mieć na sobie atrybut [ModelBinder].

Więc jeśli źródłem twoich danych jest organem Żądanie to można utworzyć niestandardowy MediaTypeFormatter zamiast model wiążący.

+0

Czy FormUrlEncodedMediaFormatter nie wykonuje żadnego wiązania modelu? Czy powinienem go odziedziczyć i coś zmienić? –

+1

"FormUrlEncodedMediaTypeFormatter" odczytuje treść żądania i używa niestandardowego analizatora składni do przeanalizowania żądania 'application/x-www-form-urlencoded' w zbiorze' KeyValuePair ', który jest następnie używany do konstruowania Model. –

+0

Którego atrybutu użyłeś w swojej metodzie akcji z MediaTypeFormatter, '[ModelBinder]', '[FromBody]' lub '[ModelBinder (typeof (x))]? – Mourndark

7

ModelBinder wydaje się stosunkowo lepiej używać niż MediaTypeFormatter. Nie musisz rejestrować go globalnie.

Znalazłem inną alternatywę, aby użyć spoiwa modelu do wiązania złożonych typów obiektów w Web API. W segregatorze modelu odczytywam treść żądania jako ciąg, a następnie za pomocą JSON.NET do deserializacji go do wymaganego typu obiektu. Można go również wykorzystać do mapowania tablic złożonych typów obiektów.

Dodałem modelu spoiwo następująco:

public class PollRequestModelBinder : IModelBinder 
{ 
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) 
    { 
     var body = actionContext.Request.Content.ReadAsStringAsync().Result; 
     var pollRequest = JsonConvert.DeserializeObject<PollRequest>(body); 
     bindingContext.Model = pollRequest; 
     return true; 
    } 
} 

A potem używam go w sterowniku API Web następująco:

public async Task<PollResponse> Post(Guid instanceId, [ModelBinder(typeof(PollRequestModelBinder))]PollRequest request) 
    { 
     // api implementation 
    } 
+0

Dobra odpowiedź, ale co z weryfikacją modelu za pomocą anotacji danych? – Rasmus

+0

JSON.NET zapewnia poprawność z atrybutem JsonProoperty (http://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_JsonPropertyAttribute_pl.htm). Możesz ustawić właściwość "Wymagane", aby ustawić Wymagane, ZezwalajNull itp. –

Powiązane problemy