【问题标题】:ASP.NET MVC Cast BaseViewModel to DerivedViewModelASP.NET MVC 将 BaseViewModel 转换为 DerivedViewModel
【发布时间】:2023-03-07 11:43:02
【问题描述】:

我正在开发一个注册流程,用户来并填写 5 个页面来完成一个流程。我决定有多个视图和一个控制器和一个 ProcessNext 操作方法来一步一步地进行。每次调用 Process Next 时,它都会获取原始视图和下一个视图。由于每个视图都与自己的视图模型相关联,因此我创建了一个基本视图模型,所有视图特定的视图模型都来自该模型。现在的问题是,强制转换会引发异常。这是示例代码

基础视图模型

public class BaseViewModel
{
     public string viewName;
}

个人视图模型

public class PersonalViewModel : BaseViewModel
{
    public string FirstName;
    // rest properties comes here
}

索引.cshtml

@Model PersonalViewModel

   @using (Html.BeginForm("ProcessNext", "Wizard", FormMethod.Post, new { class = "form-horizontal", role = "form" }))
@Html.TextBoxFor(m => m.FirstName, new { @class = "form-control" })
<input type="submit" class="btn btn-default" value="Register" />

基本上,我在这里将视图与 PersonalViewModel 绑定

现在控制器中的 ProcessNext Action 方法是这样的。

public ActionResult ProcessNext(BaseViewModel viewModelData)
{
  PersonalViewModel per = (PersonalViewModel) viewModelData;
}

这是失败并抛出类型案例异常,为什么?..

我的想法是只使用一种操作方法来转换所有这些派生的视图模型并发送到一个通用类进行验证和处理。请帮我解决这个问题。谢谢!

【问题讨论】:

  • 因为 viewModelData 不是 PersonalViewModel 您不能将基类型强制转换为派生类型。无论如何,这可能行不通,因为您发回BaseViewModel,所以与PersonalViewModel 相关的所有内容都丢失了。
  • 在基本的哎呀.. 我很高兴将基类类型转换为派生.. 对吗?.. 那么在这里可以做些什么来让它工作呢?有什么疯狂的猜测吗?
  • 在第一步中,您的帖子返回到public ActionResult Step1(PersonalViewModel model),它保存数据然后重定向到一个 GET 方法,该方法呈现您的Step2ViewModel 视图,该视图返回到public ActionResult Step2(Step2ViewModel model) 等。没有意义在 BaseViewModel 中,除了可能包含所有步进模型共有的 ID 属性
  • 并且“我很适合将基类类型转换为派生.. 对吗?” 不,你不能那样做。但是你可以反过来做。

标签: asp.net asp.net-mvc casting viewmodel


【解决方案1】:

您看到此异常的原因是您的模型类型是BaseViewModel 而不是PersonalViewModel。模型绑定器是创建模型的工具,由于您的操作模型是 BaseViewModel,它会创建一个 BaseViewModel 对象。

我建议您为每个步骤创建单独的操作。每个动作都应该有其对应的模型。我也认为在这种情况下你应该更喜欢组合而不是继承。

public class FullModel
{
    public FirstStepModel FirstStep {get;set;}
    public SecondStepModel SecondStep {get;set;}
}

然后,一旦您开始您的流程(例如在第一步),您可以创建一个 FullModel 对象并将其存储在某处(会话/cookie/序列化为文本并发送给客户端 - 这完全取决于您) .

然后在控制器中你将拥有

[HttpGet]
public ActionResult ProcessFirst()
{       
    HttpContext.Session["FullModel"] = new FullModel(); //at the beginning  store full model in session 
    var firstStepModel = new FirstsStepModel();
    return View(firstStepModel) //return a view for first step
}


[HttpPost]
public ActionResult ProcessFirst(FirstStepModel model)
{
    if(this.ModelState.IsValid)
    {
        var fullModel = HttpContext.Session["FullModel"] as FullModel; //assuming that you stored it in session variable with name "FullModel"
        if(fullModel == null)
        {
           //something went wrong and your full model is not in session..
           //return some error page
        }
        fullModel.FirstStep = model;
        HttpContext.Session["FullModel"] = fullModel; // update your session with latest model
        var secondStepModel = new SecondStepModel();
        return View("SecondStepView", secondStepModel) //return a view for second step
    }

    // model is invalid ...
    return View("FirstStepView", model);    
}

[HttpPost]
public ActionResult ProcessSecond(SecondStepModel model)
{
    var fullModel = HttpContext.Session["FullModel"] as FullModel; //assuming that you stored it in session variable with name "FullModel"
    if(fullModel == null)
    {
        //something went wrong and your full model is not in session..
        //return some error page
    }
    fullModel.SecondStep = model;
    HttpContext.Session["FullModel"] = fullModel; // update your session with latest model
    var thirdStepModel = new ThirdStepModel();
    return View("ThirdStepModel", thirdStepModel); //return a view for a third step
}

当然,您应该将所有共享代码提取到一些可重用的方法中。 在请求之间传递FullModel 完全取决于您。

如果您仍然喜欢使用一个 Action 解决方案,您需要创建一个自定义模型绑定器,该绑定器将根据从客户端传递的一些数据创建派生实例。看看这个thread

【讨论】:

  • 发生这种情况的解决方法是什么?任何的想法?扩展模型 BIinder 会起作用吗?
【解决方案2】:

我想出了一种使用模型绑定器来处理这种情况的通用方法。这里是.. 您可能需要使用 DefaultBinder 的扩展模型绑定器来实现以返回您的模型类型。

public class WizardModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var viewIdContext = bindingContext.ValueProvider.GetValue("ViewId");
        int StepId = 0;

        if (!int.TryParse(viewIdContext, out StepId))
            throw new InvalidOperationException("Incorrect view identity");
        //This is my factory who gave me child view based on the next view id you can play around with this logic to identify which view should be rendered next
        var model = WizardFactory.GetViewModel(StepId);

        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, model.GetType());
        bindingContext.ModelMetadata.Model = model;
        return model;
    }
}

你可以从你的 gloab asx 中注册这个活页夹

 ModelBinders.Binders.Add(typeof(BaseViewModel), new WizardModelBinder());

感谢所有回复我的问题的人..!!如果你们有任何问题,请告诉我。

编码愉快..!!

【讨论】:

    猜你喜欢
    • 2023-02-02
    • 1970-01-01
    • 2011-02-20
    • 2015-05-30
    • 1970-01-01
    • 2021-05-13
    • 1970-01-01
    • 2023-03-11
    • 1970-01-01
    相关资源
    最近更新 更多