2009-04-30 34 views
9

Tôi đang cố gắng sử dụng MVC cho một dự án mới sau khi đã được xung quanh khối với tất cả các mẫu và hướng dẫn và như vậy. Tuy nhiên, tôi đang có một thời gian khó khăn để tìm ra nơi mà một số điều nên diễn ra.Câu hỏi ràng buộc và xác thực mô hình ASP.NET MVC

Ví dụ, tôi có một thực thể được gọi là Hồ sơ. Thực thể này chứa các công cụ loại hồ sơ bình thường cùng với thuộc tính DateOfBirth thuộc kiểu DateTime. Trên biểu mẫu HTML, ngày sinh của trường được chia thành 3 trường. Bây giờ, tôi biết tôi có thể sử dụng một mô hình tùy chỉnh chất kết dính để xử lý này, nhưng nếu ngày nhập vào không phải là một ngày hợp lệ? Tôi có nên kiểm tra điều đó trong chất kết dính mô hình không? Nên tất cả các xác nhận của tôi đi vào mô hình chất kết dính? Có ok để chỉ có một vài điều xác nhận trong mô hình chất kết dính và xác nhận phần còn lại trong bộ điều khiển hoặc mô hình chính nó?

Đây là mã tôi hiện có, nhưng nó không có vẻ phù hợp với tôi. Có vẻ bẩn hoặc có mùi.

namespace WebSite.Models 
{ 
    public class ProfileModelBinder : IModelBinder 
    { 
     public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
     { 
      DateTime birthDate; 

      var form = controllerContext.HttpContext.Request.Form; 
      var state = controllerContext.Controller.ViewData.ModelState; 

      var profile = new Profile(); 
      profile.FirstName = form["FirstName"]; 
      profile.LastName = form["LastName"]; 
      profile.Address = form["Address"]; 
      profile.Address2 = form["Address2"]; 
      profile.City = form["City"]; 
      profile.State = form["State"]; 
      profile.Zip = form["Zip"]; 
      profile.Phone = form["Phone"]; 
      profile.Email = form["Email"]; 
      profile.Created = DateTime.UtcNow; 
      profile.IpAddress = controllerContext.HttpContext.Request.UserHostAddress; 

      var dateTemp = string.Format("{0}/{1}/{2}", 
       form["BirthMonth"], form["BirthDay"], form["BirthYear"]); 

      if (string.IsNullOrEmpty(dateTemp)) 
       state.AddModelError("BirthDate", "Required"); 
      else if (!DateTime.TryParse(dateTemp, out birthDate)) 
       state.AddModelError("BirthDate", "Invalid"); 
      else 
       profile.BirthDate = birthDate; 

      return profile; 
     }   
    } 
} 

Xây dựng trên mã mẫu ở trên, bạn sẽ làm thông báo xác thực cho trường 3 phần như thế nào? Trong trường hợp trên, tôi đang sử dụng một khóa hoàn toàn riêng biệt không thực sự tương ứng với một trường trong biểu mẫu, bởi vì tôi không muốn thông báo lỗi xuất hiện bên cạnh tất cả 3 trường. Tôi chỉ muốn nó xuất hiện ở bên phải của trường Năm.

+0

Bạn đang tìm kiếm xác thực mẫu hoặc xác thực biểu mẫu? Tôi muốn giới thiệu cả hai. Bằng cách này, bạn có thể moq mô hình cũng như cung cấp giao diện người dùng phong phú trên giao diện người dùng. –

Trả lời

5

Tôi nghĩ rằng đó là hợp lý để làm xác thực trong mô hình kết dính.Như Craig chỉ ra, xác nhận chủ yếu là tài sản của lĩnh vực kinh doanh của bạn, tuy nhiên:

  1. Đôi khi mô hình của bạn chỉ là một người mẫu trình bày câm, không phải là một đối tượng kinh doanh
  2. Có cơ chế khác nhau mà bạn có thể sử dụng để bề mặt các kiến ​​thức xác nhận vào mô hình kết dính.

