2013-06-14 14 views
6

Mam klasy DTO, który wygląda na przykład tak:.NET Web: atrybut walidacja poziomie klasy powoduje Web API rzucać ArgumentNullException jeśli instancja jest równa null

public class ExampleDto 
{ 
    [DataMember(Name = "Date", IsRequired = true, Order = 1), Required] 
    public DateTime Date { get; set; } 

    [DataMember(Name = "ParentExample", IsRequired = false, Order = 2, EmitDefaultValue = false)] 
    public Guid? ParentExampleId { get; set; } 
} 

Jeśli, jako przykład, użytkownik wprowadzi nieprawidłową datę, takie jak ten:

<?xml version="1.0" encoding="UTF-8" ?> 
<ExampleDto xmlns="http://customurl/"> 
     <Date>2012-05-25T18:23:INCORRECTDATE</Date> 
     <ParentExample>B62F10A8-4998-4626-B5B0-4B9118E11BEC</ParentExample> 
</ExampleDto> 

lub po prostu pusty korpus, a następnie argument ExampleDto przeszedł do działania będzie zerowa (w pierwszym przypadku ModelState będą miały błędy).

I zastosował CustomValidationAttribute do klasy, więc deklaracja klasy wygląda następująco:

[CustomValidation(typeof(CustomExampleValidator), "Validate")] 
public class ExampleDto 

Teraz dodałem to, jeśli argument ExampleDto jest zerowy (z powodu pustego ciała, lub problem serializacji), ArgumentNullException jest wyrzucany:

<?xml version="1.0" encoding="UTF-8"?> 
<Response xmlns="http://customurl" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> 
    <Type>Failure</Type> 
    <Message>An unknown error has occurred</Message> 
    <Errors> 
     <Error> 
      <Message>System.ArgumentNullException</Message> 
      <MessageDetail>Value cannot be null. Parameter name: 
       instance</MessageDetail> 
      <StackTrace> at System.ComponentModel.DataAnnotations.ValidationContext..ctor(Object 
       instance, IServiceProvider serviceProvider, 
       IDictionary`2 items) at System.Web.Http.Validation.Validators.DataAnnotationsModelValidator.Validate(ModelMetadata 
       metadata, Object container) at System.Web.Http.Validation.DefaultBodyModelValidator.ShallowValidate(ModelMetadata 
       metadata, ValidationContext validationContext, 
       Object container) at System.Web.Http.Validation.DefaultBodyModelValidator.ValidateNodeAndChildren(ModelMetadata 
       metadata, ValidationContext validationContext, 
       Object container) at System.Web.Http.Validation.DefaultBodyModelValidator.Validate(Object 
       model, Type type, ModelMetadataProvider metadataProvider, 
       HttpActionContext actionContext, String keyPrefix) 
       at System.Web.Http.ModelBinding.FormatterParameterBinding.&lt;&gt;c__DisplayClass1.&lt;ExecuteBindingAsync&gt;b__0(Object 
       model) at System.Threading.Tasks.TaskHelpersExtensions.&lt;&gt;c__DisplayClass36`1.&lt;&gt;c__DisplayClass38.&lt;Then&gt;b__35() 
       at System.Threading.Tasks.TaskHelpersExtensions.&lt;&gt;c__DisplayClass49.&lt;ToAsyncVoidTask&gt;b__48() 
       at System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1 
       func, CancellationToken cancellationToken)</StackTrace> 
     </Error> 
    </Errors> 
</Response> 

Reflektor pokazuje, że zerowa kontrola wykonywana jest argumentem przeciwko obiektu w konstruktora ValidationContext, tuż przed CustomValidationAttribute jest wykonywany. Wydaje się to trochę dziwne, ponieważ argumenty zerowe są akceptowane jako argumenty dla działań kontrolerów, nie? Myślę, że wszelkie tutaj zerowe sprawdzanie argumentów POWINNO być wykonywane w kodzie użytkownika lub jawnie przez atrybuty sprawdzania poprawności, zamiast przez framework.

