ASP.NET MVC对类属性使用验证

时间:2022-08-02 16:39:00

I have a simple model in my ASP.NET MVC app that I wish to put validation on a property which is a class. I've looked at ScottGu's blog here on class-level validation but the validation occurs when SaveChanges() is called on the context, which I don't want. I'd like it to react like the validation you get with DataAnnotations where it happens upon form submission.

我在我的ASP.NET MVC应用程序中有一个简单的模型,我希望将验证放在属性类上。我在这里看过ScottGu的关于类级别验证的博客但是在上下文中调用SaveChanges()时会进行验证,这是我不想要的。我希望它能像你在DataAnnotations中获得的验证一样做出反应,它会在表单提交时发生。

Here's my model:

这是我的模特:

public class SignupModel
{
    [Display(Name = "Company Name")]
    [Required(ErrorMessage = "Company Name Required")]
    public string CompanyName { get; set; }

    [Display(Name = "Company Type")]
    [Required(ErrorMessage = "Company Type Required")]
    public int SelectedCompanyTypeId { get; set; }

    [Display(Name = "Requested Services")]
    [Required(ErrorMessage = "At least 1 service is required")]
    public PostedServicesOffered PostedServices { get; set; }

    public IEnumerable<ServicesOffered> ServicesOffered { get; set; }
    public IEnumerable<ServicesOffered> SelectedServicesOffered { get; set; }
    public IEnumerable<SelectListItem> CompanyTypes { get; set; }
}

The PostedServicesOffered object is a simple class that has one property:

PostedServicesOffered对象是一个具有一个属性的简单类:

public class PostedServicesOffered
{
    public string[] ServiceOfferedIds { get; set; }
}

Here's the Controller:

这是控制器:

    public ActionResult Index()
    {
        SignupModel model = new SignupModel()
        {
            CompanyTypes = SelectCompanyType(),
            ServicesOffered = GetServicesOffered(),
            SelectedServicesOffered = new List<ServicesOffered>(),
            PostedServices = new PostedServicesOffered()
        };

        ViewBag.BannerText = "Sign Up Form";

        return View(model);
    }

    [HttpPost]
    public ActionResult Index(SignupModel model)
    {
        if (ModelState.IsValid)
        {

        }

        return View(GetSignupModel(model));
    }

    private SignupModel GetSignupModel(SignupModel model)
    {
        SignupModel signupModel = new SignupModel();
        var selectedServices = new List<ServicesOffered>();
        var postedServicesOfferedIds = new string[0];

        if (model.PostedServices == null)
            model.PostedServices = new PostedServicesOffered();

        // if a view model array of posted services ids exists
        // and is not empty,save selected ids
        if (model.PostedServices.ServiceOfferedIds != null && model.PostedServices.ServiceOfferedIds.Any())
            postedServicesOfferedIds = model.PostedServices.ServiceOfferedIds;

        //If there are any selected ids saved, create a list of ServicesOffered.
        if (postedServicesOfferedIds.Any())
            selectedServices = GetServicesOffered()
                                .Where(x => postedServicesOfferedIds.Any(s => x.ServicesOfferedId.ToString().Equals(s)))
                                .OrderBy(x => x.ServiceName)
                                .ToList();

        signupModel.CompanyTypes = SelectCompanyType();
        signupModel.ServicesOffered = GetServicesOffered();
        signupModel.SelectedServicesOffered = selectedServices;
        signupModel.PostedServices = model.PostedServices;
        signupModel.SelectedCompanyTypeId = model.SelectedCompanyTypeId;

        return signupModel;
    }

    private IEnumerable<ServicesOffered> GetServicesOffered()
    {
        return Repository.GetServicesOffered(new UnitOfWork()).OrderBy(so => so.ServiceName);
    }

    private IEnumerable<SelectListItem> SelectCompanyType()
    {
        List<SelectListItem> items = new List<SelectListItem>();
        var companyTypes = Repository.GetCompanyTypes(new UnitOfWork()).OrderBy(ct => ct.CompanyType1);

        foreach (CompanyType companyType in companyTypes)
            items.Add(new SelectListItem()
            {
                Text = companyType.CompanyType1,
                Value = companyType.CompanyTypeId.ToString()
            });

        return items;
    }

and the View:

和观点:

<div id="contact-form" class="contatct-form">
    <h4 class="classic-title"><span>Registration</span></h4>
    @using (Html.BeginForm())
    {
        <div class="validation-text">
            <h5>@Html.ValidationSummary()</h5>
        </div>
        <div class="row padBtm-20">
            <div class="col-md-4">
                @Html.LabelFor(x => x.CompanyName)
                @Html.TextBoxFor(x => x.CompanyName, new { placeholder = "Company Name..." })
            </div>
            <div class="col-md-4">
                @Html.LabelFor(x => x.SelectedCompanyTypeId)
                @Html.DropDownListFor(x => x.SelectedCompanyTypeId, Model.CompanyTypes)
                @Html.HiddenFor(x => x.SelectedCompanyTypeId, new { value = Model.SelectedCompanyTypeId })
            </div>
            <div class="col-md-4">
                @Html.LabelFor(model => model.PostedServices)<br />
                @*@Html.CheckBoxListFor(model => model.PostedServices.ServiceOfferedIds,
                                        model => model.ServicesOffered,
                                        service => service.ServicesOfferedId,
                                        service => service.ServiceName,
                                        model => model.SelectedServicesOffered,
                                        Position.Vertical)*@
                @Html.CheckBoxListFor(model => model.PostedServices,
                                        model => model.ServicesOffered,
                                        service => service.ServicesOfferedId,
                                        service => service.ServiceName,
                                        model => model.SelectedServicesOffered,
                                        Position.Vertical)
            </div>
        </div>
        <div class="row padBtm-20"></div>                        
        <input type="submit" class="button" value="Send Message" />
    }