Thomas cung cấp cho bạn một ví dụ về số 1.

Ví dụ về số 2 là khi bạn tuyên bố desribe xác nhận hiểu biết bằng cách sử dụng thuộc tính (như thuộc tính DataAnnotation [Bắt buộc]) hoặc tiêm dịch vụ xác thực lớp doanh nghiệp vào trình kết nối mô hình tùy chỉnh. Trong những tình huống này, chất kết dính mô hình là một nơi lý tưởng để chăm sóc xác nhận.

Điều đó đang được nói, mô hình ràng buộc (tìm kiếm, chuyển đổi và xáo trộn dữ liệu vào một đối tượng) và xác thực (dữ liệu đáp ứng các đặc điểm của chúng tôi) là hai mối quan tâm riêng biệt. Bạn có thể lập luận rằng họ nên tách biệt các giai đoạn/thành phần/điểm mở rộng, nhưng chúng tôi có những gì chúng tôi có, mặc dù DefaultModelBinder tạo ra một số khác biệt giữa hai trách nhiệm này. Nếu tất cả những gì bạn muốn làm là cung cấp một số xác nhận cho một kiểu đối tượng cụ thể mà bạn có thể lấy được từ DefaultModelBinder và ghi đè phương thức OnPropertyValidating để xác thực mức thuộc tính hoặc OnModelUpdated nếu bạn cần xem toàn diện.

Đây là mã tôi có bây giờ, nhưng nó không có vẻ phù hợp với tôi. Dường như bẩn hoặc có mùi.

Đối với mã cụ thể của bạn, tôi sẽ cố gắng viết một trình kết nối mô hình chỉ cho DateTime. Trình kết nối mô hình mặc định có thể xử lý tên, ràng buộc cuối cùng, vv, và ủy quyền cho trình kết nối mô hình tùy chỉnh của bạn khi nó đến một thuộc tính DateTime trên Hồ sơ. Ngoài ra, hãy thử sử dụng valueProvider trong bindingContext thay vì chuyển trực tiếp đến biểu mẫu. Những thứ này có thể giúp bạn linh hoạt hơn.

Thêm suy nghĩ tại đây: 6 Tips for ASP.NET MVC Model Binding.

+0

điều này sẽ hoạt động đối với các xác thực phía máy chủ (khi biểu mẫu được đăng), nhưng còn phía client thì sao? Tôi đang ở trong tình trạng tương tự như OP, nhưng không thể tìm ra cách để jimmy trong một quy tắc mới vào công cụ xác nhận phía máy khách (thay vì có ba xác nhận hợp lệ - một cho mỗi trường). Suy nghĩ? – elsurudo

+0

Vâng, mở rộng xác thực phía khách hàng là một chút phức tạp, nhưng có thể. Điều tôi muốn xem xét là xây dựng một giao diện người dùng khiến người dùng khó nhập các giá trị không hợp lệ (bằng cách sử dụng các trình kéo hoặc thanh trượt), và sau đó nắm bắt các trường hợp cạnh trên máy chủ. – OdeToCode

0

Ứng dụng mẫu Contact Manager trên trang web http://www.asp.net/mvc có mô tả tuyệt vời về việc tách logic xác thực của bạn thành lớp dịch vụ khỏi bộ điều khiển và mô hình của bạn.

Hoạt động tốt khi đọc

+0

Vâng tôi đã nhìn thấy ứng dụng. Điều đó không có gì cho câu hỏi cụ thể tôi đặt ra. Vấn đề của tôi là mô hình chứa trường DateTime. Tuy nhiên, biểu mẫu thể hiện điều này dưới dạng 3 hộp văn bản. Vì vậy, tôi phải làm một số hình thức xác nhận trước khi mô hình thậm chí bị ràng buộc, và tôi đang cố gắng tìm ra cách tốt nhất để làm như vậy. – Chris

