2010-02-02 18 views
23

mam następujące działania kontrolera:model niestandardowy spoiwem dla nieruchomości

[HttpPost] 
public ViewResult DoSomething(MyModel model) 
{ 
    // do something 
    return View(); 
} 

Gdzie MyModel wygląda następująco:

public class MyModel 
{ 
    public string PropertyA {get; set;} 
    public IList<int> PropertyB {get; set;} 
} 

Więc DefaultModelBinder należy powiązać to bez problemu. Jedyną rzeczą jest to, że chcę użyć specjalnego/niestandardowego spoiwa do wiązania PropertyB i również chcę ponownie użyć tego segregatora. Tak więc pomyślałem, że rozwiązaniem byłoby umieszczenie atrybutu ModelBinder przed PropertyB, co oczywiście nie działa (atrybut ModelBinder nie jest dozwolony we właściwościach). Widzę dwa rozwiązania:

  1. używać parametrów działania na każdej nieruchomości zamiast całego modelu (który nie woleliby jako model ma wiele właściwości) tak:

    public ViewResult DoSomething(string propertyA, [ModelBinder(typeof(MyModelBinder))] propertyB) 
    
  2. aby utworzyć nowy typ powiedzmy MyCustomType: List<int> i zarejestrować modelu spoiwo dla tego typu (jest to opcja)

  3. Może stworzyć wiążący dla MyModel, zastępują BindProperty i, jeżeli nieruchomość jest "PropertyB" powiązać właściwość z moim niestandardowym segregatorem. czy to możliwe?

Czy istnieje inne rozwiązanie?

Odpowiedz

14

przesłanianie BindProperty i jeżeli nieruchomość jest „PropertyB” wiążą właściwość z moim niestandardowym spoiwa

To dobre rozwiązanie, choć zamiast sprawdzania „jest PropertyB” lepiej sprawdzić na własną rękę niestandardowe atrybuty, które określają właściwość spoiwa poziomie, jak

[PropertyBinder(typeof(PropertyBBinder))] 
public IList<int> PropertyB {get; set;} 

można zobaczyć przykład BindProperty ręcznym here.

13

Naprawdę lubię twoje trzecie rozwiązanie, tylko, zrobiłbym to ogólne rozwiązanie dla wszystkich modeli Binder, przez umieszczenie go w niestandardowym segregatorze, który dziedziczy po DefaultModelBinder i jest skonfigurowany jako domyślny segregator dla twojej aplikacji MVC.

W takim przypadku nowy obiekt DefaultModelBinder będzie automatycznie wiązał każdą właściwość ozdobioną atrybutem PropertyBinder, używając typu podanego w parametrze.

Wpadłem na pomysł z tego wspaniałego artykułu: http://aboutcode.net/2011/03/12/mvc-property-binder.html.

będę również pokazać wam moje zdanie na rozwiązanie:

My DefaultModelBinder:

namespace MyApp.Web.Mvc 
{ 
    public class DefaultModelBinder : System.Web.Mvc.DefaultModelBinder 
    { 
     protected override void BindProperty(
      ControllerContext controllerContext, 
      ModelBindingContext bindingContext, 
      PropertyDescriptor propertyDescriptor) 
     { 
      var propertyBinderAttribute = TryFindPropertyBinderAttribute(propertyDescriptor); 
      if (propertyBinderAttribute != null) 
      { 
       var binder = CreateBinder(propertyBinderAttribute); 
       var value = binder.BindModel(controllerContext, bindingContext, propertyDescriptor); 
       propertyDescriptor.SetValue(bindingContext.Model, value); 
      } 
      else // revert to the default behavior. 
      { 
       base.BindProperty(controllerContext, bindingContext, propertyDescriptor); 
      } 
     } 

     IPropertyBinder CreateBinder(PropertyBinderAttribute propertyBinderAttribute) 
     { 
      return (IPropertyBinder)DependencyResolver.Current.GetService(propertyBinderAttribute.BinderType); 
     } 

     PropertyBinderAttribute TryFindPropertyBinderAttribute(PropertyDescriptor propertyDescriptor) 
     { 
      return propertyDescriptor.Attributes 
       .OfType<PropertyBinderAttribute>() 
       .FirstOrDefault(); 
     } 
    } 
} 

Moja IPropertyBinder interfejsu:

namespace MyApp.Web.Mvc 
{ 
    interface IPropertyBinder 
    { 
     object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext, MemberDescriptor memberDescriptor); 
    } 
} 

My PropertyBinderAttribute:

namespace MyApp.Web.Mvc 
{ 
    public class PropertyBinderAttribute : Attribute 
    { 
     public PropertyBinderAttribute(Type binderType) 
     { 
      BinderType = binderType; 
     } 

     public Type BinderType { get; private set; } 
    } 
} 

Przykład spoiwa właściwość:

namespace MyApp.Web.Mvc.PropertyBinders 
{ 
    public class TimeSpanBinder : IPropertyBinder 
    { 
     readonly HttpContextBase _httpContext; 

