0

I've put some Regex expression on the properties of my model that only accept numbers.

  public class MaterialsViewModel
    {
        [Display(Name = "Material")]
        public string MaterialName { get; set; }
        [Range(typeof(int), "0", "999")]
        [RegularExpression(@"^\d+$", ErrorMessage = "Please enter proper value")]
        public int? Quantity { get; set; }
        [RegularExpression(@"^\d+$", ErrorMessage = "Please enter proper value")]
        public double? Cost { get; set; }
        public IEnumerable<SelectListItem> CategoryList { get; set; }

        public int SelectedCategory { get; set; }
        public string SelectedCategoryName { get; set; }
    }

I have my View in which I can add controls dynamically

 Dynamically add ScopeOfWork and Materials<br />
    <div id="scopes">
        <h3>Scopes</h3>
        <a href="javascript:void(0)" id="addScope">Add Scope of Work</a>
        @for (int i = 0; i < Model.ScopeOfWork.Count; i++)
        {
            <div class="scope">
                <div class="form-group">
                    @Html.LabelFor(m => m.ScopeOfWork[i].ScopeOfWorkName, htmlAttributes: new { @class = "control-label col-md-2" })
                    <div class="col-md-10">
                        @Html.TextBoxFor(m => m.ScopeOfWork[i].ScopeOfWorkName, new { @class = "form-control" })
                        @Html.ValidationMessageFor(m => m.ScopeOfWork[i].ScopeOfWorkName)
                    </div>
                </div>
                <input type="hidden" class="scopeindex" name="ScopeOfWork.Index" value="@i" />
                <div class="indent materials">
                    <h4>Material</h4>
                    <a href="javascript:void(0)" class="addmaterial">Add Material</a>
                    @for (int j = 0; j < Model.ScopeOfWork[i].Materials.Count; j++)
                    {
                        <div class="material">
                            <div class="form-group">
                                @Html.LabelFor(m => m.ScopeOfWork[i].Materials[j].MaterialName, htmlAttributes: new { @class = "control-label col-md-2" })
                                <div class="col-md-2">
                                    @Html.TextBoxFor(m => m.ScopeOfWork[i].Materials[j].MaterialName, new { @class = "form-control" })
                                </div>
                                <div class="col-md-10 col-md-offset-2">
                                    @Html.ValidationMessageFor(m => m.ScopeOfWork[i].Materials[j].MaterialName)
                                </div>
                            </div>
                            <div class="form-group">
                                @Html.LabelFor(m => m.ScopeOfWork[i].Materials[j].Quantity, htmlAttributes: new { @class = "control-label col-md-2" })
                                <div class="col-md-1">
                                    @Html.TextBoxFor(m => m.ScopeOfWork[i].Materials[j].Quantity, new { @class = "form-control" })
                                </div>
                                <div class="col-md-10 col-md-offset-2">
                                    @Html.ValidationMessageFor(m => m.ScopeOfWork[i].Materials[j].Quantity)
                                </div>
                            </div>
                            <div class="form-group">
                                @Html.LabelFor(m => m.ScopeOfWork[i].Materials[j].Cost, htmlAttributes: new { @class = "control-label col-md-2" })
                                <div class="col-md-1">
                                    @Html.TextBoxFor(m => m.ScopeOfWork[i].Materials[j].Cost, new { @class = "form-control" })
                                </div>
                                <div class="col-md-10 col-md-offset-2">
                                    @Html.ValidationMessageFor(m => m.ScopeOfWork[i].Materials[j].Cost)
                                </div>
                            </div>
                            <div class="form-group">
                                <label class="control-label col-md-2">Category</label>
                                <div class="col-xs-2">
                                    @Html.DropDownListFor(m => m.ScopeOfWork[i].Materials[j].SelectedCategory, Model.ScopeOfWork[i].Materials[j].CategoryList, "Please select", htmlAttributes: new { @class = "form-control" })
                                </div>
                            </div>
                            <input type="hidden" class="materialindex" name="ScopeOfWork[@i].Materials.Index" value="@j" />
                        </div>
                    }
                </div>
            </div>
        }
    </div>

    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Create" class="btn btn-success" />
        </div>
    </div>
</div>
}


<div id="newScope" style="display:none">
    <div class="scope">
        <div class="form-group">
            <label for="_#__ScopeOfWorkName" class="control-label col-md-2">Scope Of Work</label>
            <div class="col-md-10">
                <input class="form-control" type="text" id="_#__ScopeOfWorkName" name="ScopeOfWork[#].ScopeOfWorkName" value="">
                <span class="field-validation-valid text-danger" data-valmsg-for="ScopeOfWork[#].ScopeOfWorkName" data-valmsg-replace="true"></span>
            </div>
        </div>
        <input type="hidden" class="scopeindex" name="ScopeOfWork.Index" value="#" />
        <div class="materials">
            <h4>Material</h4>
            <a href="javascript:void(0)" class="addmaterial">Add Material</a>
        </div>
        <hr />
    </div>
