จะแมป View Model กลับไปยัง Domain Model ในการดำเนินการ POST ได้อย่างไร?


87

ทุกบทความที่พบในอินเทอร์เน็ตเกี่ยวกับการใช้ ViewModels และการใช้ Automapper จะให้แนวทางของการทำแผนที่ทิศทาง "Controller -> View" คุณนำโมเดลโดเมนพร้อมกับ Select Lists ทั้งหมดไปไว้ใน ViewModel แบบพิเศษและส่งไปยังมุมมอง ชัดเจนและดี
มุมมองมีรูปแบบและในที่สุดเราก็อยู่ในการดำเนินการ POST ที่นี่ Model Binders ทั้งหมดจะเข้ามาในฉากพร้อมกับ[เห็นได้ชัด] View Model อื่นซึ่ง[อย่างเห็นได้ชัด] เกี่ยวข้องกับ ViewModel ดั้งเดิมอย่างน้อยก็ในส่วนของรูปแบบการตั้งชื่อเพื่อประโยชน์ในการเชื่อมโยงและการตรวจสอบความถูกต้อง

คุณจับคู่กับโมเดลโดเมนของคุณได้อย่างไร?

ปล่อยให้มันเป็นการกระทำแทรกเราสามารถใช้ Automapper เดียวกัน แต่ถ้าเป็นการดำเนินการอัปเดตล่ะ? เราต้องดึงข้อมูลเอนทิตีโดเมนของเราจากที่เก็บอัปเดตคุณสมบัติตามค่าใน ViewModel และบันทึกลงใน Repository

ภาคผนวก 1 (9 กุมภาพันธ์ 2553):บางครั้งการกำหนดคุณสมบัติของโมเดลไม่เพียงพอ ควรมีการดำเนินการบางอย่างกับ Domain Model ตามค่าของ View Model กล่าวคือควรเรียกวิธีการบางอย่างบน Domain Model เป็นไปได้ว่าควรมีชั้น Application Service ประเภทหนึ่งซึ่งอยู่ระหว่าง Controller และ Domain เพื่อประมวลผล View Models ...


จะจัดระเบียบรหัสนี้อย่างไรและจะวางไว้ที่ใดเพื่อให้บรรลุเป้าหมายต่อไปนี้?

  • ให้ตัวควบคุมบาง
  • ให้เกียรติการปฏิบัติของ SoC
  • ปฏิบัติตามหลักการออกแบบที่ขับเคลื่อนด้วยโดเมน
  • แห้ง
  • ยังมีต่อ ...

คำตอบ:


37

ฉันใช้อินเทอร์เฟซIBuilderและใช้งานโดยใช้ValueInjecter

public interface IBuilder<TEntity, TViewModel>
{
      TEntity BuildEntity(TViewModel viewModel);
      TViewModel BuildViewModel(TEntity entity);
      TViewModel RebuildViewModel(TViewModel viewModel); 
}

... (การใช้งาน) RebuildViewModelเพียงแค่เรียกBuildViewModel(BuilEntity(viewModel))

[HttpPost]
public ActionResult Update(ViewModel model)
{
   if(!ModelState.IsValid)
    {
       return View(builder.RebuildViewModel(model);
    }

   service.SaveOrUpdate(builder.BuildEntity(model));
   return RedirectToAction("Index");
}

btw ฉันไม่ได้เขียน ViewModel ฉันเขียน Input เพราะมันสั้นกว่ามาก แต่นั่นก็ไม่ใช่เรื่องสำคัญจริงๆที่
หวังว่ามันจะช่วยได้

อัปเดต: ตอนนี้ฉันกำลังใช้แนวทางนี้ในแอปสาธิต ProDinner ASP.net MVCซึ่งเรียกว่า IMapper ในขณะนี้นอกจากนี้ยังมี pdf ที่มีการอธิบายวิธีการนี้โดยละเอียด


ฉันชอบแนวทางนี้ สิ่งหนึ่งที่ฉันไม่ชัดเจนคือการใช้งาน IBuilder โดยเฉพาะอย่างยิ่งในแง่ของแอปพลิเคชันระดับ ตัวอย่างเช่น ViewModel ของฉันมี SelectLists 3 รายการ การใช้งานตัวสร้างดึงค่ารายการที่เลือกจากที่เก็บอย่างไร
Matt Murrell

@Matt Murrell ดูที่prodinner.codeplex.comฉันทำสิ่งนี้ที่นั่นและฉันเรียกมันว่า IMapper ที่นั่นแทนที่จะเป็น IBuilder
Omu

6
ฉันชอบแนวทางนี้ฉันนำตัวอย่างมาใช้ที่นี่: gist.github.com/2379583
Paul Stovell

ในใจของฉันมันไม่สอดคล้องกับวิธีการแบบจำลองโดเมน ดูเหมือนแนวทาง CRUD สำหรับข้อกำหนดที่ไม่ชัดเจน เราไม่ควรใช้ Factories (DDD) และวิธีการที่เกี่ยวข้องใน Domain Model เพื่อถ่ายทอดการกระทำที่สมเหตุสมผลหรือไม่? ด้วยวิธีนี้เราควรโหลดเอนทิตีจาก DB และอัปเดตตามที่ต้องการใช่ไหม ดูเหมือนจะไม่ถูกต้องทั้งหมด
Artyom

7

เครื่องมือเช่น AutoMapper สามารถใช้เพื่ออัปเดตวัตถุที่มีอยู่ด้วยข้อมูลจากวัตถุต้นทาง การดำเนินการของคอนโทรลเลอร์สำหรับการอัปเดตอาจมีลักษณะดังนี้:

[HttpPost]
public ActionResult Update(MyViewModel viewModel)
{
    MyDataModel dataModel = this.DataRepository.GetMyData(viewModel.Id);
    Mapper<MyViewModel, MyDataModel>(viewModel, dataModel);
    this.Repostitory.SaveMyData(dataModel);
    return View(viewModel);
}

นอกเหนือจากสิ่งที่ปรากฏในตัวอย่างด้านบน:

  • ข้อมูล POST เพื่อดูโมเดล + การตรวจสอบความถูกต้องเสร็จสิ้นใน ModelBinder (สามารถออกได้ด้วยการผูกแบบกำหนดเอง)
  • การจัดการข้อผิดพลาด (เช่นการตรวจจับข้อยกเว้นการเข้าถึงข้อมูลที่ถูกส่งโดย Repository) สามารถทำได้โดยตัวกรอง [HandleError]

การดำเนินการของคอนโทรลเลอร์นั้นค่อนข้างเบาบางและแยกข้อกังวลออกไป: ปัญหาการทำแผนที่ได้รับการแก้ไขแล้วในการกำหนดค่า AutoMapper การตรวจสอบความถูกต้องทำได้โดย ModelBinder และการเข้าถึงข้อมูลโดย Repository


6
ฉันไม่แน่ใจว่า Automapper มีประโยชน์ที่นี่เนื่องจากไม่สามารถย้อนกลับการแบนได้ ท้ายที่สุด Domain Model ไม่ใช่ DTO ธรรมดา ๆ เช่น View Model ดังนั้นจึงอาจไม่เพียงพอที่จะกำหนดคุณสมบัติบางอย่างให้กับมัน อาจเป็นไปได้ว่าควรดำเนินการบางอย่างกับ Domain Model ตามเนื้อหาของ View Model อย่างไรก็ตาม +1 สำหรับการแบ่งปันแนวทางที่ดีทีเดียว
Anthony Serdyukov

@Anton ValueInjecter สามารถย้อนกลับการแบนได้;)
Omu

ด้วยวิธีนี้คุณจะไม่ทำให้ตัวควบคุมบางคุณละเมิด SoC และ DRY ...
Rookian

5

ฉันอยากจะบอกว่าคุณใช้คำว่า ViewModel ซ้ำสำหรับทั้งสองทิศทางของการโต้ตอบกับไคลเอนต์ หากคุณอ่านโค้ด ASP.NET MVC ในไวด์มากพอคุณอาจเห็นความแตกต่างระหว่าง ViewModel และ EditModel ผมคิดว่าเป็นสิ่งสำคัญ

ViewModel แสดงถึงข้อมูลทั้งหมดที่จำเป็นในการแสดงผลมุมมอง ซึ่งอาจรวมถึงข้อมูลที่แสดงผลในตำแหน่งที่ไม่มีการโต้ตอบแบบคงที่และข้อมูลเพื่อทำการตรวจสอบเพื่อตัดสินใจว่าจะแสดงผลแบบใดกันแน่ โดยทั่วไปแอ็คชัน Controller GET มีหน้าที่ในการบรรจุ ViewModel สำหรับ View

