MVC 5 EF 6 TPT和ViewModels

时间:2014-01-27 16:42:54

标签: c# asp.net-mvc entity-framework inheritance mvvm

我的应用程序操作方法如下......

模型和子模型

public class BaseModel
{
    public int Id { get; set; }        
    // Other required properties
}

public class SubModel : BaseModel
{
    public string SomeString { get; set; }
    // Other SubModel properties
}

在上下文中

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<SubModel>().ToTable("SubModel");
}

模型绑定器,它接受在我的创建视图中定义的字符串并返回相应的子模型。

public class BaseModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        if (modelType.Equals(typeof(BaseModelBinder)))
        {
            string typeValue = bindingContext.ValueProvider.GetValue("ModelType").AttemptedValue;
            Type type = Type.GetType(modelType.Namespace + "." + typeValue, true);
            object model = Activator.CreateInstance(type);

            bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
            return model;
        }

        return base.CreateModel(controllerContext, bindingContext, modelType);
    }
}

控制器

[Route("Create"), HttpGet]
public ActionResult Create(string modelType)
{
    // Get Model Type from modelType and convert into appropriate type
    // to retrieve the proper editor template.
    Type type = Type.GetType("MyApp.Models." + modelType);
    object model = Activator.CreateInstance(type);
    return View(model);
}

[Route("Create"), HttpPost]
public ActionResult Create(BaseModel baseModel)
{
    if (ModelState.IsValid)
    {
        var userId = Convert.ToInt32(IdentityExtensions.GetUserId(User.Identity));
        baseModel.UserId = userId;
        baseModel.DatePosted = DateTime.UtcNow;
        // If this is a SubModel, an insert is made into table BaseModel as well
        // as an insert into the SubModel table for SubModel specific properties
        // linked by the Id of the BaseModel as the SubModel Id
        db.BaseModel.Add(baseModel);
        db.SaveChanges();
        return RedirectToAction("List");
    }        
    return View(baseModel);
}

创建视图

@model MyApp.Models.BaseModel

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    <div class="form-horizontal">
        @Html.ValidationSummary(true)
        @Html.Hidden("ModelType", Model.GetType())
        @Html.EditorForModel()
    </div>
}

SubModel的编辑器模板

@model MyApp.Models.SubModel
// Form Stuff

这一切都很好,花花公子。我可以拉出Create View并显示所请求的SubModel的正确模板,当我发布它时,Create HttpPost操作将其作为Type BaseModel的类型,并插入SubModel表和BaseModel表。现在我要做的是为我的BaseModel和SubModel创建ViewModel,以保持域模型的清洁。

问题

如果我更改我的控制器以返回ViewModel,当我发布到Create Action时,它不是正确的Type,因为它不会从BaseModel继承。

如何为BaseModel创建一个ViewModel,它只公开我可以为客户端验证进行Data Annotate的某些属性,并为SubModel创建SubViewModel,并让SibViewModel也是Type SubModel,它也是Type BaseModel既然SubModel继承自BaseModel?

我还希望SubViewModel继承BaseModelViewModel属性,因为每个SubModel都需要部分BaseModel。

看看这个link ,关于泛型的答案可能是我想要采取的路径,但不确定如何正确实施它。

我需要使用ViewModel,将其传递给Create [HttpPost]控制器并将其插入BaseModel表(或SubModel和BaseModel,具体取决于ViewModel)。

很长的帖子,可能会让我措辞混乱,因为我很难描述我有时想要完成的事情。我花了几个小时阅读ASP.NET MVC,看起来我读得越多,我对设计模式的质疑就越多,因为有很多选择来完成相同的任务,每个都有利弊,依赖关于这么多因素......我的思绪过重了。请帮忙:]

2 个答案:

答案 0 :(得分:3)

  

如何为BaseModel创建一个ViewModel,它只公开我可以为客户端验证进行Data Annotate的某些属性,并为SubModel创建SubViewModel,并让SibViewModel也是Type SubModel,它也是Type BaseModel因为SubModels继承自BaseModel?

如果我理解正确,那么你就不能。这将要求您的SubViewModel从SubModel继承BaseViewModel ,这需要多次继承。这在.NET中不可用。

即使有可能,我也不会推荐它。我相信你想要这样做的原因是尽可能多地重用代码,而不必维护SubViewModel成为域模型的“镜像”。但这真的是正确的方法吗?

考虑一下:让我们假设您的SubViewModel确实从您的域模型继承。这将导致所有属性和方法在您的viewmodel中可用,从而在您的视图中可用。它还会导致所有 future 属性和方法在您的视图中可用。您无法再控制视图可用的属性和方法,而无需更改域模型。您可以向viewmodel添加新属性和方法,而无需将其添加到域模型中,但不能删除从域模型继承的那些属性和方法。我认为这是一个缺点。理想情况下,您希望视图模型能够准确地公开视图完成其任务所需的数据,而不再需要。请记住,“视图模型”的概念是拥有一个对特定视图和它们应该完成的任务有用的模型。

还有另一个缺点。要启用客户端验证,您需要使用Data Annotations属性修饰属性。如果您的SubViewModel继承您的域模型以重用属性,例如Name,ZipCode等,那么您需要将这些属性放在域模型中的属性上。所以现在您在域模型中拥有与MVC客户端验证相关的属性。如果您后来决定在另一个使用其他技术的项目中重用您的域模型,而您没有使用MVC,那么这些属性几乎是无用的。 (我差点说,因为在特定的情况下,你可能很幸运并找到一些可以利用数据注释属性的替代验证框架,但这不能保证)实际上,你会“污染”你的域模型与域名无关的东西。

现在,我并不是说验证与域无关,因为它肯定不是。但我会考虑在域层中使用更通用的验证方法,而不是使用Data Annotations属性。 (例如,流利验证)

此外,如果您需要更复杂的验证(现在或将来),那么您很快就会发现缺少Data Annotations属性。您可以实现自己的属性,但如果您需要更复杂的验证(例如条件验证),它就不会很好。 ('该属性应遵循这些规则,如果其他属性......')

此外,如果您稍后发现需要在MVC中使用另一个不使用数据注释属性的验证框架,则需要更改域模型以删除属性。

你看到这里的模式了吗?您想要在UI(MVC)中更改某些内容,因此您需要在域模型中更改某些内容。这真的不太理想。您希望域图层和UI图层尽可能分离。

我知道我可能已经在这里切断了,而且可能没有直接回答你的问题。但我的观点是,我认为您应该重新考虑将您的域模型和MVC应用程序结合在一起,方法是让您的视图模型继承您的域模型。

实际上,如果你这样做,你上面描述的问题就会消失。

答案 1 :(得分:1)

免责声明这只是一个高级评论

像René所指出的那样,看起来你的问题不是真正的问题

因为ViewModel应该封装Model而不是继承它。这基本上就是MVVM的VM-M部分的工作原理

通常VM应该与此类似

public class VM
{
    M model;

    public M Model
    {
        get { return model; }
        set { model = value; }
    }

    public int ID
    {
        get { return model.ID; }
        set { model.ID = value; }
    }

    #region cTor
    public VM(M m)
    {
        this.Model = m;
    }
    #endregion
}

但要将Model-Laver与ViewModle-Layer分开,您可以使用界面
因此M可以是ClassInterface,具体取决于您的需求。