</div>

<div id="newMaterial" style="display:none">
    <div class="form-group">
        <label for="_#__Materials_%__MaterialName" class="control-label col-md-2">Material</label>
        <div class="col-md-2">
            <input class="form-control" type="text" id="_#__Materials_%__MaterialName" name="ScopeOfWork[#].Materials[%].MaterialName" value="">
            <span class="field-validation-valid text-danger" data-valmsg-for="ScopeOfWork[#].Materials[%].MaterialName" data-valmsg-replace="true"></span>
        </div>
    </div>

    <div class="form-group">
        <label for="_#__Materials_%__Quantity" class="control-label col-md-2">Quantity</label>
        <div class="col-md-1">
            <input class="form-control" type="text" id="_#__Materials_%__Quantity" name="ScopeOfWork[#].Materials[%].Quantity" value="">
            <span class="field-validation-valid text-danger" data-valmsg-for="ScopeOfWork[#].Materials[%].Quantity" data-valmsg-replace="true"></span>
        </div>
    </div>

    <div class="form-group">
        <label for="_#__Materials_%__Cost" class="control-label col-md-2">Cost</label>
        <div class="col-md-1">
            <input class="form-control" type="text" id="_#__Materials_%__Cost" name="ScopeOfWork[#].Materials[%].Cost" value="">
            <span class="field-validation-valid text-danger" data-valmsg-for="ScopeOfWork[#].Materials[%].Cost" data-valmsg-replace="true"></span>
        </div>
    </div>
    @*Drop down*@
    <div class="form-group">
        <label for="_#__Materials_%__SelectedCategory" class="control-label col-md-2">Category</label>
        <div class="col-xs-2">
            <select class="form-control category" id="_#__Materials_%__SelectedCategory" name="ScopeOfWork[#].Materials[%].SelectedCategory">
                <option value="">--Select--</option>
            </select>
        </div>
    </div>

    <input type="hidden" class="materialindex" name="ScopeOfWork[#].Materials.Index" value="%" />
</div>

