các vấn đề về quy trình đăng ký nhiều bước trong asp.net mvc (chế độ xem chia nhỏ, mô hình đơn)


117

Tôi có quy trình đăng ký nhiều bước , được hỗ trợ bởi đối tượng trong lớp miền , có các quy tắc xác thực được xác định trên các thuộc tính.

Tôi nên xác thực đối tượng miền như thế nào khi tên miền được chia thành nhiều chế độ xem và tôi phải lưu đối tượng một phần trong chế độ xem đầu tiên khi được đăng?

Tôi đã nghĩ về việc sử dụng Phiên nhưng điều đó là không thể vì quá trình này kéo dài và lượng dữ liệu cao, vì vậy tôi không muốn sử dụng phiên.

Tôi đã nghĩ về việc lưu tất cả dữ liệu trong một db trong bộ nhớ quan hệ (có cùng lược đồ với db chính) và sau đó chuyển dữ liệu đó sang db chính nhưng các vấn đề phát sinh do tôi nên định tuyến giữa các dịch vụ (được yêu cầu trong các khung nhìn), những người làm việc với db chính và db trong bộ nhớ.

Tôi đang tìm kiếm một giải pháp thanh lịch và sạch sẽ (chính xác hơn là một thực tiễn tốt nhất).

CẬP NHẬT VÀ Làm rõ:

@Darin Cảm ơn bạn đã trả lời chu đáo, Đó chính xác là những gì tôi đã làm cho đến bây giờ. Nhưng tình cờ tôi có một yêu cầu có nhiều tệp đính kèm trong đó, tôi thiết kế một Step2Viewví dụ người dùng có thể tải lên các tài liệu trong đó một cách không đồng bộ, nhưng các tệp đính kèm đó phải được lưu trong một bảng có quan hệ tham chiếu đến một bảng khác nên được lưu trước đó trongStep1View .

Do đó, tôi nên lưu đối tượng miền trong Step1(một phần), nhưng tôi không thể, khiến đối tượng Miền lõi được hỗ trợ được ánh xạ một phần vào ViewModel của Step1 không thể được lưu mà không có đạo cụ được chuyển đổi Step2ViewModel.


@Jani, bạn đã bao giờ tìm ra phần tải lên của cái này chưa? Tôi muốn chọn bộ não của bạn. Tôi đang làm việc về vấn đề chính xác này.
Doug Chamberlain

1
Giải pháp trong blog này khá đơn giản và dễ hiểu. Nó sử dụng div như là "các bước" bằng cách thay đổi khả năng hiển thị và xác thực jquery không phô trương của họ.
Dmitry Efimenko

Câu trả lời:


229

Trước tiên, bạn không nên sử dụng bất kỳ đối tượng miền trong quan điểm của bạn. Bạn nên sử dụng mô hình xem. Mỗi mô hình khung nhìn sẽ chỉ chứa các thuộc tính được yêu cầu bởi chế độ xem đã cho cũng như các thuộc tính xác thực cụ thể cho chế độ xem đã cho này. Vì vậy, nếu bạn có 3 bước thuật sĩ, điều này có nghĩa là bạn sẽ có 3 mô hình xem, mỗi mô hình cho mỗi bước:

public class Step1ViewModel
{
    [Required]
    public string SomeProperty { get; set; }

    ...
}

public class Step2ViewModel
{
    [Required]
    public string SomeOtherProperty { get; set; }

    ...
}

và như thế. Tất cả các mô hình khung nhìn này có thể được hỗ trợ bởi mô hình khung nhìn trình hướng dẫn chính:

public class WizardViewModel
{
    public Step1ViewModel Step1 { get; set; }
    public Step2ViewModel Step2 { get; set; }
    ...
}

sau đó bạn có thể có các hành động điều khiển hiển thị từng bước của quy trình trình hướng dẫn và chuyển chính WizardViewModelcho chế độ xem. Khi bạn đang ở bước đầu tiên bên trong hành động của bộ điều khiển, bạn có thể khởi tạo thuộc Step1tính. Sau đó, trong chế độ xem, bạn sẽ tạo biểu mẫu cho phép người dùng điền vào các thuộc tính về bước 1. Khi biểu mẫu được gửi, hành động của bộ điều khiển sẽ chỉ áp dụng quy tắc xác thực cho bước 1:

[HttpPost]
public ActionResult Step1(Step1ViewModel step1)
{
    var model = new WizardViewModel 
    {
        Step1 = step1
    };

    if (!ModelState.IsValid)
    {
        return View(model);
    }
    return View("Step2", model);
}

Bây giờ trong chế độ xem bước 2, bạn có thể sử dụng trình trợ giúp Html.Serialize từ tương lai MVC để tuần tự hóa bước 1 vào một trường ẩn bên trong biểu mẫu (sắp xếp ViewState nếu bạn muốn):

@using (Html.BeginForm("Step2", "Wizard"))
{
    @Html.Serialize("Step1", Model.Step1)
    @Html.EditorFor(x => x.Step2)
    ...
}

