Predefine entity values on creation
I have the following model classes that are related by a one-to-many
relationship. The classes are persisted into a SQL Server database via the
Code First approach:
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...
}
Each Topic contains a bunch of Chapters. Each Chapter has a Key that must
be unique within its Topic.
I was trying to validate this with the following method:
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;
}
}
}
However, Topic is always null when validation occurs after posting to the
Create or Edit action. This seems reasonable because the views contain no
information about the Topic. However, I can extract the topic in the
controller because the topic's id is part of the URL.
My first attempt was to set the topic right at the beginning of the Post
Create action in the controller:
[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)
...
}
However, the chapter's Validate method is called before the controller can
do anything. Therefore, the chapter's topic is again null.
Another approach was to tell the Create view what topic it belongs to by:
[HttpGet]
public ActionResult Create(int topicId)
{
var topic = ...
var newChapter = new Chapter() { Topic = topic };
return View(newChapter);
}
and set up a hidden field in the view:
@Html.HiddenFor(model => model.Topic)
@Html.HiddenFor(model => model.Topic.Id)
The first one gives a null topic as before. This seems natural because the
rendered hidden field's value is just the topic's ToString() result.
The second one seemingly tries to validate the topic, but fails because
there are missing properties. The actual reason is a
NullReferenceException when a read-only property of Topic tries to
evaluate another null property. I have no clue why the read-only property
is accessed at all. The call stack has some Validate... methods.
So what is the best solution for the above scenario? I'm trying to do
validation in the model, but some necessary values are missing which could
be retrieved in a controller.
I could create a view model for this task that contains a int TopicId
instead of the Topic Topic. But then I would have to copy every property
and annotation to the view model or do it via inheritance. The first
approach seems quite inefficient.
So up to now the inheritance method is probably the best option. But are
there any other options which do not come with the need to introduce an
additional type?
No comments:
Post a Comment