Действительность ModelState с использованием ViewModel

У меня есть таблица Product, таблица ProductType и, наконец, таблица ProductCompanies. Вот их отношение:

Связь таблиц продуктов

У меня есть ProductViewModel, которая выглядит так:

public class ProductViewModel
    {
        public Product Product { get; set; }

        public List<SelectListItem> ProductTypes { get; set; }
        public List<SelectListItem> ProductCompanies { get; set; }

        [RegularExpression(@"^[a-zA-Zàéèêçñ\s][a-zA-Zàéèêçñ\s-]+$", ErrorMessage = "Invalid name !")]
        public string ModelName { get; set; }

        [RegularExpression(@"^[a-zA-Zàéèêçñ\s][a-zA-Zàéèêçñ\s-]+$", ErrorMessage = "Invalid name !")]        
        public string CompanyName { get; set; }
    }

У меня есть персонализированное Создать представление, которое работает следующим образом: есть раскрывающийся список, содержащий все существующие типы продуктов. Если пользователь хочет создать новый, это можно сделать, щелкнув ссылку, после чего появится скрытый раздел. В этом разделе вы можете добавить новое название модели и указать компанию:

@model BuSIMaterial.Models.ProductViewModel

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>Create a material</legend>
        <div class="editor-label">
            Product type : <a class="product_type" id="product_type_link">Using a new model</a>
        </div>
        <div id= "existing_product_type" class="editor-field">
            @Html.DropDownListFor(p => p.Product.ProductType.Id_ProductType, Model.ProductTypes)
            @Html.ValidationMessageFor(model => model.Product.Id_ProductType)
        </div>
        <div id="new_product_type">
            <div class="editor-label">
                Model : 
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(model => model.ModelName, new { maxlength = 50, id = "model"})
                @Html.ValidationMessageFor(model => model.ModelName)
            </div>

            <div class="editor-label">
                Company : <a class="company" id="company_link">Using a new company name</a>
            </div>
            <div id="existing_company" class="editor-field">
                @Html.DropDownListFor(p => p.Product.ProductType.Id_ProductCompany, Model.ProductCompanies)
                @Html.ValidationMessageFor(model => model.Product.ProductType.Id_ProductCompany)
            </div>
            <div id="new_company">
                <div class="editor-label">
                    Name : 
                </div>
                <div class="editor-field">
                    @Html.TextBoxFor(model => model.CompanyName, new { id = "company_name"})
                    @Html.ValidationMessageFor(model => model.CompanyName)
                </div>       
                </div>
        </div>

        <div class="editor-label">
            Catalog price : 
        </div>
        <div class="editor-field">
            @Html.TextBoxFor(model => model.Product.CatalogPrice)
            @Html.ValidationMessageFor(model => model.Product.CatalogPrice)
        </div>
        <div class="editor-label">
            Serial number : 
        </div>
        <div class="editor-field">
            @Html.TextBoxFor(model => model.Product.SerialNumber)
            @Html.ValidationMessageFor(model => model.Product.SerialNumber)
        </div>
        <div class="editor-label">
            Purchase date : 
        </div>
        <div class="editor-field">
            @Html.TextBoxFor(model => model.Product.PurchaseDate, new { id = "datepicker"})
            @Html.ValidationMessageFor(model => model.Product.PurchaseDate)
        </div>

        <div class="form-actions">
          <button type="submit" class="btn btn-primary">Create</button>
        </div>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
    @Scripts.Render("~/bundles/jqueryui")
    @Styles.Render("~/Content/themes/base/css")

    <script type="text/javascript">

        $(document).ready(function () {

            $("#product_type_link").click(function () {

                if ($("#new_product_type").css("display") == "block") {

                    $("#new_product_type").css("display", "none");
                    $("#existing_product_type").css("display", "block");
                    $("#product_type_link").text("Using a new model");
                    $("#model").val("");
                    $("#company_name").val("");

                }
                else {
                    $("#new_product_type").css("display", "block");
                    $("#existing_product_type").css("display", "none");
                    $("#product_type_link").text("Using an existing model")
                }
            });

            $("#company_link").click(function () {

                if ($("#new_company").css("display") == "block") {

                    $("#new_company").css("display", "none");
                    $("#existing_company").css("display", "block");
                    $("#company_link").text("Using a new company name");
                    $("#company_name").val("");

                }
                else {
                    $("#new_company").css("display", "block");
                    $("#existing_company").css("display", "none");
                    $("#company_link").text("Using an existing company name")
                }
            });

        });

    </script>
}