và bên trong hành động POST của bước 2:

[HttpPost]
public ActionResult Step2(Step2ViewModel step2, [Deserialize] Step1ViewModel step1)
{
    var model = new WizardViewModel 
    {
        Step1 = step1,
        Step2 = step2
    }

    if (!ModelState.IsValid)
    {
        return View(model);
    }
    return View("Step3", model);
}

Và cứ như vậy cho đến khi bạn đến bước cuối cùng, nơi bạn sẽ có WizardViewModel đầy đủ tất cả dữ liệu. Sau đó, bạn sẽ ánh xạ mô hình xem vào mô hình miền của bạn và chuyển nó đến lớp dịch vụ để xử lý. Lớp dịch vụ có thể tự thực hiện bất kỳ quy tắc xác thực nào và v.v.

Ngoài ra còn có một cách khác: sử dụng javascript và đặt tất cả trên cùng một trang. Có rất nhiều plugin jquery cung cấp chức năng thuật sĩ ( Stepy là một plugin đẹp). Về cơ bản, đây là vấn đề hiển thị và ẩn div trên máy khách trong trường hợp bạn không còn phải lo lắng về trạng thái tồn tại giữa các bước.

Nhưng bất kể giải pháp nào bạn chọn luôn luôn sử dụng các mô hình xem và thực hiện xác nhận trên các mô hình xem đó. Chừng nào bạn còn gắn các thuộc tính xác thực chú thích dữ liệu trên các mô hình miền của mình, bạn sẽ phải vật lộn rất nhiều vì các mô hình miền không được điều chỉnh theo các khung nhìn.


CẬP NHẬT:

OK, do nhiều ý kiến ​​tôi rút ra kết luận rằng câu trả lời của tôi không rõ ràng. Và tôi phải đồng ý. Vì vậy, hãy để tôi cố gắng xây dựng thêm ví dụ của tôi.

Chúng tôi có thể xác định một giao diện mà tất cả các mô hình xem bước nên thực hiện (đó chỉ là giao diện đánh dấu):

public interface IStepViewModel
{
}

sau đó chúng tôi sẽ xác định 3 bước cho trình hướng dẫn trong đó mỗi bước tất nhiên chỉ chứa các thuộc tính mà nó yêu cầu cũng như các thuộc tính xác thực có liên quan:

[Serializable]
public class Step1ViewModel: IStepViewModel
{
    [Required]
    public string Foo { get; set; }
}

[Serializable]
public class Step2ViewModel : IStepViewModel
{
    public string Bar { get; set; }
}

[Serializable]
public class Step3ViewModel : IStepViewModel
{
    [Required]
    public string Baz { get; set; }
}

tiếp theo, chúng tôi xác định mô hình khung nhìn trình hướng dẫn chính bao gồm danh sách các bước và chỉ mục bước hiện tại:

[Serializable]
public class WizardViewModel
{
    public int CurrentStepIndex { get; set; }
    public IList<IStepViewModel> Steps { get; set; }

    public void Initialize()
    {
        Steps = typeof(IStepViewModel)
            .Assembly
            .GetTypes()
            .Where(t => !t.IsAbstract && typeof(IStepViewModel).IsAssignableFrom(t))
            .Select(t => (IStepViewModel)Activator.CreateInstance(t))
            .ToList();
    }
}

Sau đó, chúng tôi chuyển sang bộ điều khiển:

public class WizardController : Controller
{
    public ActionResult Index()
    {
        var wizard = new WizardViewModel();
        wizard.Initialize();
        return View(wizard);
    }

    [HttpPost]
    public ActionResult Index(
        [Deserialize] WizardViewModel wizard, 
        IStepViewModel step
    )
    {
        wizard.Steps[wizard.CurrentStepIndex] = step;
        if (ModelState.IsValid)
        {
            if (!string.IsNullOrEmpty(Request["next"]))
            {
                wizard.CurrentStepIndex++;
            }
            else if (!string.IsNullOrEmpty(Request["prev"]))
            {
                wizard.CurrentStepIndex--;
            }
            else
            {
                // TODO: we have finished: all the step partial
                // view models have passed validation => map them
                // back to the domain model and do some processing with
                // the results

                return Content("thanks for filling this form", "text/plain");
            }
        }
        else if (!string.IsNullOrEmpty(Request["prev"]))
        {
            // Even if validation failed we allow the user to
            // navigate to previous steps
            wizard.CurrentStepIndex--;
        }
        return View(wizard);
    }
}

Vài nhận xét về bộ điều khiển này:

  • Hành động Index POST sử dụng các [Deserialize]thuộc tính từ thư viện Microsoft Futures, vì vậy hãy đảm bảo bạn đã cài đặt MvcContribNuGet. Đó là lý do tại sao các mô hình xem nên được trang trí với [Serializable]thuộc tính
  • Hành động Index POST lấy làm đối số một IStepViewModelgiao diện để điều này có ý nghĩa, chúng ta cần một chất kết dính mô hình tùy chỉnh.

Đây là chất kết dính mô hình liên quan:

