0

我有以下通过一对多关系相关的模型类。这些类通过 Code First 方法持久化到 SQL Server 数据库中:

public class Topic
{
    [Key]
    public int Id { get; set; }

    [InverseProperty("Topic")]
    public virtual IList<Chapter> Chapters { get; set; }

    //some other properties...
}

public class Chapter : IValidatableObject
{
    [Key]
    public int Id { get; set; }

    [Required]
    public string Key { get; set }

    public virtual Topic Topic { get; set; }

    //some other properties...
}

每个Topic包含一堆Chapters. 每个Chapter都有一个Key在其Topic.

我试图用以下方法验证这一点:

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{  
    var chaptersWithSameKey = Topic.Chapters.Where(t => t.Key == Key);
    foreach (var item in chaptersWithSameKey)
    {
        if (item.Id != Id)
        {
            yield return new ValidationResult("The key must be unique.", new string[] { "Key" });
            break;
        }
    }            
}

但是,Topic始终null是在发布到“创建”或“编辑”操作后发生验证时。这似乎是合理的,因为视图不包含有关Topic. 但是,我可以在控制器中提取主题,因为主题的 id 是 URL 的一部分。

我的第一次尝试是在控制器中的 Post Create 操作的开头设置主题:

[HttpPost]
public ActionResult Create(int topicId, Chapter chapter)
{
    var topic = db.Topics.Find(topicId);
    if (topic == null)
        return HttpNotFound();
    chapter.Topic = topic;
    if(ModelState.IsValid)
        ...
}

然而,本章的Validate方法在控制器可以做任何事情之前被调用。因此,本章的主题又是null

另一种方法是通过以下方式告诉 Create 视图它属于哪个主题:

[HttpGet]
public ActionResult Create(int topicId)
{
    var topic = ...
    var newChapter = new Chapter() { Topic = topic };
    return View(newChapter);
}

并在视图中设置一个隐藏字段:

@Html.HiddenFor(model => model.Topic)
@Html.HiddenFor(model => model.Topic.Id)

第一个null像以前一样给出一个主题。这看起来很自然,因为渲染的隐藏字段的值只是主题的ToString()结果。

第二个似乎试图验证主题,但由于缺少属性而失败。实际原因是NullReferenceException当一个只读属性Topic试图评估另一个null属性时。我完全不知道为什么要访问只读属性。调用堆栈有一些Validate...方法。

那么上述场景的最佳解决方案是什么?我正在尝试在模型中进行验证,但缺少一些可以在控制器中检索的必要值。

我可以为此任务创建一个包含 aint TopicId而不是Topic Topic. 但是我必须将每个属性和注释复制到视图模型或通过继承来完成。第一种方法似乎效率很低。

所以到目前为止,继承方法可能是最好的选择。但是有没有其他选项不需要引入额外的类型?

4

2 回答 2

1

首先,您必须意识到 Validation(然后是您的Validate()方法)是在ModelBinderAction 执行之前由 早期执行的。

其次,我认为您的主要问题是您没有使用 aViewModel而是将您返回Entity/Model到视图并返回到控制器。

您的视图通常与模型/实体本身具有不同的职责和关注点(就像您的情况一样)。不同的数据结构、不同的验证规则,最重要的是,您可以塑造您的 ViewModel 对象以适应确切的页面/视图需求。

您当前的Validate()方法似乎适合数据层验证需求,而不是您的视图验证需求。

尝试这个:

public class CreateChapterViewModel : IValidatableObject
{
    public int Id { get; set; }  // possible not needed for 'Create' flow
    public string Key { get; set }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {  
         // validation logic that applies to Chapter creation only, for example:
         // if (this.Key == null) ...
    }
}

然后在你的Action

[HttpPost]
public ActionResult Create(int topicId, CreateChapterViewModel chapter)
{
     ...
}

总而言之,不要试图强迫你的实体在你的视图上,他们通常有不同的需求,用 ViewModels 给他们提供,让他们也发送 ViewModels。

这种方法的权衡是您必须将您的实体映射到 ViewModel 并返回,要么创建您自己的映射器,要么使用类似AutoMapper的东西。

于 2013-08-28T14:39:31.517 回答
0

这是我的最终解决方案,它稍微改编了haim的答案。我不完全同意他的原因是我想在创建(或编辑)实体时强制执行模型层约束。提到的约束(唯一键)是模型层约束,我不明白为什么应该将它移到视图中。

我做了以下。Chapter保持不变(包括验证属性和自定义验证方法)。我创建了一个视图模型,它继承了Chapter's属性和行为,并添加了TopicId用于标识主题的属性。此外,它会覆盖该Topic属性并从数据库中获取主题。

[NotMapped]
public class ChapterViewModel : Chapter
{
    public int TopicId { get; set; }
    public override Topic Topic
    {
        get
        {
            return DbContext.Topics.Find(TopicId);
        }
    }

    private MyDbContext ctx;
    public MyDbContext DbContext
    {
        private get { if (ctx == null) ctx = new CadenzaDbContext(); return ctx; }
        set { ctx = value; }
    }

    public ChapterViewModel() { }
    public ChapterViewModel(Chapter c)
    {
        Id = c.Id;
        TopicId = c.Topic == null ? -1 : c.Topic.Id;
        Key = c.Key;
    }
    public Chapter ToPlainChapter(MyDbContext db)
    {
        DbContext = db;
        return new Chapter()
        {
            Id = Id,
            Topic = Topic,
            Key = Key,
            Name = Name
        };
    }
}

它可以像原来一样使用Chapter。对于Chapter视图或在访问DbContext.

上述方法的优点是我不必将每个属性都复制到视图模型中。此外,现有的验证规则也适用于视图模型,这在我的情况下是所需的行为。任何显示属性只能应用一次,而不是在每个视图模型中。

于 2013-08-30T12:43:40.017 回答