В своем почтовом действии я просто проверяю, ввел ли пользователь что-то в скрытое поле. Если это сделано, я делаю некоторую работу, но ничего особенно сложного:

[HttpPost]
public ActionResult Create(ProductViewModel pvm)
{
    var productTypeList = from obj in db.ProductTypes orderby obj.ProductCompany.Name ascending where !((from element in db.VehicleTypes select element.Id_ProductType).Contains(obj.Id_ProductType)) select obj;

    ViewBag.Id_ProductType = new SelectList(productTypeList, "Id_ProductType", "Information", pvm.Product.Id_ProductType);
    pvm.ProductTypes = productTypeList.ToList().Select(p => new SelectListItem { Text = p.Information, Value = p.Id_ProductType.ToString() }).ToList();

    ViewBag.Id_ProductCompany = new SelectList(db.ProductCompanies, "Id_ProductCompany", "Name", pvm.Product.ProductType.Id_ProductCompany);
    pvm.ProductCompanies = db.ProductCompanies.ToList().Select(c => new SelectListItem { Text = c.Name, Value = c.Id_ProductCompany.ToString() }).ToList();

    Product product = new Product()
    {
        PurchaseDate = pvm.Product.PurchaseDate,
        SerialNumber = pvm.Product.SerialNumber,
        CatalogPrice = pvm.Product.CatalogPrice
    };

    ProductType productType = null; 

    if (ModelState.IsValid)
    {
        ModelStateDictionary errors = Validator.isValid(pvm.Product);

        if (errors.Count > 0)
        {
            ModelState.Merge(errors);
            return View(pvm);
        }

        if (!string.IsNullOrWhiteSpace(pvm.ModelName))
        {
            if (!string.IsNullOrWhiteSpace(pvm.CompanyName))
            {
                ProductCompany productCompany = new ProductCompany()
                {
                    Name = pvm.CompanyName
                };

                productType = new ProductType()
                {
                    Model = pvm.ModelName,
                    ProductCompany = productCompany
                };

            }
            else
            {
                productType = new ProductType()
                {
                    Model = pvm.ModelName,
                    Id_ProductCompany = pvm.Product.ProductType.Id_ProductCompany
                };
            }
        }
        else
        {
            productType = new ProductType()
            {
                Id_ProductType = pvm.Product.Id_ProductType,
                Id_ProductCompany = pvm.Product.ProductType.Id_ProductCompany
            };
        }

        product.ProductType = productType;
        db.Products.AddObject(product);
        db.SaveChanges();

        return RedirectToAction("Index");
    }

    return View(pvm);
}

Моя проблема в том, что я никогда не достигаю кода внутри if(ModelState.isValid), потому что мой ModelState недействителен... И я не знаю почему! Выполнив отладку несколько раз, я обнаружил, что Product.ProducType.Model моей ViewModel имеет значение null (и не должно быть, потому что, если пользователь выбирает что-то в раскрывающемся списке, все в порядке, и если он вводит новое имя модели, все должно быть в порядке).

Любая идея о том, что происходит?

EDIT: HttpGet действия Create:

public ActionResult Create()
{
    ProductViewModel pvm = new ProductViewModel();
    var productTypeList = from obj in db.ProductTypes orderby obj.ProductCompany.Name ascending where !((from element in db.VehicleTypes select element.Id_ProductType).Contains(obj.Id_ProductType)) select obj;

    ViewBag.Id_ProductType = new SelectList(productTypeList, "Id_ProductType", "Information");
    pvm.ProductTypes = productTypeList.ToList().Select(p => new SelectListItem { Text = p.Information, Value = p.Id_ProductType.ToString() }).ToList();

    ViewBag.Id_ProductCompany = new SelectList(db.ProductCompanies, "Id_ProductCompany", "Name");
    pvm.ProductCompanies = db.ProductCompanies.ToList().Select(c => new SelectListItem { Text = c.Name, Value = c.Id_ProductCompany.ToString() }).ToList();


    return View(pvm);
}