public class StepViewModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var stepTypeValue = bindingContext.ValueProvider.GetValue("StepType");
        var stepType = Type.GetType((string)stepTypeValue.ConvertTo(typeof(string)), true);
        var step = Activator.CreateInstance(stepType);
        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => step, stepType);
        return step;
    }
}

Chất kết dính này sử dụng một trường ẩn đặc biệt có tên StepType sẽ chứa loại cụ thể của từng bước và chúng tôi sẽ gửi theo từng yêu cầu.

Chất kết dính mô hình này sẽ được đăng ký tại Application_Start:

ModelBinders.Binders.Add(typeof(IStepViewModel), new StepViewModelBinder());

Phần còn thiếu cuối cùng của câu đố là các khung nhìn. Đây là ~/Views/Wizard/Index.cshtmlquan điểm chính :

@using Microsoft.Web.Mvc
@model WizardViewModel

@{
    var currentStep = Model.Steps[Model.CurrentStepIndex];
}

<h3>Step @(Model.CurrentStepIndex + 1) out of @Model.Steps.Count</h3>

@using (Html.BeginForm())
{
    @Html.Serialize("wizard", Model)

    @Html.Hidden("StepType", Model.Steps[Model.CurrentStepIndex].GetType())
    @Html.EditorFor(x => currentStep, null, "")

    if (Model.CurrentStepIndex > 0)
    {
        <input type="submit" value="Previous" name="prev" />
    }

    if (Model.CurrentStepIndex < Model.Steps.Count - 1)
    {
        <input type="submit" value="Next" name="next" />
    }
    else
    {
        <input type="submit" value="Finish" name="finish" />
    }
}

Và đó là tất cả những gì bạn cần để làm cho nó hoạt động. Tất nhiên, nếu bạn muốn, bạn có thể cá nhân hóa giao diện của một số hoặc tất cả các bước của trình hướng dẫn bằng cách xác định mẫu trình chỉnh sửa tùy chỉnh. Ví dụ: hãy làm điều đó cho bước 2. Vì vậy, chúng tôi xác định một ~/Views/Wizard/EditorTemplates/Step2ViewModel.cshtmlphần:

@model Step2ViewModel

Special Step 2
@Html.TextBoxFor(x => x.Bar)

Đây là cấu trúc trông như thế nào:

nhập mô tả hình ảnh ở đây

Tất nhiên là có chỗ để cải thiện. Hành động Index POST trông giống như s..t. Có quá nhiều mã trong đó. Một sự đơn giản hóa hơn nữa sẽ liên quan đến việc chuyển tất cả các công cụ cơ sở hạ tầng như chỉ mục, quản lý chỉ mục hiện tại, sao chép bước hiện tại vào trình hướng dẫn, ... vào một chất kết dính mô hình khác. Vì vậy, cuối cùng chúng tôi kết thúc với:

[HttpPost]
public ActionResult Index(WizardViewModel wizard)
{
    if (ModelState.IsValid)
    {
        // TODO: we have finished: all the step partial
        // view models have passed validation => map them
        // back to the domain model and do some processing with
        // the results
        return Content("thanks for filling this form", "text/plain");
    }
    return View(wizard);
}

đó là cách các hành động POST sẽ trông như thế nào. Tôi sẽ để lại cải tiến này cho lần tiếp theo :-)


1
@Doug Chamberlain, tôi sử dụng AutoMapper để chuyển đổi giữa các mô hình xem và mô hình miền của tôi.
Darin Dimitrov

1
@Doug Chamberlain, vui lòng xem câu trả lời cập nhật của tôi. Tôi hy vọng nó làm cho mọi thứ rõ ràng hơn một chút so với bài viết ban đầu của tôi.
Darin Dimitrov

20
+1 @Jani: bạn thực sự cần cho Darin 50 điểm cho câu trả lời này. Nó rất toàn diện. Và anh quản lý để nhắc lại nhu cầu sử dụng ViewModel chứ không phải các mô hình Miền ;-)
Tom Chantler

3
Tôi không thể tìm thấy thuộc tính Deserialize ở bất cứ đâu ... Ngoài ra trong trang codeplex của mvccontrib tôi tìm thấy 94fa6078a115 này của Jeremy Skinner ngày 1 tháng 8 năm 2010 lúc 5:55 PM 0 Loại bỏ chất kết dính Deserialize không dùng nữa Bạn muốn tôi làm gì?
Chuck Norris

2
Tôi đã tìm thấy một vấn đề trong khi tôi không nêu tên quan điểm của mình Bước 1, Bước 2, v.v ... Của tôi được đặt tên một cái gì đó có ý nghĩa hơn, nhưng không phải theo thứ tự chữ cái. Vì vậy, cuối cùng tôi đã nhận được các mô hình của tôi theo thứ tự sai. Tôi đã thêm một thuộc tính StepNumber vào giao diện IStepViewModel. Bây giờ tôi có thể sắp xếp theo thứ này trong phương thức Khởi tạo của WizardViewModel.
Jeff Reddy