</div>

In short, the ServicesOffered property is a list of services that are displayed as CheckBoxes. I am using a NeGut package called MvcCheckBoxList (found here) which creates a set of checkboxes based on a list.

简而言之,ServicesOffered属性是显示为CheckBoxes的服务列表。我正在使用名为MvcCheckBoxList(在此处找到)的NeGut包,它根据列表创建一组复选框。

What's happening is when I click Submit with no checkboxes checked, the form validates the other properties first ans displays those error messages first. It's only after those fields pass validation and the form is posted, that the PostedServices field fails validation and shows the error. How can I get it to validate without posting like all the other properties?

发生的事情是当我单击提交而没有选中复选框时,表单首先验证其他属性,然后首先显示这些错误消息。只有在这些字段通过验证并且表单发布之后,才会发布PostedServices字段验证失败并显示错误。如何在不发布所有其他属性的情况下进行验证?

1 个解决方案

#1


0  

The problem is that you are encapsulating your string array inside of another class. Let me break down what's happening at validation, so you can understand why this isn't working.

问题是你将字符串数组封装在另一个类中。让我分解验证时发生的事情,这样你就可以理解为什么这不起作用了。

First, validation happens both client-side and server-side. The problem you seem to have is that everything else but this property is validated client-side. Then, once the form actually posts, this particular property fails server-side and causes the form to redisplay at that point. The client-side validation happens by adding data-* attributes to the form fields. These attributes in turn inform the client-side validation code what type of validation to run. The Required attribute obviously makes the client-side code force a value to be entered, but the problem is that your PostedServices property is not represented on your form anywhere. Instead, you have a field for PostedServices.ServiceOfferedIds which notably is not required.

首先,验证发生在客户端和服务器端。您似乎遇到的问题是除了此属性之外的所有其他内容都经过验证客户端。然后,一旦表单实际发布,此特定属性将在服务器端失败并导致表单在该点重新显示。通过向表单字段添加data- *属性来进行客户端验证。这些属性反过来通知客户端验证代码运行哪种类型的验证。 Required属性显然使客户端代码强制输入一个值,但问题是您的PostedServices属性没有在任何地方表示在您的表单上。相反,你有一个PostedServices.ServiceOfferedIds字段,特别是不需要。

Once the form is posted, the modelbinder news up an instance of SignupModel, mapping over the posted data. However, since nothing is being posted for ServiceOfferedIds, it has nothing with which to create an instance of PostedServicesOffered and therefore sets the PostedServices property to null. Then, the Required validation catches server-side.

一旦表单发布,模型绑定器就会新闻SignupModel的实例,映射已发布的数据。但是,由于没有为ServiceOfferedIds发布任何内容,因此它无需创建PostedServicesOffered的实例,因此将PostedServices属性设置为null。然后,必需的验证捕获服务器端。

I'm not sure why you chose to encapsulate this one property in another class, but if you change your PostedServices to:

我不确定为什么你选择将这个属性封装在另一个类中,但如果你将PostedServices更改为:

[Display(Name = "Requested Services")]
[Required(ErrorMessage = "At least 1 service is required")]
public string[] PostedServices { get; set; }

So that you're interacting with the string array directly, everything should work fine.

因此,您直接与字符串数组进行交互,一切都应该正常工作。

#1


0  

The problem is that you are encapsulating your string array inside of another class. Let me break down what's happening at validation, so you can understand why this isn't working.

问题是你将字符串数组封装在另一个类中。让我分解验证时发生的事情,这样你就可以理解为什么这不起作用了。

First, validation happens both client-side and server-side. The problem you seem to have is that everything else but this property is validated client-side. Then, once the form actually posts, this particular property fails server-side and causes the form to redisplay at that point. The client-side validation happens by adding data-* attributes to the form fields. These attributes in turn inform the client-side validation code what type of validation to run. The Required attribute obviously makes the client-side code force a value to be entered, but the problem is that your PostedServices property is not represented on your form anywhere. Instead, you have a field for PostedServices.ServiceOfferedIds which notably is not required.

首先,验证发生在客户端和服务器端。您似乎遇到的问题是除了此属性之外的所有其他内容都经过验证客户端。然后,一旦表单实际发布,此特定属性将在服务器端失败并导致表单在该点重新显示。通过向表单字段添加data- *属性来进行客户端验证。这些属性反过来通知客户端验证代码运行哪种类型的验证。 Required属性显然使客户端代码强制输入一个值,但问题是您的PostedServices属性没有在任何地方表示在您的表单上。相反,你有一个PostedServices.ServiceOfferedIds字段,特别是不需要。

Once the form is posted, the modelbinder news up an instance of SignupModel, mapping over the posted data. However, since nothing is being posted for ServiceOfferedIds, it has nothing with which to create an instance of PostedServicesOffered and therefore sets the PostedServices property to null. Then, the Required validation catches server-side.

一旦表单发布,模型绑定器就会新闻SignupModel的实例,映射已发布的数据。但是,由于没有为ServiceOfferedIds发布任何内容,因此它无需创建PostedServicesOffered的实例,因此将PostedServices属性设置为null。然后,必需的验证捕获服务器端。

I'm not sure why you chose to encapsulate this one property in another class, but if you change your PostedServices to:

我不确定为什么你选择将这个属性封装在另一个类中,但如果你将PostedServices更改为:

[Display(Name = "Requested Services")]
[Required(ErrorMessage = "At least 1 service is required")]
public string[] PostedServices { get; set; }

So that you're interacting with the string array directly, everything should work fine.

因此,您直接与字符串数组进行交互,一切都应该正常工作。