person Traffy    schedule 29.04.2013    source источник
comment
Вы уверены, что ваши поля проходят тесты регулярных выражений?   -  person Dave Alperovich    schedule 29.04.2013
comment
Дело не в регулярном выражении. На самом деле, даже если я выберу что-то в своем раскрывающемся списке, ProductType.Model будет нулевым.   -  person Traffy    schedule 29.04.2013
comment
IC. пожалуйста, поделитесь своей get версией Create, я хотел бы увидеть ваш экземпляр...   -  person Dave Alperovich    schedule 29.04.2013
comment
Вот. Спасибо за ваше время.   -  person Traffy    schedule 29.04.2013
comment
НП. Я не вижу экземпляр pvm.Product   -  person Dave Alperovich    schedule 29.04.2013
comment
Должен ли я что-то создавать? Потому что я создаю новый (продукт).   -  person Traffy    schedule 29.04.2013
comment
p.Product.ProductType.Id_ProductType вы пытаетесь заполнить член члена класса, экземпляр которого вы не создали. вы должны создавать экземпляры как p.Product, так и p.Product.ProductType, иначе вы ничего не заполняете.   -  person Dave Alperovich    schedule 29.04.2013
comment
Не могли бы вы написать это в ответе, чтобы я мог проверить его и принять ваш ответ?   -  person Traffy    schedule 30.04.2013
comment
Если ваше Modelstate недействительно, что ModelState.Errors скажет вам при проверке?   -  person Major Byte    schedule 30.04.2013
comment
@MajorByte На самом деле, теперь у меня новая ошибка: пока я сохраняю объект, он возвращает ошибку, в которой говорится, что невозможно вставить значение NULL в столбец «Модель», но я выбираю что-то из выпадающего списка...   -  person Traffy    schedule 30.04.2013
comment
Поскольку Product должен иметь ProductType, связыватель модели создает новый объект ProductType при обратной передаче. Вероятно, для этого ProductType установлен только его идентификатор, поэтому вы получаете: невозможно вставить значение NULL в столбец «Модель». Теперь вы, вероятно, не хотите создавать новый ProductType каждый раз, когда хотите создать продукт.   -  person Major Byte    schedule 30.04.2013
comment
@MajorByte, я думаю, это именно то, что вы говорите. Верно, у меня есть только правильный идентификатор типа продукта... Как мне поступить, чтобы получить относительную информацию?   -  person Traffy    schedule 30.04.2013
comment
Что, если вы измените `@Html.DropDownListFor(p =› p.Product.ProductType.Id_ProductType, Model.ProductTypes)` в своем представлении на `@Html.DropDownListFor(p =› p.Product.Id_ProductType, Model.ProductTypes)` то есть просто установить для ID_ProductType продукта выбранное значение, а не создавать (новый) объект ProductType с этим идентификатором.   -  person Major Byte    schedule 30.04.2013
comment
давайте продолжим это обсуждение в чате   -  person Traffy    schedule 30.04.2013


Ответы (1)


Как следует из комментариев: измените @Html.DropDownListFor(p => p.Product.ProductType.Id_ProductType, Model.ProductTypes) в своем представлении на @Html.DropDownListFor(p => p.Product.Id_ProductType, Model.ProductTypes), т.е. просто установите для ID_ProductType продукта выбранное значение, а не создайте (новый) объект ProductType с этим идентификатором.

Затем в методе Create измените

    else
    {
        productType = new ProductType()
        {
            Id_ProductType = pvm.Product.Id_ProductType,
            Id_ProductCompany = pvm.Product.ProductType.Id_ProductCompany
        };
    }

    product.ProductType = productType;
    db.Products.AddObject(product);
    db.SaveChanges();

to

    else
    {
        product.Id_ProductType = pvm.Product.Id_ProductType,
    }

    product.ProductType = productType;
    db.Products.AddObject(product);
    db.SaveChanges();
person Major Byte    schedule 29.04.2013