Oto wdrożenie że mam wymyślić (czekając na nic lepszego pomysłu :))
Jest to ogólne podejście i myślę, że jest dość skalowalny - umożliwiając nadzieją podobny rodzaj sprawdzania głębi do parametrów, jak w przypadku sprawdzania poprawności modelu w tym samym czasie, co funkcja automatycznego reagowania na błędy (gdy stan modelu zawiera jeden lub więcej błędów), którego szukałem.
Mam nadzieję, że to nie jest zbyt dużo kodu dla odpowiedzi SO (!); Miałem mnóstwo komentarzy do dokumentacji, które usunąłem, aby było krótsze.
Tak, w moim scenariuszu Mam dwa typy modelu błędu, że jeżeli one wystąpią, należy zablokować wykonanie metody działania:
- Niepowodzenie sprawdzania poprawności schematu XML, z którego wartość parametru zostanie zbudowana
- Missing (null) parametr wartość
walidacja Schema jest aktualnie wykonywane podczas modelu wiązania i automatycznie dodaje do modelu błędy ModelState - tak to świetnie. Potrzebuję więc sposobu na sprawdzenie automatycznego zerowania.
W końcu stworzył dwie klasy, aby zakończyć sprawdzanie poprawności:
[AttributeUsage(AttributeTargets.Parameter,
AllowMultiple = false, Inherited = false)]
public abstract class ValidateParameterAttribute : Attribute
{
private bool _continueValidation = false;
public bool ContinueValidation
{ get { return _continueValidation; } set { _continueValidation = value; } }
private int _order = -1;
public int Order { get { return _order; } set { _order = value; } }
public abstract bool Validate
(ControllerContext context, ParameterDescriptor parameter, object value);
public abstract ModelError CreateModelError
(ControllerContext context, ParameterDescriptor parameter, object value);
public virtual ModelError GetModelError
(ControllerContext context, ParameterDescriptor parameter, object value)
{
if (!Validate(context, parameter, value))
return CreateModelError(context, parameter, value);
return null;
}
}
[AttributeUsage(AttributeTargets.Parameter,
AllowMultiple = false, Inherited = false)]
public class RequiredParameterAttribute : ValidateParameterAttribute
{
private object _missing = null;
public object MissingValue
{ get { return _missing; } set { _missing = value; } }
public virtual object GetMissingValue
(ControllerContext context, ParameterDescriptor parameter)
{
//using a virtual method so that a missing value could be selected based
//on the current controller's state.
return MissingValue;
}
public override bool Validate
(ControllerContext context, ParameterDescriptor parameter, object value)
{
return !object.Equals(value, GetMissingValue(context, parameter));
}
public override ModelError CreateModelError
(ControllerContext context, ParameterDescriptor parameter, object value)
{
return new ModelError(
string.Format("Parameter {0} is required", parameter.ParameterName));
}
}
Dzięki temu mogę wtedy to zrobić:
public void ActionMethod([RequiredParameter]MyModel p1){ /* code here */ }
Ale to na własną rękę nie robić nic oczywiście, więc teraz potrzebujemy czegoś, aby faktycznie uruchomić sprawdzanie poprawności, aby uzyskać błędy modelu i dodać je do stanu modelu.
Wprowadź ParameterValidationAttribute
:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
Inherited = false)]
public class ParameterValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var paramDescriptors = filterContext.ActionDescriptor.GetParameters();
if (paramDescriptors == null || paramDescriptors.Length == 0)
return;
var parameters = filterContext.ActionParameters;
object paramvalue = null;
ModelStateDictionary modelState
= filterContext.Controller.ViewData.ModelState;
ModelState paramState = null;
ModelError modelError = null;
foreach (var paramDescriptor in paramDescriptors)
{
paramState = modelState[paramDescriptor.ParameterName];
//fetch the parameter value, if this fails we simply end up with null
parameters.TryGetValue(paramDescriptor.ParameterName, out paramvalue);
foreach (var validator in paramDescriptor.GetCustomAttributes
(typeof(ValidateParameterAttribute), false)
.Cast<ValidateParameterAttribute>().OrderBy(a => a.Order)
)
{
modelError =
validator.GetModelError(filterContext, paramDescriptor, paramvalue);
if(modelError!=null)
{
//create model state for this parameter if not already present
if (paramState == null)
modelState[paramDescriptor.ParameterName] =
paramState = new ModelState();
paramState.Errors.Add(modelError);
//break if no more validation should be performed
if (validator.ContinueValidation == false)
break;
}
}
}
base.OnActionExecuting(filterContext);
}
}
Uff! Prawie tam teraz ...
Tak, teraz możemy to zrobić:
[ParameterValidation]
public ActionResult([RequiredParameter]MyModel p1)
{
//ViewData.ModelState["p1"] will now contain an error if null when called
}
pełnego puzzli potrzebujemy czegoś, co można zbadać modelowych błędy i automatycznie reagować, jeśli takie istnieją. Jest to najmniej schludny klas (I hate nazwę i typ parametru używany) i prawdopodobnie będę go zmienić w moim projekcie, ale działa tak wyślę to tak:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
Inherited = false)]
public abstract class RespondWithModelErrorsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
ModelStateDictionary modelState =
filterContext.Controller.ViewData.ModelState;
if (modelState.Any(kvp => kvp.Value.Errors.Count > 0))
filterContext.Result = CreateResult(filterContext,
modelState.Where(kvp => kvp.Value.Errors.Count > 0));
base.OnActionExecuting(filterContext);
}
public abstract ActionResult CreateResult(
ActionExecutingContext filterContext,
IEnumerable<KeyValuePair<string, ModelState>> modelStateWithErrors);
}
W moim Aplikacja Mam XmlResult, który pobiera instancję modelu i serializuje do odpowiedzi przy użyciu albo DataContractSerializer lub XmlSerializer - tak więc stworzyłem RespondWithXmlModelErrorsAttribute
, który dziedziczy z tego ostatniego typu, aby sformułować jeden z tych z modelem jako klasę Errors
, która po prostu zawiera każdą z nich błędów modelu jako łańcuchów. Kod odpowiedzi jest również automatycznie ustawiony na 400 złych żądań.
Tak więc, teraz mogę to zrobić:
[ParameterValidation]
[RespondWithXmlModelErrors(Order = int.MaxValue)]
public ActionResult([RequiredParameter]MyModel p1)
{
//now if p1 is null, the method won't even be called.
}
W przypadku stron internetowych nie musi być wymagane ten ostatni etap, ponieważ błędy modelowe są zwykle zawarte w re-rendering strony, który wysłał dane w pierwszej kolejności, a istniejące podejście MVC odpowiada tej grzywnie.
Ale dla usług internetowych (zarówno XML, jak i JSON) możliwość odciążenia raportowania błędów na coś innego sprawia, że pisanie właściwej metody działania jest o wiele łatwiejsze - i czuję się o wiele bardziej ekspresyjnie.
, ponieważ działa to bardzo dobrze dla mnie i myślę, że jest odpowiednio rozszerzalny, aby być naprawdę ważnym dodatkiem do naszego wewnętrzna biblioteka rozszerzeń Mvc. –
To jest świetne rozwiązanie i to, po czym byłem .. Pozdrawiam Andras. Moja pierwsza próba była prawie na miejscu, ale widzę, czego mi brakowało. – horHAY