13

Để bổ sung cho câu trả lời của Amit Bagga, bạn sẽ tìm thấy bên dưới những gì tôi đã làm. Ngay cả khi kém thanh lịch, tôi thấy cách này đơn giản hơn câu trả lời của Darin.

Điều khiển:

public ActionResult Step1()
{
    if (Session["wizard"] != null)
    {
        WizardProductViewModel wiz = (WizardProductViewModel)Session["wizard"];
        return View(wiz.Step1);
    }
    return View();
}

[HttpPost]
public ActionResult Step1(Step1ViewModel step1)
{
    if (ModelState.IsValid)
    {
        WizardProductViewModel wiz = new WizardProductViewModel();
        wiz.Step1 = step1;
        //Store the wizard in session
        Session["wizard"] = wiz;
        return RedirectToAction("Step2");
    }
    return View(step1);
}

public ActionResult Step2()
{
    if (Session["wizard"] != null)
    {
        WizardProductViewModel wiz = (WizardProductViewModel)Session["wizard"];
        return View(wiz.Step2);
    }
    return View();
}

[HttpPost]
public ActionResult Step2(Step2ViewModel step2)
{
    if (ModelState.IsValid)
    {
        //Pull the wizard from session
        WizardProductViewModel wiz = (WizardProductViewModel)Session["wizard"];
        wiz.Step2 = step2;
        //Store the wizard in session
        Session["wizard"] = wiz;
        //return View("Step3");
        return RedirectToAction("Step3");
    }
    return View(step2);
}

public ActionResult Step3()
{
    WizardProductViewModel wiz = (WizardProductViewModel)Session["wizard"];
    return View(wiz.Step3);
}

[HttpPost]
public ActionResult Step3(Step3ViewModel step3)
{
    if (ModelState.IsValid)
    {
        //Pull the wizard from session
        WizardProductViewModel wiz = (WizardProductViewModel)Session["wizard"];
        wiz.Step3 = step3;
        //Save the data
        Product product = new Product
        {
            //Binding with view models
            Name = wiz.Step1.Name,
            ListPrice = wiz.Step2.ListPrice,
            DiscontinuedDate = wiz.Step3.DiscontinuedDate
        };

        db.Products.Add(product);
        db.SaveChanges();
        return RedirectToAction("Index", "Product");
    }
    return View(step3);
}

Mô hình:

 [Serializable]
    public class Step1ViewModel 
    {
        [Required]
        [MaxLength(20, ErrorMessage="Longueur max de 20 caractères")]
        public string Name { get; set; }

    }

    [Serializable]
    public class Step2ViewModel
    {
        public Decimal ListPrice { get; set; }

    }

    [Serializable]
    public class Step3ViewModel
    {
        public DateTime? DiscontinuedDate { get; set; }
    }

    [Serializable]
    public class WizardProductViewModel
    {
        public Step1ViewModel Step1  { get; set; }
        public Step2ViewModel Step2  { get; set; }
        public Step3ViewModel Step3  { get; set; }
    }

11

Tôi sẽ đề nghị bạn duy trì trạng thái Hoàn thành quy trình trên máy khách bằng cách sử dụng Jquery.

Ví dụ, chúng tôi có một quy trình Wizard ba bước.

  1. Người dùng được trình bày với Bước 1 trên đó có nút được gắn nhãn "Tiếp theo"
  2. Khi nhấp vào Tiếp theo, chúng tôi thực hiện Yêu cầu Ajax và tạo DIV có tên Step2 và tải HTML vào DIV đó.
  3. Trên Bước 3, chúng ta có một Nút có nhãn "Hoàn tất" khi Nhấp vào nút đăng dữ liệu bằng cách sử dụng cuộc gọi $ .post.

Bằng cách này, bạn có thể dễ dàng xây dựng đối tượng miền của mình trực tiếp từ dữ liệu bài đăng mẫu và trong trường hợp dữ liệu có lỗi trả về JSON hợp lệ giữ tất cả thông báo lỗi và hiển thị chúng trong div.

Vui lòng chia các bước

public class Wizard 
{
  public Step1 Step1 {get;set;}
  public Step2 Step2 {get;set;}
  public Step3 Step3 {get;set;}
}

public ActionResult Step1(Step1 step)
{
  if(Model.IsValid)
 {
   Wizard wiz = new Wizard();
   wiz.Step1 = step;
  //Store the Wizard in Session;
  //Return the action
 }
}

public ActionResult Step2(Step2 step)
{
 if(Model.IsValid)
 {
   //Pull the Wizard From Session
   wiz.Step2=step;
 }
}

Trên đây chỉ là một minh chứng sẽ giúp bạn đạt được kết quả cuối cùng. Ở Bước cuối cùng, bạn phải tạo Đối tượng Miền và điền các giá trị chính xác từ Đối tượng Thuật sĩ và Lưu trữ vào cơ sở dữ liệu.


