简体   繁体   English

从通用EditorFor模板绑定模型属性的值

[英]Bind values of a property of my model from a generic EditorFor template

I am attempting to write a generic framework to assist with developing wizard-style forms. 我试图编写一个通用框架来协助开发向导样式的表单。

I have a model with properties representing each step in the wizard, eg 我有一个模型,该模型的属性表示向导中的每个步骤,例如

public class ExampleWizardTransaction : WizardTransaction
{
    public override TransactionType TransactionType { get; set; } = TransactionType.ExampleWizard;
    public override string ControllerName { get; set; } = "WizardExample";

    [DisplayName("Client Details")]
    public ClientDetails ClientDetails { get; set; } = new ClientDetails();

    [DisplayName("Client Questions")]
    public ClientQuestions ClientQuestions { get; set; } = new ClientQuestions();

    [DisplayName("Client Preferences")]
    public ClientPreferences ClientPreferences { get; set; } = new ClientPreferences();
}

[Step(1)]
public class ClientDetails : IStep
{
    [Display(Description = "Please enter your first name")]
    public string FirstName { get; set; }

    [Display(Description = "Please enter your last name")]
    public string LastName { get; set; }
}

[Step(2)]
public class ClientQuestions : IStep
{
    [DisplayName("What is your favourite car?")]
    public string FavouriteCar { get; set; }

    [DisplayName("What is your favourite holiday destination?")]
    public string FavouriteDestination { get; set; }
}

[Step(3)]
public class ClientPreferences : IStep
{
    [DisplayName("Red or Blue?")]
    public Colours Colour { get; set; }

    [DisplayName("Do you like Indian food")]
    public bool LikeFood { get; set; }
}

Initially, I had a partial view for each wizard step which looked like this: 最初,我对每个向导步骤都有部分视图,如下所示:

@model Web.Models.ExampleWizard.Create

<div class="row">
    <div class="col-md-6">
        @Html.EditorFor(x => x.ExampleWizardTransaction.ClientDetails)
    </div>
</div>

Using this, the values of my form bind up correctly since when I post it up MVC knows the binding context. 使用此方法,表单的值可以正确绑定,因为在我发布表单时,MVC知道绑定上下文。

On my form, I render the partial view by passing in the step number, eg 在我的表单上,我通过传递步骤号来呈现局部视图,例如

Html.RenderPartial($"_CreateWizard_{Model.ExampleWizardTransaction.CurrentStep}", Model);

I'm attempting to generalise the code, so that I don't need to include a partial view for every step in the wizard. 我试图对代码进行一般化,因此不需要在向导的每个步骤中都包含部分视图。

To do that, I'm rendering an action which determines which type is associated with the wizard step and I return a partial view: 为此,我呈现了一个操作,该操作确定与向导步骤关联的类型,然后返回部分视图:

Html.RenderAction("GetStep", "ExampleWizard", Model.ExampleWizardTransaction);

My partial view specifies an interface that each wizard step implements: 我的局部视图指定了每个向导步骤都实现的接口:

_WizardStep.cshtml _WizardStep.cshtml

@model Services.ViewModels.Wizard.IStep

<div class="row">
    <div class="col-md-6">
        @Html.EditorFor(x => x)
    </div>
</div>

