2010-09-08 4 views
5

Tôi đã nghĩ ra một số cách để làm điều này nhưng tôi muốn có được cái nhìn của cộng đồng. Tôi có cảm giác rằng câu trả lời là đáng sợ đơn giản - tôi không ngại nhìn ngu ngốc (những đứa trẻ của tôi đã lấy đi nỗi sợ đó từ lâu!)Ngăn chặn phương pháp MVC Action thực hiện nếu tham số là null

Tôi đang viết một dịch vụ web REST XML bằng MVC2. Tất cả các loại XML mà người tiêu dùng của dịch vụ web sẽ nhận và gửi được quản lý bởi XSD đơn giản nhưng mở rộng, và các thông số này sẽ bị ràng buộc từ xml trong phần thân yêu cầu thông qua một trình kết nối mô hình mặc định tùy chỉnh và nhà cung cấp giá trị.

Tôi có một số lượng tốt các bộ điều khiển, mỗi phương pháp có số lượng hành động tốt (không quá - chỉ 'tốt';)) - và trong mọi trường hợp, các phương thức hành động này sẽ chấp nhận các kiểu mô hình loại tham chiếu.

Trong thực tế mọi trường hợp sẽ xảy ra lỗi cho người gọi không cung cấp các giá trị tham số này và như vậy có thể gửi lại thông báo lỗi chuẩn như "The parameter {name} type:{ns:type} is required".

Điều tôi muốn làm là để có thể xác thực tham số không phải là null trước khi thực hiện một phương thức hành động; và sau đó để trả về một ActionResult đại diện cho Error cho máy khách (cho điều này tôi đã có loại XMLResult) mà không có phương thức hành động tự nó phải tự định giá các tham số.

Vì vậy, thay vì:

public ActionResult ActionMethod(RefType model) 
{ 
    if(model == null) 
     return new Xml(new Error("'model' must be provided")); 
} 

Cái gì như:

public ActionResult ActionMethod([NotNull]RefType model) 
{ 
    //model now guaranteed not to be null. 
} 

Tôi biết điều này là chính xác loại xuyên suốt mà có thể đạt được trong MVC.

Dường như với tôi rằng việc ghi đè bộ điều khiển cơ sở là OnActionExecuting hoặc ActionFilter tùy chỉnh là cách có khả năng nhất để thực hiện việc này. Tôi cũng muốn có thể mở rộng hệ thống để nó tự động nhận các lỗi xác nhận lược đồ XML (được thêm vào ModelState trong khi ràng buộc bởi một nhà cung cấp giá trị tùy chỉnh), do đó ngăn chặn phương thức hành động tiếp tục nếu có tham số nào không thể tải các giá trị một cách chính xác vì yêu cầu XML được tạo thành không đúng.

Trả lời

2

Đây là việc thực hiện mà tôi đã đưa ra (trong khi chờ đợi bất kỳ ý tưởng tốt hơn :))

Đó là một cách tiếp cận chung và tôi nghĩ là khá khả năng mở rộng - cho phép hy vọng một tương tự loại chiều sâu để xác thực thông số khi bạn nhận được xác thực mẫu cùng một lúc như cung cấp chức năng tự động trả lời lỗi (khi trạng thái mô hình chứa một hoặc nhiều lỗi) mà tôi đang tìm kiếm.

Tôi hy vọng đây không phải là quá nhiều mã cho câu trả lời SO (!); Tôi đã có một tải ý kiến ​​tài liệu trong đó tôi đã đưa ra để giữ cho nó ngắn hơn.

Vì vậy, trong trường hợp của tôi, tôi có hai loại lỗi mô hình đó, nếu chúng xảy ra, nên chặn thực hiện các phương pháp hành động:

  • Không xác schema của XML từ đó một giá trị tham số sẽ được xây dựng
  • Thiếu (null) tham số giá trị

Schema xác nhận hiện đang thực hiện trong mô hình ràng buộc, và tự động thêm lỗi mô hình để các ModelState - vì vậy đó là tuyệt vời. Vì vậy, tôi cần một cách để thực hiện kiểm tra tự động null.

Cuối cùng tôi đã tạo ra hai lớp học để quấn lên xác nhận:

[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)); 
    } 
} 

Với điều này thì tôi có thể làm điều này:

public void ActionMethod([RequiredParameter]MyModel p1){ /* code here */ } 

Nhưng điều này tự nó không làm bất cứ điều gì của Tất nhiên, vì vậy bây giờ chúng tôi cần một cái gì đó để thực sự kích hoạt xác nhận, để có được các lỗi mô hình và thêm chúng vào trạng thái mô hình.

Nhập 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); 
    } 
} 

Whew! Gần đó bây giờ ...

Vì vậy, bây giờ chúng ta có thể làm điều này:

[ParameterValidation] 
public ActionResult([RequiredParameter]MyModel p1) 
{ 
    //ViewData.ModelState["p1"] will now contain an error if null when called 
} 

Để hoàn thành các câu đố chúng ta cần cái gì đó có thể điều tra các lỗi mô hình và tự động trả lời nếu có bất kỳ. Đây là gọn gàng nhất của các lớp (Tôi ghét tên và kiểu tham số được sử dụng) và tôi có lẽ sẽ thay đổi nó trong dự án của tôi, nhưng nó hoạt động vì vậy tôi sẽ đăng nó anyway:

[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); 
} 

Trong tôi ứng dụng Tôi có một XmlResult mà có một mẫu Model và serializes để đáp ứng bằng cách sử dụng hoặc DataContractSerializer hoặc XmlSerializer - vì vậy tôi đã tạo ra RespondWithXmlModelErrorsAttribute kế thừa từ loại cuối cùng này để xây dựng một trong những người có mô hình như một lớp Errors. của các lỗi mô hình dưới dạng chuỗi. Mã phản hồi cũng tự động được đặt thành 400 Yêu cầu không hợp lệ.

Vì vậy, bây giờ tôi có thể làm điều này:

[ParameterValidation] 
[RespondWithXmlModelErrors(Order = int.MaxValue)] 
public ActionResult([RequiredParameter]MyModel p1) 
{ 
    //now if p1 is null, the method won't even be called. 
} 

Trong trường hợp của các trang web giai đoạn cuối cùng này sẽ không nhất thiết được yêu cầu, vì lỗi mô hình thường được bao gồm trong một tái dựng hình của trang đó gửi các dữ liệu ở nơi đầu tiên, và cách tiếp cận MVC hiện có phù hợp với tiền phạt này.

Nhưng đối với các dịch vụ web (XML hoặc JSON) có thể giảm tải báo cáo lỗi sang một thứ khác, hãy viết phương pháp hành động thực tế dễ dàng hơn nhiều - và tôi cảm thấy nhiều hơn nữa.

+0

chấp nhận câu trả lời này vì nó hoạt động thực sự tốt cho tôi, và tôi nghĩ nó có thể mở rộng phù hợp. thư viện tiện ích mở rộng Mvc trong nhà. –

+0

Đây là một giải pháp tuyệt vời và những gì tôi đã được sau khi .. chúc mừng Andras. Nỗ lực đầu tiên của tôi gần như ở đó nhưng tôi thấy những gì tôi đã mất tích. – horHAY

1

Bạn cũng có thể thêm các ràng buộc bằng cách sử dụng cụm từ thông dụng cho các giá trị tuyến đường riêng lẻ. Sau đó, nếu những khó khăn này không được duy trì, phương pháp hành động sẽ không được nhấn:

routes.MapRoute ("SomeWebService", "service/{userId}", 
       new { controller = "Service", action = "UserService" }, 
       new { userId = @"\d+" }); 

Hoặc bạn có thể tạo ra những hạn chế tùy chỉnh để xác nhận giá trị con đường với nhau như một gói. Điều này có lẽ sẽ là một chiến lược tốt hơn cho bạn. Có một cái nhìn ở đây: Creating a Custom Route Constraint

+0

Rất tiếc, điều này sẽ không hoạt động vì các thông số này bị ràng buộc từ xml trong phần thân yêu cầu, do đó không dễ dàng có sẵn cho công cụ định tuyến; Tôi không muốn kiểm tra xml yêu cầu ở cấp định tuyến để thực hiện xác thực này. Điều này cũng sẽ làm cho việc trả lại một thông báo lỗi tốt đẹp trở nên khó khăn hơn. –

+0

Đã chỉnh sửa để làm nổi bật các tham số này sẽ đến từ phần thân yêu cầu và do đó việc này phải được thực hiện sau khi mô hình ràng buộc –