Vâng, đó là một giải pháp thú vị, nhưng chúng tôi có kết nối internet kém ở phía khách hàng, và anh ấy / cô ấy nên gửi cho chúng tôi một loạt các tập tin. vì vậy chúng tôi đã từ chối giải pháp đó sớm hơn
Jahan

Bạn có thể vui lòng cho tôi biết khối lượng dữ liệu mà khách hàng sẽ tải lên không.
Amit Bagga

Một số tệp, gần mười, mỗi tệp gần 1 MB.
Jahan

5

Thuật sĩ chỉ là các bước đơn giản để xử lý một mô hình đơn giản. Không có lý do để tạo nhiều mô hình cho một trình hướng dẫn. Tất cả những gì bạn sẽ làm là tạo một mô hình duy nhất và chuyển nó giữa các hành động trong một bộ điều khiển duy nhất.

public class MyModel
{
     [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
     public Guid Id { get; set };
     public string StepOneData { get; set; }
     public string StepTwoData { get; set; }
}

Các coed ở trên là đơn giản ngu ngốc để thay thế các lĩnh vực của bạn trong đó. Tiếp theo, chúng tôi bắt đầu với một hành động đơn giản khởi xướng thuật sĩ của chúng tôi.

    public ActionResult WizardStep1()
    {
        return View(new MyModel());
    }

Thao tác này gọi chế độ xem "WizardStep1.cshtml (nếu sử dụng dao cạo). Bạn có thể sử dụng trình hướng dẫn tạo mẫu nếu bạn muốn. Chúng tôi sẽ chuyển hướng bài đăng sang một hành động khác.

<WizardStep1.cshtml>
@using (Html.BeginForm("WizardStep2", "MyWizard")) {

Điều cần lưu ý là chúng tôi sẽ đăng điều này lên một hành động khác; hành động WizardStep2

    [HttpPost]
    public ActionResult WizardStep2(MyModel myModel)
    {
        return ModelState.IsValid ? View(myModel) : View("WizardStep1", myModel);
    }

Trong hành động này, chúng tôi kiểm tra xem mô hình của chúng tôi có hợp lệ không và nếu có, chúng tôi sẽ gửi nó đến chế độ xem WizardStep2.cshtml của chúng tôi, chúng tôi sẽ gửi lại cho bước một với các lỗi xác thực. Trong mỗi bước chúng tôi gửi nó đến bước tiếp theo, xác thực bước đó và tiếp tục. Bây giờ một số nhà phát triển hiểu biết có thể nói tốt rằng chúng tôi không thể di chuyển giữa các bước như thế này nếu chúng tôi sử dụng các thuộc tính [Bắt buộc] hoặc chú thích dữ liệu khác giữa các bước. Và bạn sẽ đúng, vì vậy hãy loại bỏ các lỗi trên các mục chưa được kiểm tra. như dưới đây.

    [HttpPost]
    public ActionResult WizardStep3(MyModel myModel)
    {
        foreach (var error in ModelState["StepTwoData"].Errors)
        {
            ModelState["StepTwoData"].Errors.Remove(error);
        }

Cuối cùng, chúng tôi sẽ lưu mô hình một lần vào kho lưu trữ dữ liệu. Điều này cũng ngăn người dùng khởi động trình hướng dẫn nhưng không hoàn thành việc không lưu dữ liệu không đầy đủ vào cơ sở dữ liệu.

Tôi hy vọng bạn thấy phương pháp triển khai thuật sĩ này dễ sử dụng và bảo trì hơn bất kỳ phương pháp nào được đề cập trước đây.

Cảm ơn vì đã đọc.


Bạn có điều này trong một giải pháp hoàn chỉnh tôi có thể thử không? Cảm ơn
mpora

5

Tôi muốn chia sẻ cách xử lý các yêu cầu này của riêng tôi. Tôi hoàn toàn không muốn sử dụng SessionState, tôi cũng không muốn nó xử lý phía máy khách và phương thức tuần tự hóa yêu cầu MVC Futures mà tôi không muốn đưa vào dự án của mình.

Thay vào đó, tôi đã xây dựng Trình trợ giúp HTML sẽ lặp qua tất cả các thuộc tính của mô hình và tạo một phần tử ẩn tùy chỉnh cho mỗi phần tử. Nếu nó là một tài sản phức tạp thì nó sẽ chạy đệ quy trên nó.

Trong biểu mẫu của bạn, chúng sẽ được đăng lên bộ điều khiển cùng với dữ liệu mô hình mới ở mỗi bước "trình hướng dẫn".

Tôi đã viết cái này cho MVC 5.

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Web;
using System.Web.Routing;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Reflection;

namespace YourNamespace
{
    public static class CHTML
    {
        public static MvcHtmlString HiddenClassFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression)
        {
            return HiddenClassFor(html, expression, null);
        }

        public static MvcHtmlString HiddenClassFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
        {
            ModelMetadata _metaData = ModelMetadata.FromLambdaExpression(expression, html.ViewData);

            if (_metaData.Model == null)
                return MvcHtmlString.Empty;

            RouteValueDictionary _dict = htmlAttributes != null ? new RouteValueDictionary(htmlAttributes) : null;

            return MvcHtmlString.Create(HiddenClassFor(html, expression, _metaData, _dict).ToString());
        }

        private static StringBuilder HiddenClassFor<TModel>(HtmlHelper<TModel> html, LambdaExpression expression, ModelMetadata metaData, IDictionary<string, object> htmlAttributes)
        {
            StringBuilder _sb = new StringBuilder();

            foreach (ModelMetadata _prop in metaData.Properties)
            {
                Type _type = typeof(Func<,>).MakeGenericType(typeof(TModel), _prop.ModelType);
                var _body = Expression.Property(expression.Body, _prop.PropertyName);
                LambdaExpression _propExp = Expression.Lambda(_type, _body, expression.Parameters);

                if (!_prop.IsComplexType)
                {
                    string _id = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(ExpressionHelper.GetExpressionText(_propExp));
                    string _name = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(_propExp));
                    object _value = _prop.Model;

                    _sb.Append(MinHiddenFor(_id, _name, _value, htmlAttributes));
                }
                else
                {
                    if (_prop.ModelType.IsArray)
                        _sb.Append(HiddenArrayFor(html, _propExp, _prop, htmlAttributes));
                    else if (_prop.ModelType.IsClass)
                        _sb.Append(HiddenClassFor(html, _propExp, _prop, htmlAttributes));
                    else
                        throw new Exception(string.Format("Cannot handle complex property, {0}, of type, {1}.", _prop.PropertyName, _prop.ModelType));
                }
            }

            return _sb;
        }

        public static MvcHtmlString HiddenArrayFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression)
        {
            return HiddenArrayFor(html, expression, null);
        }

        public static MvcHtmlString HiddenArrayFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
        {
            ModelMetadata _metaData = ModelMetadata.FromLambdaExpression(expression, html.ViewData);

            if (_metaData.Model == null)
                return MvcHtmlString.Empty;

            RouteValueDictionary _dict = htmlAttributes != null ? new RouteValueDictionary(htmlAttributes) : null;

            return MvcHtmlString.Create(HiddenArrayFor(html, expression, _metaData, _dict).ToString());
        }

        private static StringBuilder HiddenArrayFor<TModel>(HtmlHelper<TModel> html, LambdaExpression expression, ModelMetadata metaData, IDictionary<string, object> htmlAttributes)
        {
            Type _eleType = metaData.ModelType.GetElementType();
            Type _type = typeof(Func<,>).MakeGenericType(typeof(TModel), _eleType);

            object[] _array = (object[])metaData.Model;

            StringBuilder _sb = new StringBuilder();

            for (int i = 0; i < _array.Length; i++)
            {
                var _body = Expression.ArrayIndex(expression.Body, Expression.Constant(i));
                LambdaExpression _arrayExp = Expression.Lambda(_type, _body, expression.Parameters);
                ModelMetadata _valueMeta = ModelMetadata.FromLambdaExpression((dynamic)_arrayExp, html.ViewData);

                if (_eleType.IsClass)
                {
                    _sb.Append(HiddenClassFor(html, _arrayExp, _valueMeta, htmlAttributes));
                }
                else
                {
                    string _id = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(ExpressionHelper.GetExpressionText(_arrayExp));
                    string _name = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(_arrayExp));
                    object _value = _valueMeta.Model;

                    _sb.Append(MinHiddenFor(_id, _name, _value, htmlAttributes));
                }
            }

            return _sb;
        }