When I use the above, the form renders correctly, but the values no longer bind up on the POST, which I assume is because it doesn't have the binding context for the properties (eg the id and name of the input types aren't fully qualified). 当我使用上面的方法时,表单可以正确呈现,但是值不再在POST上绑定,我认为这是因为它没有属性的绑定上下文(例如,输入类型的ID和名称为'完全合格)。

I have an EditorFor template for the string properties on my wizard steps which renders a textbox: 我在向导步骤中有一个用于字符串属性的EditorFor模板,用于呈现文本框:

@model string
<div class="col-md-12">
    <div class="form-group">
        <label class="m-b-none" for="@ViewData.Model">
            @ViewData.ModelMetadata.DisplayName
        </label>
        <span class="help-block m-b-none small m-t-none">@ViewData.ModelMetadata.Description</span>
        <div class="input-group">
            @Html.TextBox("", Model, new {@class = "form-control"})
            <div class="input-group-addon">
                <i class="fa validation"></i>
            </div>
        </div>

    </div>
</div>

Is it possible to use my generic "_WizardStep.cshtml" partial view and still bind up the properties for the current step to my model? 是否可以使用我的通用“ _WizardStep.cshtml”局部视图,并且仍将当前步骤的属性绑定到我的模型?

My controller looks like this: 我的控制器如下所示:

[HttpPost]
public virtual ActionResult CreateWizard(Create model, string action)
{
    var createModel = CreateModel<Create>();
    switch (createModel.Save(action))
    {
        case WizardState.Finished:
            return RedirectToActionWithMessage("List", "Transaction", "Completed", ToastNotificationStatus.Success);
        case WizardState.Ongoing:
            return RedirectToAction(MVC.ExampleWizard.CreateWizard(
                model.ExampleWizardTransaction.Id,
                model.ExampleWizardTransaction.GetNextStep(action)));
        default:
            model.MapExistingTransactions<ExampleWizardTransaction>();
            return View(model);
    }
}

My 'Create' model contains my 'ExampleWizardTransaction' property which contains each of the wizard steps which implement the IStep interface. 我的“创建”模型包含我的“ ExampleWizardTransaction”属性,该属性包含实现IStep接口的每个向导步骤。

Taking inspiration from @StephenMuecke's answer, I took the following approach. 从@StephenMuecke的答案中获得启发,我采取了以下方法。

On my 'CreateWizard.cshtml' view, I render the step with the following line: 在我的“ CreateWizard.cshtml”视图上,用以下行呈现此步骤:

@Html.WizardPartialFor(x => x.ExampleWizardTransaction.GetStepObject<IStep>(), "_WizardStep", Model.ExampleWizardTransaction)

This calls the 'WizardPartialFor' extension method: 这将调用“ WizardPartialFor”扩展方法:

public static MvcHtmlString WizardPartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper,
    Expression<Func<TModel, TProperty>> expression, string partialViewName, IWizardTransaction wizardTransaction)
{
    var compiled = expression.Compile();
    var result = compiled.Invoke(helper.ViewData.Model);

    PropertyInfo currentStep = wizardTransaction.GetCurrentStepPropertyInfo();

    string currentStepName = currentStep.PropertyType.Name;

    var name = $"{currentStep.DeclaringType.Name}.{currentStepName}";

    var viewData = new ViewDataDictionary(helper.ViewData)
    {
        TemplateInfo = new TemplateInfo { HtmlFieldPrefix = name }
    };

    return helper.Partial(partialViewName, result, viewData);
}

public static PropertyInfo GetCurrentStepPropertyInfo(this IWizardTransaction wizardTransaction)
{
    var properties = wizardTransaction.GetType().GetProperties()
        .Where(x => x.PropertyType.GetCustomAttributes(typeof(StepAttribute), true).Any());

    return properties.FirstOrDefault(x => ((StepAttribute)Attribute
        .GetCustomAttribute(x.PropertyType, typeof(StepAttribute))).Step == wizardTransaction.CurrentStep);
}

Into this extension method, we call an extension method which gets the wizard step object: 在此扩展方法中,我们调用一个扩展方法,该方法获取向导步骤对象:

public static TProperty GetStepObject<TProperty>(this IWizardTransaction wizardTransaction)
    where TProperty : class
{
    var properties = wizardTransaction.GetType().GetProperties()
        .Where(x => x.PropertyType.GetCustomAttributes(typeof(StepAttribute), true).Any());

    var @object = properties.FirstOrDefault(x => ((StepAttribute)Attribute
            .GetCustomAttribute(x.PropertyType, typeof(StepAttribute))).Step == wizardTransaction.CurrentStep);

    return @object.GetValue(wizardTransaction) as TProperty;
}

This successfully renders my generic _WizardStep partial view and successfully binds up the data on POST also. 这样可以成功呈现我的通用_WizardStep部分视图,并且还可以成功绑定POST上的数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM