MVC Required Property within Optional Property

2.7k Views Asked by At

I have two entities:

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

    [Required]
    public ChildThing TheFirstThing { get; set; }

    public ChildThing TheSecondThing { get; set; }
}

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

    [Required]
    public string Code { get; set; }

    public string Name { get; set; }
}

and a view model:

public class ParentViewModel
{
    public string Message { get; set; }

    public ParentThing ParentThing { get; set; }
}

and a view:

@using (@Html.BeginForm())
{
    <label>Code 1</label>
    @Html.EditorFor(model => model.ParentThing.TheFirstThing.Code)

    <label>Name 1</label>
    @Html.EditorFor(model => model.ParentThing.TheFirstThing.Name)

    <label>Code 2</label>
    @Html.EditorFor(model => model.ParentThing.TheSecondThing.Code)

    <label>Name 2</label>
    @Html.EditorFor(model => model.ParentThing.TheSecondThing.Name)

    <input type="submit" value="Save" />
}

In my post back I add the ParentThing to the context and attempt to save changes. I receive a validation error on the Code property of the TheSecondThing property of the ParentThing as it is required.

What are some alternatives for saving an optional property that contains required properties?

2

There are 2 best solutions below

0
On

As jamie suggested, remove any dependencies between your entities and your models...they're 2 separate things Don't use data annotations on your entities, use data annotations on your models. Remove the ParentThing property of your model and add as primitive properties in your model as you need (i.e. Message, ParentThingId, TheFirstThingId, TheFirstThingCode, TheFirstThingName, etc) and add all your data annotation attributes to the model. If you need to validate your entities (you probably will) do that on your business logic.

I hope it makes sense

Leo.

1
On

In response to the current suggestions this is what I now have.

Entities remain the same.

The modified view model:

public class ParentViewModel
{
    public string Message { get; set; }

    public string FirstThingCode { get; set; }

    public string FirstThingName { get; set; }

    public string SecondThingCode { get; set; }

    public string SecondThingName { get; set; }
}

The modified view:

@using (@Html.BeginForm())
{
    <label>Code 1</label>
    @Html.EditorFor(model => model.FirstThingCode)

    <label>Name 1</label>
    @Html.EditorFor(model => model.FirstThingName)

    <label>Code 2</label>
    @Html.EditorFor(model => model.SecondThingCode)

    <label>Name 2</label>
    @Html.EditorFor(model => model.SecondThingName)

    <input type="submit" value="Save" />
}

The DTO:

public class ParentThingDTO
{
    public ParentThingDTO(ParentViewModel model)
    {
        FirstThingCode = model.FirstThingCode;
        FirstThingName = model.FirstThingName;
        SecondThingCode = model.SecondThingCode;
        SecondThingName = model.SecondThingName;
    }

    public int Id { get; set; }

    public string FirstThingCode { get; set; }

    public string FirstThingName { get; set; }

    public string SecondThingCode { get; set; }

    public string SecondThingName { get; set; }

    public ParentThing GenerateEntity()
    {
        var thing = new ParentThing();
        thing.TheFirstThing = new ChildThing
        {
            Code = FirstThingCode,
            Name = FirstThingName
        };

        if (!string.IsNullOrWhiteSpace(SecondThingCode))
        {
            thing.TheSecondThing = new ChildThing
            {
                Code = SecondThingCode,
                Name = SecondThingName
            };
        }

        return thing;
    }
}

The postback action in the controller:

    [HttpPost]
    public ActionResult Create(ParentViewModel model)
    {
        try
        {
            var dto = new ParentThingDTO(model);
            var parentThing = dto.GenerateEntity();

            using (var context = new QuantumContext())
            {
                context.ParentThings.Add(parentThing);
                context.SaveChanges();
                model.Message = "Saved";
            }
        }
        catch (Exception ex)
        {
            model.Message = ex.Message;
        }

        return View(model);
    }

The null or whitespace test in the dto GenerateEntity method solves my initial problem of the MVC required property within the optional property. How does it look?