EditModel (หรืออาจจะเป็น ActionModel) แทนข้อมูลที่จำเป็นในการดำเนินการที่ผู้ใช้ต้องการทำสำหรับ POST นั้น ดังนั้น EditModel จึงพยายามอธิบายการกระทำจริงๆ สิ่งนี้อาจจะยกเว้นข้อมูลบางส่วนจาก ViewModel และแม้ว่าจะเกี่ยวข้องกันฉันคิดว่าสิ่งสำคัญคือต้องตระหนักว่ามันแตกต่างกันอย่างแน่นอน

หนึ่งความคิด

ที่กล่าวว่าคุณสามารถมีการกำหนดค่า AutoMapper ได้อย่างง่ายดายสำหรับการเริ่มต้นจาก Model -> ViewModel และอีกแบบหนึ่งเพื่อไปจาก EditModel -> Model จากนั้นการกระทำของคอนโทรลเลอร์ที่แตกต่างกันก็ต้องใช้ AutoMapper นรก EditModel อาจมีฟังก์ชั่นในการตรวจสอบคุณสมบัติของมันกับโมเดลและเพื่อใช้ค่าเหล่านั้นกับโมเดลเอง มันไม่ได้ทำอย่างอื่นและคุณมี ModelBinders ใน MVC เพื่อแมปคำขอกับ EditModel อยู่ดี

แนวคิดอื่น

นอกเหนือจากนั้นสิ่งที่ฉันคิดเกี่ยวกับเมื่อเร็ว ๆ นี้ประเภทนี้ได้ผลจากแนวคิดของ ActionModel ก็คือสิ่งที่ลูกค้าโพสต์กลับมาหาคุณนั้นเป็นคำอธิบายของการกระทำหลายอย่างที่ผู้ใช้ทำไม่ใช่แค่ข้อมูลขนาดใหญ่เพียงก้อนเดียว แน่นอนว่าสิ่งนี้จะต้องใช้ Javascript ในฝั่งไคลเอ็นต์ในการจัดการ แต่ฉันคิดว่าแนวคิดนี้น่าสนใจ

โดยพื้นฐานแล้วเมื่อผู้ใช้ดำเนินการบนหน้าจอที่คุณนำเสนอ Javascript จะเริ่มสร้างรายการวัตถุการดำเนินการ ตัวอย่างอาจเป็นไปได้ว่าผู้ใช้อยู่ที่หน้าจอข้อมูลพนักงาน พวกเขาอัปเดตนามสกุลและเพิ่มที่อยู่ใหม่เนื่องจากพนักงานเพิ่งแต่งงาน ภายใต้ฝาครอบนี้จะสร้างChangeEmployeeNameและAddEmployeeMailingAddressวัตถุในรายการ ผู้ใช้คลิก 'บันทึก' เพื่อดำเนินการเปลี่ยนแปลงและคุณส่งรายการของวัตถุสองชิ้นซึ่งแต่ละรายการมีเพียงข้อมูลที่จำเป็นในการดำเนินการแต่ละอย่าง

คุณจะต้องมี ModelBinder ที่ชาญฉลาดมากขึ้นจากนั้นจึงเป็นค่าเริ่มต้น แต่ serializer JSON ที่ดีควรสามารถดูแลการแมปของวัตถุการดำเนินการฝั่งไคลเอ็นต์กับวัตถุฝั่งเซิร์ฟเวอร์ได้ ฝั่งเซิร์ฟเวอร์ (หากคุณอยู่ในสภาพแวดล้อมแบบ 2 ชั้น) อาจมีเมธอดที่ดำเนินการกับโมเดลที่ทำงานด้วยได้อย่างง่ายดาย ดังนั้นการดำเนินการของคอนโทรลเลอร์จะจบลงด้วยการได้รับ Id สำหรับอินสแตนซ์ Model เพื่อดึงและรายการของการดำเนินการที่จะดำเนินการ หรือการดำเนินการมีรหัสอยู่เพื่อให้แยกออกจากกัน

ดังนั้นอาจมีบางอย่างเช่นนี้เกิดขึ้นที่ฝั่งเซิร์ฟเวอร์:

public interface IUserAction<TModel>
{
     long ModelId { get; set; }
     IEnumerable<string> Validate(TModel model);
     void Complete(TModel model);
}