        public static MvcHtmlString MinHiddenFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression)
        {
            return MinHiddenFor(html, expression, null);
        }

        public static MvcHtmlString MinHiddenFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
        {
            string _id = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(ExpressionHelper.GetExpressionText(expression));
            string _name = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(expression));
            object _value = ModelMetadata.FromLambdaExpression(expression, html.ViewData).Model;
            RouteValueDictionary _dict = htmlAttributes != null ? new RouteValueDictionary(htmlAttributes) : null;

            return MinHiddenFor(_id, _name, _value, _dict);
        }

        public static MvcHtmlString MinHiddenFor(string id, string name, object value, IDictionary<string, object> htmlAttributes)
        {
            TagBuilder _input = new TagBuilder("input");
            _input.Attributes.Add("id", id);
            _input.Attributes.Add("name", name);
            _input.Attributes.Add("type", "hidden");

            if (value != null)
            {
                _input.Attributes.Add("value", value.ToString());
            }

            if (htmlAttributes != null)
            {
                foreach (KeyValuePair<string, object> _pair in htmlAttributes)
                {
                    _input.MergeAttribute(_pair.Key, _pair.Value.ToString(), true);
                }
            }

            return new MvcHtmlString(_input.ToString(TagRenderMode.SelfClosing));
        }
    }
}

Bây giờ đối với tất cả các bước của "trình hướng dẫn" của bạn, bạn có thể sử dụng cùng một mô hình cơ sở và chuyển các thuộc tính mô hình "Bước 1,2,3" vào trình trợ giúp @ Html.HiddenClassFor bằng biểu thức lambda.