+1

Hãy xem bài đăng này: http://www.hanselman.com/blog/SplittingDateTimeUnitTestingASPNETMVCCustomModelBinders.aspx – Charlino

1

Xác thực phải được thực hiện ở nhiều nơi, theo chức năng của từng địa điểm. Ví dụ: nếu trình kết nối mô hình của bạn không thể tìm thấy các giá trị đã gửi vào giá trị Ngày giờ đúng, thì trình kết nối có thể thêm lỗi trạng thái mô hình. Mặt khác, nếu logic nghiệp vụ của bạn yêu cầu ngày phải nằm trong một phạm vi nhất định, điều này sẽ không phù hợp để làm và mô hình kết dính; nó phải ở trong lớp logic nghiệp vụ. Các bộ điều khiển cũng có thể thêm các lỗi xác nhận nếu, ví dụ, mô hình chỉnh sửa không thể được chuyển đổi thành một mô hình thực thể.

Một khuôn khổ xác thực như xVal làm cho việc này đơn giản hơn rất nhiều.

2

Tôi đã có cùng một tình huống chính xác vào ngày khác ... bên dưới là mã ràng buộc mô hình của tôi. Về cơ bản nó liên kết tất cả các DateTime? các trường của một mô hình cho các trường tháng/ngày/năm từ một biểu mẫu (nếu có thể) Vì vậy, có, tôi thêm vào xác nhận hợp lệ ở đây, vì nó có vẻ thích hợp để làm như vậy.

public class DateModelBinder : DefaultModelBinder 
    { 

     protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor) 
     { 

      if (propertyDescriptor.PropertyType == typeof(DateTime?)) 
      { 
       string DateMonth = _GetDateValue(bindingContext, propertyDescriptor.Name + "Month"); 
       string DateDay = _GetDateValue(bindingContext, propertyDescriptor.Name + "Day"); 
       string DateYear = _GetDateValue(bindingContext, propertyDescriptor.Name + "Year"); 
       // Try to parse the date if we have at least a month, day or year 
       if (!String.IsNullOrEmpty(DateMonth) || !String.IsNullOrEmpty(DateDay) || !String.IsNullOrEmpty(DateYear)) 
       { 
        DateTime fullDate; 
        CultureInfo enUS = new CultureInfo("en-US"); 
        // If we can parse it, set the model property 
        if (DateTime.TryParse(DateMonth + "/" + DateDay + "/" + DateYear, 
             enUS, 
             DateTimeStyles.None, out fullDate)) 
        { 
         SetProperty(controllerContext, bindingContext, propertyDescriptor, (DateTime?)fullDate); 
        } 
        // The date is invalid, so we need to add a model error 
        else 
        { 
         string ModelPropertyName = bindingContext.ModelName; 
         if(ModelPropertyName != "") 
         { 
          ModelPropertyName += "."; 
         } 
         ModelPropertyName += propertyDescriptor.Name; 
         bindingContext.ModelState.AddModelError(ModelPropertyName, "Invalid date supplied for " + propertyDescriptor.Name); 
        } 
       } 
       return; 
      } 
      base.BindProperty(controllerContext, bindingContext, propertyDescriptor); 
     } 

     // Get a property from binding context 
     private string _GetDateValue(ModelBindingContext bindingContext, string key) 
     { 
      ValueProviderResult valueResult; 
      bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName + "." + key, out valueResult); 
      //Didn't work? Try without the prefix if needed... 
      // && bindingContext.FallbackToEmptyPrefix == true 
      if (valueResult == null) 
      { 
       bindingContext.ValueProvider.TryGetValue(key, out valueResult); 
      } 
      if (valueResult == null) 
      { 
       return null; 
      } 
      return (string)valueResult.ConvertTo(typeof(string)); 
     } 

    } 

Lưu ý: Tôi có một số vấn đề với bindingContext.FallbackToEmptyPrefix luôn sai ... không thể tìm thấy bất kỳ thông tin hữu ích nào về điều đó, nhưng bạn có ý tưởng.

+0

Có vẻ như một bản sao/quá khứ của mã từ bài đăng blog Scott Hanselman: http://www.hanselman.com/blog /SplittingDateTimeUnitTestingASPNETMVCCustomModelBinders.aspx – JMP

4

Đôi khi mô hình là mô hình chế độ xem, không phải là mô hình miền. Trong trường hợp này, bạn có thể hưởng lợi từ việc tách hai thiết bị đó và thiết kế mô hình xem để phù hợp với chế độ xem của bạn.

Bây giờ bạn có thể để mô hình chế độ xem xác thực đầu vào và phân tích ba trường thành một DateTime. Sau đó, nó có thể cập nhật mô hình miền:

public ActionResult SomeAction(ViewModel vm) 
{ 
    if (vm.IsValid) 
    { 
     var dm = repositoryOrSomething.GetDomainModel(); 
     vm.Update(dm); 
    } 

    // more code... 
} 
0

Tôi đã mệt mỏi vì tạo ra các ViewModels có mục đích nhỏ chỉ chạm vào các phần của mô hình miền rộng một dặm của tôi.

Vì vậy, tôi đã nấu phương pháp của riêng mình để giải quyết vấn đề này. ViewModel của tôi là một typeOf DomainModel, và tôi sử dụng một custom model binder để đảm bảo tính chất nhận dạng của nó tải đầu tiên - khi danh tính được thiết lập - nó kích hoạt một DomainModel.Load, và phần còn lại của hoạt động ràng buộc về cơ bản thực hiện một 'hợp nhất'.

Một lần nữa, khi ViewModel của tôi bị ràng buộc (ví dụ: trên biểu mẫu POST), sau khi các trường thiết yếu bao gồm ID được đặt - nó sẽ tải lên ngay lập tức mô hình miền từ cơ sở dữ liệu. Tôi chỉ cần đưa ra một thay thế cho DefaultModelBinder. Trình liên kết mô hình tùy chỉnh của tôi posted here on StackOverflow cho phép bạn kiểm soát thứ tự ràng buộc của các thuộc tính.

Khi tôi có thể đảm bảo rằng đặc tính nhận dạng bị ràng buộc, (nội bộ của chế độ xem của tôi lắng nghe hoàn thành trình định danh) Tôi kích hoạt tải mô hình miền của tôi, tức là 'hợp nhất' vào mô hình miền được tải. Về cơ bản, tôi có thể có tất cả các chế độ xem dao cạo khác nhau, cho dù chúng phơi bày 5 trường biểu mẫu hoặc 50 trường của mô hình .. tất cả đều gửi đến một hành động điều khiển trông như thế này (được cấp, tôi vẫn thực hiện các hành động riêng biệt khi cần thiết làm công cụ kinh doanh tùy chỉnh phù hợp ..nhưng vấn đề là, hành động điều khiển của tôi tập trung và ngắn gọn)

<HttpPost()> 
<Authorize(Roles:="MYCOMPANY\activeDirRoleForEditing")> 
Function Edit(<Http.FromBody()> ByVal mergedModel As OrderModel) As ActionResult 
    'notice: NO loading logic here - it already happened during model binding 
    'just do the right thing based upon resulting model state 
    If Me.ModelState.IsValid Then 

     mergedModel.SaveAndReload("MyServiceWebConfigKey") 

     ViewBag.SuccessMessage = String.Format("You have successfully edited the order {0}", mergedModel.Id) 

     Return View("Edit", mergedModel) 
    Else 
     ViewBag.ErrorText = String.Format("Order {0} not saved. Check for errors and correct.", mergedModel.Id) 
     Return View("Edit", mergedModel) 
    End If 
End Function 
Các vấn đề liên quan