     public TimeSpanBinder(HttpContextBase httpContext) 
     { 
      _httpContext = httpContext; 
     } 

     public object BindModel(
      ControllerContext controllerContext, 
      ModelBindingContext bindingContext, 
      MemberDescriptor memberDescriptor) 
     { 
      var timeString = _httpContext.Request.Form[memberDescriptor.Name].ToLower(); 
      var timeParts = timeString.Replace("am", "").Replace("pm", "").Trim().Split(':'); 
      return 
       new TimeSpan(
        int.Parse(timeParts[0]) + (timeString.Contains("pm") ? 12 : 0), 
        int.Parse(timeParts[1]), 
        0); 
     } 
    } 
} 

przykładzie powyższego spoiwa własności używany:

namespace MyApp.Web.Models 
{ 
    public class MyModel 
    { 
     [PropertyBinder(typeof(TimeSpanBinder))] 
     public TimeSpan InspectionDate { get; set; } 
    } 
} 
+1

Sądzę, że to podejście można poprawić, zastępując 'GetPropertyValue'. Zobacz [moja odpowiedź] (http://stackoverflow.com/a/19727630/129073), aby uzyskać szczegółowe informacje. – Gebb

+0

Czy możliwe jest posiadanie podobnej implementacji PropertyBinder dla Web API? – mehul9595

+2

'CreateBinder' nie działa dla mnie z tym konstruktorem, więc upuściłem' HttpContext' i użyłem 'var value = bindingContext.ValueProvider.GetValue (memberDescriptor.Name);', aby uzyskać wartość właściwości. – Brad

5

@ odpowiedź jonathanconway jest wielka, ale Chciałbym dodać drobne szczegóły.

Prawdopodobnie lepiej zastąpić metodę GetPropertyValue zamiast BindProperty, aby mechanizm sprawdzania poprawności dla DefaultBinder mógł działać.

protected override object GetPropertyValue(
    ControllerContext controllerContext, 
    ModelBindingContext bindingContext, 
    PropertyDescriptor propertyDescriptor, 
    IModelBinder propertyBinder) 
{ 
    PropertyBinderAttribute propertyBinderAttribute = 
     TryFindPropertyBinderAttribute(propertyDescriptor); 
    if (propertyBinderAttribute != null) 
    { 
     propertyBinder = CreateBinder(propertyBinderAttribute); 
    } 

    return base.GetPropertyValue(
     controllerContext, 
     bindingContext, 
     propertyDescriptor, 
     propertyBinder); 
} 
+1

W zależności od pożądanego zastosowania może się to udać i może nie działać.Należy pamiętać, że parametr GetPropertyValue jest uruchamiany tylko wtedy, gdy element w obiekcie przychodzącym (w tym przypadku formularz żądania/ciąg zapytania) jest zgodny z właściwością. Jeśli chcesz mieć niestandardowy segregator modelowy, który byłby w stanie wyszukiwać elementy o innej nazwie, powiedz może wypełnij słownik, nie miałbyś szczęścia. Na przykład mapowanie "field1 = test & field2 = ing & field3 = now" mapowałoby na słownik, w którym "1" był kluczem, a "test" - wartość. Rozwiązanie autorstwa Jonathana Conwaya pozwala na to. Może hybryda, pozwalając obu jest lepsza. –

1

Minęło 6 lat, odkąd zadawano to pytanie, wolałbym zająć tę przestrzeń, aby podsumować aktualizację, zamiast dostarczać zupełnie nowe rozwiązanie. W momencie pisania tego artykułu MVC 5 istniało już od jakiegoś czasu, a ASP.NET Core właśnie się pojawił.

Postępowałem zgodnie z podejściem zbadanym w poście napisanym przez Vijaya Anand (btw, dzięki Vijaya): http://www.prideparrot.com/blog/archive/2012/6/customizing_property_binding_through_attributes. Warto zauważyć, że logika powiązania danych jest umieszczana w niestandardowej klasie atrybutów, jaką jest metoda BindProperty klasy StringArrayPropertyBindAttribute w przykładzie Vijaya Anand.

Jednak we wszystkich innych artykułach na ten temat, które przeczytałem (łącznie z rozwiązaniem @ jonathanconway), niestandardowa klasa atrybutów jest tylko kamieniem milowym, która prowadzi do znalezienia właściwego niestandardowego spoiwa modelu do zastosowania; a logika powiązania jest umieszczana w segregatorze niestandardowego modelu, który zwykle jest obiektem IModelBinder.

Pierwsze podejście jest dla mnie prostsze. Mogą istnieć pewne niedociągnięcia w pierwszym podejściu, których jeszcze nie znałem, bo jestem całkiem nowy w ramach MVC w tej chwili.

Ponadto odkryłem, że klasa ExtendedModelBinder w przykładzie Vijaya Anand jest niepotrzebna w MVC 5. Wydaje się, że klasa DefaultModelBinder, która jest dostarczana z MVC 5, jest wystarczająco inteligentna, aby współpracować z niestandardowymi atrybutami wiązania modelu.

Powiązane problemy