Asp.net mvc如何最佳实践重建ViewModel?

6
在POST请求中,如果验证失败并且在将ViewModel带有模型状态的错误返回到同一视图之前,您是否会为所有SelectLists、ReadOnly字段等重建ViewModel? 现在,我有不同的方法来填充第一次(用于GET Edit-Method)/从域对象重建ViewModel,什么是最佳实践,让我能够DRY,并且不必每次向ViewModel添加新的只读属性时更改两个方法?
我的解决方案:遵循此模式 按照此处建议的模式进行操作:https://dev59.com/LnE95IYBdhLWcg3wrP4o#2775656 在IModelBuilder实现中
Build(..)
{  
   var viewModel = new ViewModel();     
   // and Fill all Non-ReadOnly fields
   ...
   ...
   call CompleteViewModel(viewModel)     
}  

CompleteViewModel(ViewModel viewModel)
{
  //Fill all ReadOnly & SelectLists
  ...
}

我选择这个解决方案的原因是因为我不想在服务器上存储东西以跨越HTTP请求检索。
3个回答

7
我不重建它,因为我不停留在POST状态。我遵循POST-REDIRECT-GET模式,所以如果我使用POST HTTP方法发布到/User/Edit/1,我将被重定向到使用GET的/User/Edit/1。
ModelState被传输到TempData中,以遵循Post-Redirect-Get并在GET调用时可用。视图模型在一个地方构建,在GET调用中。例如:
    [HttpPost]
    [ExportModelStateToTempData]
    public ActionResult Edit(int id, SomeVM postedModel)
    {
        if (ModelState.IsValid) {
            //do something with postedModel and then go back to list
            return RedirectToAction(ControllerActions.List);
        }

        //return back to edit, because there was an error
        return RedirectToAction(ControllerActions.Edit, new { id });
    }

    [ImportModelStateFromTempData]
    public ActionResult Edit(int id)
    {
        var model = //create model here
        return View(ControllerActions.Edit, model);
    }

这是与属性导入/导出ModelState相关的代码:
public abstract class ModelStateTempDataTransferAttribute : ActionFilterAttribute
{
    protected static readonly string Key = typeof(ModelStateTempDataTransferAttribute).FullName;
}

public class ExportModelStateToTempDataAttribute : ModelStateTempDataTransferAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        //Only export when ModelState is not valid
        if (!filterContext.Controller.ViewData.ModelState.IsValid)
        {
            //Export if we are redirecting
            if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult))
            {
                filterContext.Controller.TempData[Key] = filterContext.Controller.ViewData.ModelState;
            }
        }

        base.OnActionExecuted(filterContext);
    }
}

public class ImportModelStateFromTempDataAttribute : ModelStateTempDataTransferAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        ModelStateDictionary modelState = filterContext.Controller.TempData[Key] as ModelStateDictionary;

        if (modelState != null)
        {
            //Only Import if we are viewing
            if (filterContext.Result is ViewResult)
            {
                filterContext.Controller.ViewData.ModelState.Merge(modelState);
            }
            else
            {
                //Otherwise remove it.
                filterContext.Controller.TempData.Remove(Key);
            }
        }

        base.OnActionExecuted(filterContext);
    }
}

这非常有趣,我知道这会将错误传递给GET调用,但它是否也会传递用户更改? - pjobs
@pjobs:是的,它也传输表单值。ModelState 包含表单值。 - LukLed
两年后,这仍然很棒。只需将其放入我的项目中即可。 - JB06
@Josh:是的。我在MVC项目中每天都在使用它。 - LukLed
谢谢!这非常有用! - Dmitry Sikorsky

1
最简单的解决方案是将您的viewModel传递给该方法并考虑null。
private MyViewModel BuildViewModel(MyViewModel model = null)
{
    model = model ?? new MyViewModel();
    model.ReadOnlyList = new .....
    .
    .
    return model;
}

创建:

 var model = BuildViewModel();

重新构建:

 model = buildViewModel(model);

但是我认为这样做会覆盖用户所做的更改,除非我们检查只读字段、选择列表和元数据(属性),然后只更新它们。 - pjobs
@pjobs 如果您仅更新只读字段,那么不应覆盖任何用户输入!如果您传递一个填充有用户数据的模型,则会返回相同的模型,并且只会更新您覆盖的属性! - Bassam Mehanni
好的,明白了。将ViewModel的创建分为两个方法,一个填充只读属性,另一个填充非只读属性,在POST的情况下,像你说的那样调用第一个方法。 - pjobs
@pjobs 你可以这样做...但我的意思是,这种方法对于两种情况都很好...当你传递一个模型时,它不会覆盖非只读属性,它只会填充只读属性并返回相同的模型。 - Bassam Mehanni
抱歉,我想当我说CreateNew/Rebuild时可能让您感到困惑了。在这里,当我说Create-New时,我的意思是为Edit(GET)方法填充ViewModel。 - pjobs
如果您要最初填写只读字段,请在调用BuildViewModel()方法后在您的GET方法中执行此操作。 - Bassam Mehanni

0

我喜欢@LukLed上面的答案 - 看起来非常有趣。如果你想要另一个选项,这是我目前的做法。

在我的服务层中,我有一个方法来构建我的视图模型。我在GET时调用它并将视图模型返回给视图。在POST上,我从传入的ID构建模型,然后尝试更新模型(TryUpdateModel)。从那里开始,您可以做任何您喜欢的事情(保存、检查模型状态等)。使用这种方法,您只需要一个构建方法,并且只需要在模型更改时更新它一次(例如,在将来添加/删除属性等)。

[HttpGet]
public ActionResult AssessFocuses(int apaID)
{
    var model = this.apaService.BuildAssessFocusesViewModel(apaID);
    return this.View(model);
}

[HttpPost]
public ActionResult AssessFocuses(int apaID, string button)
{
    var model = this.apaService.BuildAssessFocusesViewModel(apaID);
    this.TryUpdateModel(model);

    switch (button)
    {
        case ButtonSubmitValues.Back:
        case ButtonSubmitValues.Next:
        case ButtonSubmitValues.Save:
        case ButtonSubmitValues.SaveAndClose:
            {
                try
                {
                    this.apaService.SaveFocusResults(model);
                }
                catch (ModelStateException<AssessFocusesViewModel> mse)
                {
                    mse.ApplyTo(this.ModelState);
                }

                if (!this.ModelState.IsValid)
                {
                    this.ShowErrorMessage(Resources.ErrorMsg_WEB_ValidationSummaryTitle);
                    return this.View(model);
                }

                break;
            }

        default:
            throw new InvalidOperationException(string.Format(Resources.ErrorMsg_WEB_InvalidButton, button));
    }

    switch (button)
    {
        case ButtonSubmitValues.Back:
            return this.RedirectToActionFor<APAController>(c => c.EnterRecommendationsPartner(model.ApaID));

        case ButtonSubmitValues.Next:
            return this.RedirectToActionFor<APAController>(c => c.AssessCompetenciesPartner(model.ApaID));

        case ButtonSubmitValues.Save:
            this.ShowSuccessMessage(Resources.Msg_WEB_NotifyBarSuccessGeneral);
            return this.RedirectToActionFor<APAController>(c => c.AssessFocuses(model.ApaID));

        case ButtonSubmitValues.SaveAndClose:
        default: 
            return this.RedirectToActionFor<UtilityController>(c => c.CloseWindow());
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接