Bạn thậm chí có thể có một nút quay lại ở mỗi bước nếu bạn muốn. Chỉ cần có một nút quay lại trong biểu mẫu của bạn sẽ đăng nó lên một hành động StepNBack trên bộ điều khiển bằng cách sử dụng thuộc tính định dạng. Không bao gồm trong ví dụ dưới đây mà chỉ là một ý tưởng cho bạn.

Dù sao ở đây là một ví dụ cơ bản:

Đây là MÔ HÌNH của bạn

public class WizardModel
{
    // you can store additional properties for your "wizard" / parent model here
    // these properties can be saved between pages by storing them in the form using @Html.MinHiddenFor(m => m.WizardID)
    public int? WizardID { get; set; }

    public string WizardType { get; set; }

    [Required]
    public Step1 Step1 { get; set; }

    [Required]
    public Step2 Step2 { get; set; }

    [Required]
    public Step3 Step3 { get; set; }

    // if you want to use the same model / view / controller for EDITING existing data as well as submitting NEW data here is an example of how to handle it
    public bool IsNew
    {
        get
        {
            return WizardID.HasValue;
        }
    }
}

public class Step1
{
    [Required]
    [MaxLength(32)]
    [Display(Name = "First Name")]
    public string FirstName { get; set; }

    [Required]
    [MaxLength(32)]
    [Display(Name = "Last Name")]
    public string LastName { get; set; }
}

public class Step2
{
    [Required]
    [MaxLength(512)]
    [Display(Name = "Biography")]
    public string Biography { get; set; }
}

public class Step3
{        
    // lets have an array of strings here to shake things up
    [Required]
    [Display(Name = "Your Favorite Foods")]
    public string[] FavoriteFoods { get; set; }
}

Đây là ĐIỀU KHIỂN của bạn

public class WizardController : Controller
{
    [HttpGet]
    [Route("wizard/new")]
    public ActionResult New()
    {
        WizardModel _model = new WizardModel()
        {
            WizardID = null,
            WizardType = "UserInfo"
        };

        return View("Step1", _model);
    }

    [HttpGet]
    [Route("wizard/edit/{wizardID:int}")]
    public ActionResult Edit(int wizardID)
    {
        WizardModel _model = database.GetData(wizardID);

        return View("Step1", _model);
    }

    [HttpPost]
    [Route("wizard/step1")]
    public ActionResult Step1(WizardModel model)
    {
        // just check if the values in the step1 model are valid
        // shouldn't use ModelState.IsValid here because that would check step2 & step3.
        // which isn't entered yet
        if (ModelState.IsValidField("Step1"))
        {
            return View("Step2", model);
        }

        return View("Step1", model);
    }

    [HttpPost]
    [Route("wizard/step2")]
    public ActionResult Step2(WizardModel model)
    {
        if (ModelState.IsValidField("Step2"))
        {
            return View("Step3", model);
        }

        return View("Step2", model);
    }

    [HttpPost]
    [Route("wizard/step3")]
    public ActionResult Step3(WizardModel model)
    {
        // all of the data for the wizard model is complete.
        // so now we check the entire model state
        if (ModelState.IsValid)
        {
            // validation succeeded. save the data from the model.
            // the model.IsNew is just if you want users to be able to
            // edit their existing data.
            if (model.IsNew)
                database.NewData(model);
            else
                database.EditData(model);

            return RedirectToAction("Success");
        }

        return View("Step3", model);
    }
}

Dưới đây là những lượt xem của bạn

Bước 1

@model WizardModel

@{
    ViewBag.Title = "Step 1";
}

@using (Html.BeginForm("Step1", "Wizard", FormMethod.Post))
{
    @Html.MinHiddenFor(m => m.WizardID)
    @Html.MinHiddenFor(m => m.WizardType)

    @Html.LabelFor(m => m.Step1.FirstName)
    @Html.TextBoxFor(m => m.Step1.FirstName)

    @Html.LabelFor(m => m.Step1.LastName)
    @Html.TextBoxFor(m => m.Step1.LastName)

    <button type="submit">Submit</button>
}

Bước 2

@model WizardModel

@{
    ViewBag.Title = "Step 2";
}

@using (Html.BeginForm("Step2", "Wizard", FormMethod.Post))
{
    @Html.MinHiddenFor(m => m.WizardID)
    @Html.MinHiddenFor(m => m.WizardType)
    @Html.HiddenClassFor(m => m.Step1)

    @Html.LabelFor(m => m.Step2.Biography)
    @Html.TextAreaFor(m => m.Step2.Biography)

    <button type="submit">Submit</button>
}

Bước 3

@model WizardModel

@{
    ViewBag.Title = "Step 3";
}

