이러한 요구 사항을 처리하는 방법을 공유하고 싶었습니다. 나는 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));
}
}
}
이제 "마법사"의 모든 단계에서 동일한 기본 모델을 사용하고 "Step 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>
}