<script>
    var form = $('form');
    var scope = $('#newScope');
    var material = $('#newMaterial');
    var categories = @Html.Raw(Json.Encode(Model.CategoryList));

    form.on('click', '.addmaterial', function () {
        var clone = material.clone();
        var scopeIndex = $(this).closest('.scope').find('.scopeindex').val();
        clone.html($(clone).html().replace(/#/g, scopeIndex));

        var materialIndex = new Date().getTime();
        clone.html($(clone).html().replace(/%/g, materialIndex));
        // drop down list
        var select = clone.find('.category');
        $.each(categories, function(index, item) {
            select.append($('<option></option>').val(item.Value).text(item.Text));
        });
        $(this).closest('.materials').append(clone.html());

        form.data('validator', null);
        $.validator.unobtrusive.parse(form);
    });

    $('#addScope').click(function () {
        var clone = scope.clone();
        var scopeIndex = new Date().getTime();
        clone.html($(clone).html().replace(/#/g, scopeIndex));
        $('#scopes').append(clone.html());
        form.data('validator', null);
        $.validator.unobtrusive.parse(form);
    });
</script>

Controller:

 [HttpPost]
        public ActionResult _CreateProject(ProjectViewModel project)
        {
            tblProject projectModel = new tblProject();
            tblScopeOfWork scopeModel = new tblScopeOfWork();
            tblMaterial materialModel = new tblMaterial();

            if (ModelState.IsValid)
            {
                projectModel.ProjectName = project.ProjectName;
                projectModel.ProjectLocation = project.ProjectLocation;
                projectModel.ProjectDescription = project.ProjectDescription;
                projectModel.WorkArea = project.WorkArea;
                projectModel.ModeOfPayment = project.ModeOfPayment;
                projectModel.Duration = project.Duration;
                projectModel.StartDate = project.StartDate;
                projectModel.EndDate = project.EndDate;
                projectModel.ProfitSupervision = project.ProfitSupervision;
                projectModel.ProjectStatus = project.ProjectStatus;
                projectModel.ForemanId = project.ForemanId;
                projectModel.ClientId = project.ClientId;

                db.tblProjects.Add(projectModel);
                db.SaveChanges();

                //Get the recently created ProjectId
                var recentProjectId = db.tblProjects.OrderByDescending(x => x.ProjectId).FirstOrDefault().ProjectId;

                //Get all values from List of ScopeOfWork
                //Add each ScopeOfWork to the database
                for (int scopeIndex = 0; scopeIndex < project.ScopeOfWork.Count; scopeIndex++)
                {
                    scopeModel = new tblScopeOfWork();
                    scopeModel.ScopeOfWork = project.ScopeOfWork[scopeIndex].ScopeOfWorkName;
                    scopeModel.ProjectId = recentProjectId;

                    db.tblScopeOfWorks.Add(scopeModel);
                    db.SaveChanges();

                    //Get the recently created ScopeOfWorkId
                    var recentScopeOfWorkId = db.tblScopeOfWorks.OrderByDescending(x => x.ScopeOfWorkId).FirstOrDefault().ScopeOfWorkId;

                    //Get all materials from its corresponding ScopeOfWork and save to database
                    for (int materialIndex = 0; materialIndex < project.ScopeOfWork[scopeIndex].Materials.Count; materialIndex++)
                    {
                        materialModel = new tblMaterial();
                        materialModel.Description = project.ScopeOfWork[scopeIndex].Materials[materialIndex].MaterialName;
                        materialModel.Quantity = project.ScopeOfWork[scopeIndex].Materials[materialIndex].Quantity;
                        materialModel.Cost = project.ScopeOfWork[scopeIndex].Materials[materialIndex].Cost;
                        materialModel.ScopeOfWorkId = recentScopeOfWorkId;
                        materialModel.CategoryId = project.ScopeOfWork[scopeIndex].Materials[materialIndex].SelectedCategory;
                        db.tblMaterials.Add(materialModel);
                    }
                }
                db.SaveChanges();
            }

            project.ScopeOfWork = new List<ScopeOfWorkViewModel>
            {
                new ScopeOfWorkViewModel()
                {
                    Materials = new List<MaterialsViewModel>
                    {
                        new MaterialsViewModel()
                        {
                            CategoryList = new SelectList(db.tblCategories, "CategoryId", "CategoryName")
                        }
                    }
                }
            };
            return View(project);
        }
    }

The problem is I tried to put some string ('ss') on the textbox of quantity and cost to see if the validation will work and this appears The value 'ss' is not valid for Quantity and The value 'ss is not valid for Cost.but it should be Please enter proper value. And also whenever the validation occurs, the Add Material link cannot add another newMaterial.

I have tried and check these answer and also DotNetFiddle but still it has some errors.

progammer101
  • 47
  • 1
  • 8

1 Answers1

1

You do not need your regex (its an int so it can only accept a number anyway). And ditto for the cost property (but why have you made it decimal if you don't allow fractions?).

The validation for the type will be performed first, and because its invalid, no further validation is performed.

Change the attribute to to a [Required]

[Range(typeof(int), "0", "999")]
[Required(ErrorMessage = "Please enter proper value")]
public int? Quantity { get; set; }

Refer also this question/answer for changing the default error message for an invalid value for a type.

As a side note, your dynamically added items will not give client side validation because you have not added the necessary data-val-* attributes. You need to inspect the html generated for the elements in the for loops and copy the html exactly, except replacing the collection indexers.

You also have an issue with your POST method because you set the ScopeOfWork property to a new collection, and wipe out all the data the user has entered before you return the view. The basic structure of your POST method should be

[HttpPost]
public ActionResult _CreateProject(ProjectViewModel project)
{
    if (!ModelState.IsValid)
    {
        ConfigureViewModel(project);
        return View(project);
    }
    // code to initialize your data models, save and redirect
}

private void ConfigureViewModel(ProjectViewModel model)
{
    var categories = db.tblCategories;
    model.CategoryList = new SelectList(categories , "CategoryId", "CategoryName");
    foreach (var scope in model.ScopeOfWork)
    {
        foreach (var material in scope.Materials)
        {
            material.CategoryList = new SelectList(categories , "CategoryId", "CategoryName");
        }
    }
}

Note the ConfigureviewModel() method is also called from your GET method to populate the SelectLists

  • Oh I see, but still when I click the save button, it will return my view but once my view is returned I wasn't able to add a new material anymore but I can add a scope. What causes this? – progammer101 Jun 16 '17 at 07:53
  • Give me a moment to study your code in more detail. I'll also add a link showing how to change the default messages for a type. –  Jun 16 '17 at 07:55
  • I also tried to create 3 Add Materials element with no input just to check the validation and post it, once the view is return only 1 Add material element is shown and not 3. – progammer101 Jun 16 '17 at 07:56
  • Sorry, which is the problem - not being able to add a new `Material` or the fact that only one is shown when you return the view? –  Jun 16 '17 at 08:00
  • Both actually. I tried to Add 3 `Materials` with no input. 1 only is shown after the view is return and I can't add a new `Material`. – progammer101 Jun 16 '17 at 08:02
  • But are you getting 3 in the POST method? –  Jun 16 '17 at 08:04
  • If I add correct values, yes I get 3 in my controller. – progammer101 Jun 16 '17 at 08:10
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/146836/discussion-between-stephen-muecke-and-progammer101). –  Jun 16 '17 at 08:10