[Transaction] //just assuming some sort of 2-tier with transactions handled by filter
public ActionResult Save(IEnumerable<IUserAction<Employee>> actions)
{
     var errors = new List<string>();
     foreach( var action in actions ) 
     {
         // relying on ORM's identity map to prevent multiple database hits
         var employee = _employeeRepository.Get(action.ModelId);
         errors.AddRange(action.Validate(employee));
     }

     // handle error cases possibly rendering view with them

     foreach( var action in editModel.UserActions )
     {
         var employee = _employeeRepository.Get(action.ModelId);
         action.Complete(employee);
         // against relying on ORMs ability to properly generate SQL and batch changes
         _employeeRepository.Update(employee);
     }

     // render the success view
}

นั่นทำให้การดำเนินการโพสต์กลับค่อนข้างเป็นเรื่องธรรมดาเนื่องจากคุณใช้ ModelBinder ของคุณเพื่อให้คุณได้รับอินสแตนซ์ IUserAction ที่ถูกต้องและอินสแตนซ์ IUserAction ของคุณเพื่อดำเนินการตรรกะที่ถูกต้องเองหรือ (เป็นไปได้มากกว่า) เรียกเข้าสู่ Model พร้อมข้อมูล

หากคุณอยู่ในสภาพแวดล้อม 3 ชั้น IUserAction สามารถสร้าง DTO แบบง่าย ๆ เพื่อยิงข้ามขอบเขตและดำเนินการในวิธีการที่คล้ายกันในเลเยอร์แอป ขึ้นอยู่กับว่าคุณทำเลเยอร์นั้นอย่างไรมันสามารถแบ่งออกได้ง่ายมากและยังคงอยู่ในธุรกรรม (สิ่งที่อยู่ในใจคือคำขอ / คำตอบของ Agatha และการใช้ประโยชน์จากแผนที่ข้อมูลประจำตัวของ DI และ NHibernate)

อย่างไรก็ตามฉันแน่ใจว่ามันไม่ใช่ความคิดที่สมบูรณ์แบบมันต้องใช้ JS ในฝั่งไคลเอนต์ในการจัดการและฉันยังไม่สามารถทำโปรเจ็กต์เพื่อดูว่ามันออกมาอย่างไร แต่โพสต์พยายามคิดว่าจะทำอย่างไร ไปที่นั่นแล้วกลับมาอีกครั้งดังนั้นฉันคิดว่าฉันจะให้ความคิดของฉัน ฉันหวังว่ามันจะช่วยได้และฉันชอบที่จะได้ยินวิธีอื่น ๆ ในการจัดการการโต้ตอบ


น่าสนใจ. เกี่ยวกับความแตกต่างระหว่าง ViewModel และ EditModel ... คุณแนะนำว่าสำหรับฟังก์ชันแก้ไขคุณจะใช้ ViewModel เพื่อสร้างแบบฟอร์มจากนั้นเชื่อมโยงกับ EditModel เมื่อผู้ใช้โพสต์หรือไม่ ถ้าเป็นเช่นนั้นคุณจะจัดการกับสถานการณ์ที่คุณต้องโพสต์แบบฟอร์มใหม่ได้อย่างไรเนื่องจากข้อผิดพลาดในการตรวจสอบความถูกต้อง (ตัวอย่างเช่นเมื่อ ViewModel มีองค์ประกอบที่จะเติมข้อมูลแบบเลื่อนลง) คุณจะรวมองค์ประกอบแบบเลื่อนลงใน EditModel ด้วยหรือไม่ ในกรณีนี้อะไรคือความแตกต่างระหว่างทั้งสอง?
UpTheCreek

ฉันเดาว่าคุณกังวลว่าถ้าฉันใช้ EditModel แล้วเกิดข้อผิดพลาดฉันต้องสร้าง ViewModel ของฉันใหม่ซึ่งอาจมีราคาแพงมาก ฉันจะบอกว่าแค่สร้าง ViewModel ใหม่และตรวจสอบให้แน่ใจว่ามีที่สำหรับใส่ข้อความแจ้งเตือนผู้ใช้ (อาจเป็นทั้งข้อความบวกและลบเช่นข้อผิดพลาดในการตรวจสอบความถูกต้อง) หากปรากฎว่าเป็นปัญหาด้านประสิทธิภาพคุณสามารถแคช ViewModel ได้ตลอดเวลาจนกว่าคำขอถัดไปของเซสชันนั้นจะสิ้นสุดลง (อาจเป็นโพสต์ของ EditModel)
Sean Copenhaver

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.