@using (Html.BeginForm("Step3", "Wizard", FormMethod.Post))
{
    @Html.MinHiddenFor(m => m.WizardID)
    @Html.MinHiddenFor(m => m.WizardType)
    @Html.HiddenClassFor(m => m.Step1)
    @Html.HiddenClassFor(m => m.Step2)

    @Html.LabelFor(m => m.Step3.FavoriteFoods)
    @Html.ListBoxFor(m => m.Step3.FavoriteFoods,
        new SelectListItem[]
        {
            new SelectListItem() { Value = "Pizza", Text = "Pizza" },
            new SelectListItem() { Value = "Sandwiches", Text = "Sandwiches" },
            new SelectListItem() { Value = "Burgers", Text = "Burgers" },
        });

    <button type="submit">Submit</button>
}

1
Bạn có thể làm rõ hơn giải pháp của bạn bằng cách cung cấp mô hình xem và bộ điều khiển?
Tyler Durden

2

Thêm thông tin từ câu trả lời của @ Darin.

Điều gì sẽ xảy ra nếu bạn có phong cách thiết kế riêng cho từng bước và muốn duy trì mỗi chế độ xem một phần riêng biệt hoặc nếu bạn có nhiều thuộc tính cho mỗi bước thì sao?

Trong khi sử dụng Html.EditorFor chúng tôi có giới hạn để sử dụng một phần xem.

Tạo 3 Chế độ xem một phần trong Sharedthư mục có tên:Step1ViewModel.cshtml , Step3ViewModel.cshtml , Step3ViewModel.cshtml

Để cho ngắn gọn, tôi chỉ đăng quan điểm cấp 1, các bước khác giống như câu trả lời của Darin.

Step1ViewModel.cs

[Serializable]
public class Step1ViewModel : IStepViewModel
{
  [Required]
  public string FirstName { get; set; }

  public string LastName { get; set; }

  public string PhoneNo { get; set; }

  public string EmailId { get; set; }

  public int Age { get; set; }

 }

Step1ViewModel.cshtml

 @model WizardPages.ViewModels.Step1ViewModel

<div class="container">
    <h2>Personal Details</h2>

    <div class="form-group">
        <label class="control-label col-sm-2" for="email">First Name:</label>
        <div class="col-sm-10">
            @Html.TextBoxFor(x => x.FirstName)
        </div>
    </div>
    <div class="form-group">
        <label class="control-label col-sm-2" for="pwd">Last Name:</label>
        <div class="col-sm-10">
            @Html.TextBoxFor(x => x.LastName)
        </div>
    </div>
    <div class="form-group">
        <label class="control-label col-sm-2" for="pwd">Phone No:</label>
        <div class="col-sm-10"> 
            @Html.TextBoxFor(x => x.PhoneNo)
        </div>
    </div>
    <div class="form-group">
        <label class="control-label col-sm-2" for="pwd">Email Id:</label>
        <div class="col-sm-10">
            @Html.TextBoxFor(x => x.EmailId)
        </div>
    </div>


</div>

Index.cshtml

@using Microsoft.Web.Mvc
@model WizardPages.ViewModels.WizardViewModel

@{
    var currentStep = Model.Steps[Model.CurrentStepIndex];

    string viewName = currentStep.ToString().Substring(currentStep.ToString().LastIndexOf('.') + 1);
}

<h3>Step @(Model.CurrentStepIndex + 1) out of @Model.Steps.Count</h3>

@using (Html.BeginForm())
{
    @Html.Serialize("wizard", Model)

    @Html.Hidden("StepType", Model.Steps[Model.CurrentStepIndex].GetType())

    @Html.Partial(""+ viewName + "", currentStep);

    if (Model.CurrentStepIndex > 0)
    {

     <input type="submit" value="Previous" name="prev" class="btn btn-warning" />

    }

    if (Model.CurrentStepIndex < Model.Steps.Count - 1)
    {

      <input type="submit" value="Next" name="next" class="btn btn-info" />

    }
    else
    {

      <input type="submit" value="Finish" name="finish" class="btn btn-success" />

    }
}

Nếu có một số giải pháp tốt hơn, xin vui lòng bình luận để cho người khác biết.


-9

Một tùy chọn là tạo tập hợp các bảng giống hệt nhau sẽ lưu trữ dữ liệu được thu thập trong mỗi bước. Sau đó, trong bước cuối cùng nếu mọi việc suôn sẻ, bạn có thể tạo thực thể thực bằng cách sao chép dữ liệu tạm thời và lưu trữ nó.

Khác là tạo Value Objectscho mỗi bước và lưu trữ sau đó trong Cachehoặc Session. Sau đó, nếu mọi việc suôn sẻ, bạn có thể tạo đối tượng Miền của mình từ chúng và lưu nó


1
Sẽ tốt đẹp nếu những người xuống bỏ phiếu cũng đưa ra lý do của họ.
Martin

Không bỏ phiếu cho bạn, nhưng câu trả lời của bạn hoàn toàn không liên quan đến câu hỏi. OP đang hỏi về cách tạo trình hướng dẫn, trong khi bạn trả lời về cách xử lý phản hồi ở phía sau.
Mất trí nhớ

1
Tôi thường không bỏ phiếu, nhưng khi tôi làm, tôi chắc chắn rằng upvote của nó :-)
Suhail Mumtaz Awan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.