Öncelikle, görünümlerinizde herhangi bir etki alanı nesnesi kullanmamalısınız. Görünüm modellerini kullanmalısınız. Her görünüm modeli, yalnızca verilen görünümün gerektirdiği özellikleri ve bu görünüme özgü doğrulama özniteliklerini içerecektir. Dolayısıyla, 3 adımlı sihirbazınız varsa bu, her adım için bir tane olmak üzere 3 görünüm modeliniz olacağı anlamına gelir:
public class Step1ViewModel
{
[Required]
public string SomeProperty { get; set; }
...
}
public class Step2ViewModel
{
[Required]
public string SomeOtherProperty { get; set; }
...
}
ve bunun gibi. Tüm bu görünüm modelleri, bir ana sihirbaz görünüm modeli tarafından desteklenebilir:
public class WizardViewModel
{
public Step1ViewModel Step1 { get; set; }
public Step2ViewModel Step2 { get; set; }
...
}
daha sonra, sihirbaz sürecinin her adımını gerçekleştiren ve ana öğeyi WizardViewModel
görünüme geçiren denetleyici eylemlerine sahip olabilirsiniz . Denetleyici eyleminin ilk adımındayken, Step1
özelliği başlatabilirsiniz . Ardından görünümün içinde, kullanıcının 1. adımla ilgili özellikleri doldurmasına izin veren formu oluşturursunuz. Form gönderildiğinde, denetleyici eylemi yalnızca 1. adım için doğrulama kurallarını uygular:
[HttpPost]
public ActionResult Step1(Step1ViewModel step1)
{
var model = new WizardViewModel
{
Step1 = step1
};
if (!ModelState.IsValid)
{
return View(model);
}
return View("Step2", model);
}
Şimdi 2. adım görünümünün içinde, 1. adımı formun içindeki gizli bir alana serileştirmek için MVC vadeli işlemlerinden Html.Serialize yardımcısını kullanabilirsiniz (isterseniz bir ViewState türü):
@using (Html.BeginForm("Step2", "Wizard"))
{
@Html.Serialize("Step1", Model.Step1)
@Html.EditorFor(x => x.Step2)
...
}
ve 2. adımın POST eyleminin içinde:
[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);
}
Ve böylece, WizardViewModel
tüm verilerle doldurulacağınız son adıma gelene kadar devam edin . Ardından görünüm modelini etki alanı modelinizle eşler ve işlenmesi için hizmet katmanına aktarırsınız. Hizmet katmanı herhangi bir doğrulama kuralını kendisi gerçekleştirebilir ve bu böyle devam eder ...
Başka bir alternatif daha var: javascript kullanmak ve hepsini aynı sayfaya koymak. Orada sihirbaz işlevselliği sağlayan birçok jquery eklentisi var ( Stepy güzel bir eklentidir ). Temelde bu, istemcide div'leri gösterme ve gizleme meselesidir; bu durumda artık adımlar arasında kalıcı durum hakkında endişelenmenize gerek kalmaz.
Ancak hangi çözümü seçerseniz seçin, her zaman görüntüleme modellerini kullanın ve bu görünüm modellerinde doğrulama gerçekleştirin. Etki alanı modellerinize veri ek açıklaması doğrulama özniteliklerini yapıştırdığınız sürece, etki alanı modelleri görünümlere uyarlanmadığı için çok zorlanacaksınız.
GÜNCELLEME:
Tamam, sayısız yorum nedeniyle cevabımın net olmadığı sonucuna vardım. Ve kabul etmeliyim. Öyleyse, örneğimi daha da geliştirmeye çalışayım.
Tüm adım görünümü modellerinin uygulaması gereken bir arayüz tanımlayabiliriz (bu sadece bir işaret arayüzü):
public interface IStepViewModel
{
}
daha sonra sihirbaz için, her adımın elbette yalnızca gerektirdiği özellikleri ve ilgili doğrulama niteliklerini içereceği 3 adım tanımlarız:
[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; }
}
daha sonra, bir adım listesi ve geçerli bir adım dizininden oluşan ana sihirbaz görünüm modelini tanımlarız:
[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();
}
}
Sonra denetleyiciye geçiyoruz:
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);
}
}
Bu denetleyici hakkında birkaç açıklama:
- Dizin POST eylemi
[Deserialize]
Microsoft Futures kitaplığındaki öznitelikleri kullanır, bu nedenle MvcContrib
NuGet'i yüklediğinizden emin olun . Görünüm modellerinin [Serializable]
öznitelikle dekore edilmesinin nedeni budur.
- Index POST işlemi argüman olarak bir
IStepViewModel
arabirim alır, bu nedenle bunun anlamlı olması için özel bir model bağlayıcısına ihtiyacımız var.
İlişkili model bağlayıcı:
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;
}
}
Bu bağlayıcı, her adımın somut türünü içeren ve her istek üzerine göndereceğimiz StepType adlı özel bir gizli alan kullanır.
Bu model bağlayıcı şurada kaydedilecek Application_Start
:
ModelBinders.Binders.Add(typeof(IStepViewModel), new StepViewModelBinder());
Bulmacanın son eksik kısmı, görünümlerdir. İşte ana ~/Views/Wizard/Index.cshtml
görünüm:
@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" />
}
}
Ve bunu çalıştırmak için ihtiyacınız olan tek şey bu. Elbette isterseniz, özel bir düzenleyici şablonu tanımlayarak sihirbazın bazı veya tüm adımlarının görünümünü ve hissini kişiselleştirebilirsiniz. Örneğin 2. adım için yapalım. Yani bir ~/Views/Wizard/EditorTemplates/Step2ViewModel.cshtml
parçayı tanımlıyoruz :
@model Step2ViewModel
Special Step 2
@Html.TextBoxFor(x => x.Bar)
Yapı şu şekildedir:
Elbette iyileştirme için yer var. Dizin POST eylemi s..t gibi görünür. İçinde çok fazla kod var. Daha fazla basitleştirme, dizin, mevcut dizin yönetimi, mevcut adımın sihirbaza kopyalanması gibi tüm altyapı öğelerinin başka bir model bağlayıcısına taşınmasını içerecektir. Böylece sonunda şunu elde ederiz:
[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 eylemlerinin nasıl görünmesi gerektiği daha fazlasıdır. Bu iyileştirmeyi bir dahaki sefere bırakıyorum :-)