asp.net mvc의 다중 단계 등록 프로세스 문제 (분할 뷰 모델, 단일 모델)
속성에 정의 된 유효성 검사 규칙이 있는 도메인 계층 의 단일 개체를 기반으로 하는 다단계 등록 프로세스 가 있습니다.
도메인이 여러보기로 분할 될 때 도메인 개체를 어떻게 확인해야하며 게시 할 때 첫 번째보기에서 개체를 부분적으로 저장해야합니까?
세션 사용에 대해 생각했지만 프로세스가 길고 데이터 양이 많을 가능성이 없기 때문에 세션을 사용하고 싶지 않습니다.
모든 데이터를 관계형 인 메모리 DB (main db와 동일한 스키마 사용)에 저장 한 다음 해당 데이터를 main db로 플러시하는 것에 대해 생각했지만 문제가 발생하여 서비스와 함께 작업하는 서비스 (보기에서 요청 됨) 기본 DB 및 메모리 내 DB.
우아하고 깨끗한 솔루션을 찾고 있습니다 (보다 정확하게 모범 사례).
업데이트 및 설명 :
@Darin 귀하의 사려 깊은 답변에 감사드립니다. 지금까지 제가 한 일이었습니다. 그러나 우연히 나는 많은 첨부 파일이있는 요청을 받았습니다. Step2View
예를 들어 사용자가 문서를 비동기 적으로 업로드 할 수 있는 예를 디자인합니다 . Step1View
.
따라서 Step1
(부분적으로) 에 도메인 객체를 저장해야 하지만 Step1의 ViewModel에 부분적으로 매핑 된 백업 된 Core Domain 객체를 변환 된 소품없이 저장할 수 없습니다 Step2ViewModel
.
먼저 뷰에서 도메인 개체를 사용해서는 안됩니다. 뷰 모델을 사용해야합니다. 각 뷰 모델에는 해당 뷰에 필요한 속성과이 뷰에 고유 한 유효성 검사 특성 만 포함됩니다. 따라서 3 단계 마법사가있는 경우 각 단계마다 하나씩 3 개의 뷰 모델이 있습니다.
public class Step1ViewModel
{
[Required]
public string SomeProperty { get; set; }
...
}
public class Step2ViewModel
{
[Required]
public string SomeOtherProperty { get; set; }
...
}
등등. 이러한 모든 뷰 모델은 기본 마법사 뷰 모델로 뒷받침 될 수 있습니다.
public class WizardViewModel
{
public Step1ViewModel Step1 { get; set; }
public Step2ViewModel Step2 { get; set; }
...
}
그러면 마법사 프로세스의 각 단계를 렌더링하고 메인 WizardViewModel
을 뷰에 전달하는 컨트롤러 작업을 수행 할 수 있습니다. 컨트롤러 작업 내부의 첫 번째 단계에있을 때 Step1
속성을 초기화 할 수 있습니다. 그런 다음보기 내에서 사용자가 1 단계에 대한 특성을 채울 수있는 양식을 생성합니다. 양식이 제출되면 컨트롤러 조치는 1 단계에 대해서만 유효성 검증 규칙을 적용합니다.
[HttpPost]
public ActionResult Step1(Step1ViewModel step1)
{
var model = new WizardViewModel
{
Step1 = step1
};
if (!ModelState.IsValid)
{
return View(model);
}
return View("Step2", model);
}
이제 2 단계보기 에서 MVC 선물 의 Html.Serialize 도우미 를 사용하여 1 단계를 양식 내부의 숨겨진 필드 (원하는 경우 ViewState 정렬)로 직렬화 할 수 있습니다.
@using (Html.BeginForm("Step2", "Wizard"))
{
@Html.Serialize("Step1", Model.Step1)
@Html.EditorFor(x => x.Step2)
...
}
그리고 step2의 POST 동작 내부 :
[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);
}
그리고 WizardViewModel
모든 데이터로 채워질 마지막 단계에 도달 할 때까지 계속하십시오 . 그런 다음 뷰 모델을 도메인 모델에 매핑하고 처리를 위해 서비스 계층으로 전달합니다. 서비스 계층은 유효성 검사 규칙 자체를 수행 할 수 있습니다.
또 다른 대안이 있습니다 : 자바 스크립트를 사용하고 모두 같은 페이지에 배치하십시오. 마법사 기능을 제공하는 많은 jquery 플러그인 이 있습니다 ( Stepy 는 좋은 것입니다). 기본적으로 클라이언트에서 div를 표시하거나 숨기는 문제입니다.이 경우 더 이상 단계 간 상태 유지에 대해 걱정할 필요가 없습니다.
그러나 어떤 솔루션을 선택하든 항상 뷰 모델을 사용하고 해당 뷰 모델에 대한 유효성 검사를 수행하십시오. 도메인 모델에서 데이터 주석 유효성 검사 속성을 고수하는 한 도메인 모델이보기에 적합하지 않기 때문에 매우 힘들 것입니다.
최신 정보:
많은 의견으로 인해 내 대답이 명확하지 않다는 결론을 내립니다. 그리고 동의해야합니다. 제 예를 더 자세히 설명하겠습니다.
모든 단계 뷰 모델이 구현해야하는 인터페이스를 정의 할 수 있습니다 (표식 인터페이스 일뿐).
public interface IStepViewModel
{
}
그런 다음 각 단계마다 필요한 속성과 관련 유효성 검사 속성 만 포함하는 마법사에 대해 3 단계를 정의합니다.
[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; }
}
다음으로 단계 목록과 현재 단계 색인으로 구성된 기본 마법사보기 모델을 정의합니다.
[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();
}
}
그런 다음 컨트롤러로 이동합니다.
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);
}
}
이 컨트롤러에 대한 몇 가지주의 사항 :
- 인덱스 POST 조치는
[Deserialize]
Microsoft Futures 라이브러리 의 속성을 사용 하므로MvcContrib
NuGet 을 설치했는지 확인하십시오 . 이것이 뷰 모델이[Serializable]
속성 으로 장식되어야하는 이유입니다 - 인덱스 POST 액션은
IStepViewModel
인터페이스 를 인수로 사용하므로 이해하기 위해서는 사용자 정의 모델 바인더가 필요합니다.
관련 모델 바인더는 다음과 같습니다.
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;
}
}
이 바인더는 StepType이라는 특수한 숨겨진 필드를 사용합니다. 여기에는 각 단계의 구체적인 유형이 포함되며 각 요청에 따라 전송됩니다.
이 모델 바인더는 다음에 등록됩니다 Application_Start
.
ModelBinders.Binders.Add(typeof(IStepViewModel), new StepViewModelBinder());
퍼즐의 마지막 누락 부분은 전망입니다. 주요 ~/Views/Wizard/Index.cshtml
견해 는 다음과 같습니다 .
@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" />
}
}
그리고 이것이 당신이이 일을하는데 필요한 전부입니다. 물론 원하는 경우 사용자 정의 편집기 템플리트를 정의하여 마법사의 일부 또는 모든 단계의 모양과 느낌을 개인화 할 수 있습니다. 예를 들어 2 단계에서 해보겠습니다. ~/Views/Wizard/EditorTemplates/Step2ViewModel.cshtml
부분 을 정의합니다 .
@model Step2ViewModel
Special Step 2
@Html.TextBoxFor(x => x.Bar)
구조는 다음과 같습니다.
물론 개선의 여지가 있습니다. 인덱스 POST 작업은 s..t처럼 보입니다. 코드가 너무 많습니다. 추가 단순화는 색인, 현재 색인 관리, 현재 단계를 마법사로 복사하는 등의 모든 인프라를 다른 모델 바인더로 이동하는 것과 관련됩니다. 결국 우리는 다음과 같이 끝납니다.
[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);
}
POST 작업의 모양이 더 낫습니다. 나는 다음에이 개선을 떠날 것입니다 :-)
Amit Bagga의 답변을 보충하기 위해 내가 한 일을 아래에서 찾을 수 있습니다. 우아하지는 않지만이 방법은 Darin의 대답보다 간단합니다.
컨트롤러 :
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);
}
모델 :
[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; }
}
Jquery를 사용하여 클라이언트에서 완료 프로세스 상태를 유지하는 것이 좋습니다.
예를 들어 3 단계 마법사 프로세스가 있습니다.
- "다음"이라는 단추가있는 Step1이 표시된 사용자
- 다음을 클릭하면 Ajax 요청을 만들고 Step2라는 DIV를 생성하고 해당 DIV에 HTML을로드합니다.
- 3 단계에는 $ .post 호출을 사용하여 데이터를 게시 할 때 "완료"라는 레이블이 붙은 버튼이 있습니다.
이렇게하면 양식 게시물 데이터에서 직접 도메인 객체를 쉽게 구축 할 수 있으며 데이터에 오류가있는 경우 모든 오류 메시지를 보유하는 유효한 JSON을 반환하고 div에 표시합니다.
단계를 나누십시오
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;
}
}
위의 내용은 최종 결과를 얻는 데 도움이되는 데모 일뿐입니다. 최종 단계에서 도메인 오브젝트를 작성하고 마법사 오브젝트에서 올바른 값을 채워 데이터베이스에 저장해야합니다.
마법사는 간단한 모델을 처리하는 간단한 단계입니다. 마법사에 대해 여러 모델을 작성할 이유가 없습니다. 단일 모델을 만들어 단일 컨트롤러의 작업간에 전달하기 만하면됩니다.
public class MyModel
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set };
public string StepOneData { get; set; }
public string StepTwoData { get; set; }
}
위의 남녀 공학은 어리석은 간단하므로 필드를 대체하십시오. 다음으로 마법사를 시작하는 간단한 작업으로 시작합니다.
public ActionResult WizardStep1()
{
return View(new MyModel());
}
뷰를 "WizardStep1.cshtml"(즉, 면도기를 사용하는 경우)이라고합니다. 원하는 경우 템플릿 생성 마법사를 사용할 수 있습니다. 게시물을 다른 작업으로 리디렉션합니다.
<WizardStep1.cshtml>
@using (Html.BeginForm("WizardStep2", "MyWizard")) {
주목할 것은 우리는 이것을 다른 행동에 게시 할 것입니다. WizardStep2 조치
[HttpPost]
public ActionResult WizardStep2(MyModel myModel)
{
return ModelState.IsValid ? View(myModel) : View("WizardStep1", myModel);
}
이 작업에서 모델이 유효한지 확인하고, 그렇다면 모델을 WizardStep2.cshtml 뷰로 보내면 유효성 검사 오류가있는 1 단계로 다시 보냅니다. 각 단계에서 다음 단계로 보내서 해당 단계를 확인하고 계속 진행하십시오. 이제 일부 정통한 개발자는 단계 사이에 [필수] 속성 또는 다른 데이터 주석을 사용하는 경우 이와 같은 단계 사이를 이동할 수 없다고 말할 수 있습니다. 그리고 당신은 옳을 것이므로 아직 확인하지 않은 항목의 오류를 제거하십시오. 아래처럼.
[HttpPost]
public ActionResult WizardStep3(MyModel myModel)
{
foreach (var error in ModelState["StepTwoData"].Errors)
{
ModelState["StepTwoData"].Errors.Remove(error);
}
마지막으로 모델을 데이터 저장소에 한 번 저장합니다. 또한 마법사를 시작하는 사용자는 방지하지만 불완전한 데이터를 데이터베이스에 저장하지는 않습니다.
마법사를 구현하는이 방법이 이전에 언급 한 방법보다 훨씬 더 사용하기 쉽고 유지 관리되기를 바랍니다.
읽어 주셔서 감사합니다.
이러한 요구 사항을 처리하는 내 자신의 방식을 공유하고 싶었습니다. 나는 SessionState를 전혀 사용하고 싶지 않았고 클라이언트 측을 처리하고 싶지 않았으며 serialize 메소드에는 프로젝트에 포함하고 싶지 않은 MVC Futures가 필요합니다.
대신 모델의 모든 속성을 반복하고 각각에 대해 사용자 정의 숨겨진 요소를 생성하는 HTML 도우미를 만들었습니다. 복잡한 속성 인 경우 재귀 적으로 실행됩니다.
귀하의 양식에 각 "마법사"단계에서 새 모델 데이터와 함께 컨트롤러에 게시됩니다.
나는 이것을 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));
}
}
}
이제 "마법사"의 모든 단계에서 동일한 기본 모델을 사용하고 "1,2,3"모델 속성을 @ Html.HiddenClassFor 도우미에 람다 식을 사용하여 전달할 수 있습니다.
원하는 경우 각 단계마다 뒤로 버튼이있을 수도 있습니다. formaction 속성을 사용하여 컨트롤러의 StepNBack 액션에 게시 할 폼에 뒤로 버튼이 있습니다. 아래 예제에는 포함되어 있지 않지만 아이디어는 아닙니다.
어쨌든 여기에 기본 예가 있습니다.
여기 당신의 모델입니다
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; }
}
여기 컨트롤러가 있습니다
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);
}
}
여기 당신의 견해가 있습니다
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>
}
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>
}
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>
}
@Darin의 답변에서 더 많은 정보 추가.
각 단계에 대해 별도의 디자인 스타일이 있고 각각을 별도의 부분보기로 유지하려는 경우 또는 각 단계에 대해 여러 속성이있는 경우 어떻게해야합니까?
사용하는 동안 Html.EditorFor
부분보기를 사용하는 데 제한이 있습니다.
다음 Shared
폴더에 3 개의 부분 뷰를 만듭니다 .Step1ViewModel.cshtml , Step3ViewModel.cshtml , Step3ViewModel.cshtml
간결하게 1st patial view를 게시하는 다른 단계는 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" />
}
}
더 나은 해결책이 있다면 다른 사람들에게 알려주십시오.
하나의 옵션은 각 단계에서 수집 된 데이터를 저장할 동일한 테이블 세트를 작성하는 것입니다. 그런 다음 마지막 단계에서 모든 것이 잘 진행되면 임시 데이터를 복사하여 실제 엔터티를 만들어 저장할 수 있습니다.
다른 방법은 Value Objects
각 단계마다 생성 한 다음 Cache
또는에 저장하는 것 Session
입니다. 그런 다음 모든 것이 잘되면 도메인 객체를 만들어 저장할 수 있습니다.
'development' 카테고리의 다른 글
HTML 이미지 맵이 계속 사용됩니까? (0) | 2020.07.28 |
---|---|
SyntaxError : 예기치 않은 토큰 함수-Async Await Nodejs (0) | 2020.07.27 |
무한 목록이있는 폴더 대 폴더 동작 (0) | 2020.07.27 |
선택된 ng-option 변경시 가치 얻기 (0) | 2020.07.27 |
SQL Server 데이터베이스의 모든 데이터 삭제 (0) | 2020.07.27 |