Jeśli użytkownik przesyła prawidłowy kod XML/JSON, ten wyjątek nie jest generowany, a parametr CustomValidationAttribute jest wykonywany zgodnie z oczekiwaniami, ale użytkownicy nie zawsze mogą być zaufani, aby przesłać poprawny plik XML/JSON, a także uzyskać rozwlekły wygląd ArgumentNullException za ich wysiłki, zamiast za to, że jestem w stanie się zwrócić.

Staram się znaleźć kogoś, kto tego doświadczył. Istnieje wiele przykładów zastosowania walidatorów "złożonych" na poziomie właściwości, ale bardziej sensowne jest dla mnie zastosowanie tutaj walidacji na poziomie klasy (ponieważ wymagane są różne właściwości, JEŚLI konkretna właściwość nie jest pusta, a inne są wymagane, JEŻELI inna właściwość nie jest pusta) i nie mogę znaleźć niczego, co mogłoby powiedzieć, że atrybuty sprawdzania poprawności zastosowane na poziomie klasy są nieobsługiwane.

+1

jako obejście, Wprowadziłem jawnie IValidatableObject, aby wykonać walidację. Wolę jednak używać atrybutów sprawdzania poprawności, więc nadal szukam wyjaśnienia. –

+0

Próbowałem powtórzyć Twój problem, ale bez powodzenia ... Spójrz na to zdjęcie: http://tallmaris.com/blog/wp-content/uploads/2013/06/Capture.png. Jak widać wartość dotarła do walidatora i nie jest pusta. Gdy dojdzie do kontrolera, 'IsValid' będzie fałszywe z powodu daty oczywiście. Opublikowane informacje dotyczące tego wyniku to: 'var example = new {Chi =" YYY ", PatientStatus = 1, Date =" 2012-05-25T18: 23: XXXX ", ParentExample =" B62F10A8-4998-4626-B5B0-4B9118E11BEC "};' (następnie Json serializował i przesyłał). – Tallmaris

+0

Witaj Tallmaris, dziękuję za oddanie. Wyjątek nie zostanie zgłoszony, jeśli wartość nie jest pusta, tylko jeśli ma wartość NULL.Spróbuj wysłać z var example = null ;, lub przesłać json ręcznie i z błędem składni w nim. –

Odpowiedz

1

Mam ten sam problem. Niestety z kilkoma atrybutami ValidationAttributes. Przepisanie ich wszystkich na IValidatableObject tuż przed wydaniem jest naprawdę niewykonalne. Więc moja szybkie i brudne rozwiązaniem było złapać tych wyjątków w filtrze i odesłać właściwej odpowiedzi:

public class GeneralExceptionFilterAttribute : ExceptionFilterAttribute 
{ 
    public override void OnException(HttpActionExecutedContext context) 
    { 
     var exceptionType = context.Exception.GetType(); 
     HttpResponseMessage response = null; 

     if(exceptionType == typeof(ArgumentNullException) 
      && context.Exception.StackTrace.TrimStart().StartsWith("at System.ComponentModel.DataAnnotations.ValidationContext..ctor")) 
     { 
      response = new HttpResponseMessage(HttpStatusCode.BadRequest) 
      { 
       Content = new StringContent(context.Exception.Message) 
      }; 
     } 
     else 
     { 
      response = new HttpResponseMessage(HttpStatusCode.InternalServerError) 
      { 
       Content = new StringContent(context.Exception.Message), 
       ReasonPhrase = "Unhandled exception" 
      }; 
     } 

     context.Response = response; 
     _errorLogger.LogError(response?.ReasonPhrase, context.Exception); 
    } 
} 

i zarejestrować filer globalnie w WebApiConfig.cs:

config.Filters.Add(new GeneralExceptionFilterAttribute());