ASP.NET MVC:如何使用 Html.ValidationSummary 向最终用户解释无效类型违规?

发布于 2024-08-30 03:47:18 字数 1636 浏览 1 评论 0原文

这里有严重的 n00b 警告;请手下留情!

所以我完成了 书呆子晚餐 MVC 教程,现在我正在转换的过程中使用 Nerd Dining 程序作为一种粗略模板的 ASP.NET MVC 的 VB.NET 应用程序。

我使用“IsValid / GetRuleViolations()”模式来识别无效的用户输入或违反业务规则的值。我正在使用 LINQ to SQL,并利用“OnValidate()”挂钩,该挂钩允许我运行验证并在尝试通过 CustomerRepository 类将更改保存到数据库时引发应用程序异常。

无论如何,一切都运行良好,除了当表单值到达我的验证方法时,无效类型已经转换为默认值或现有值。 (我有一个“StreetNumber”属性,它是一个整数,尽管我想这对于 DateTime 或任何其他非字符串来说也是一个问题。)

现在,我猜测 UpdateModel() 方法会抛出异常,然后更改值,因为 Html.ValidationMessage 显示在 StreetNumber 字段旁边,但我的验证方法永远看不到原始输入。这有两个问题:

  1. 虽然 Html.ValidationMessage 确实发出信号表明出现了问题,但 Html.ValidationSummary 中没有相应的条目。如果我什至可以让异常消息显示在那里,指示无效的转换或某些总比没有好。

  2. 驻留在我的客户部分类中的验证方法永远不会看到原始用户输入,因此我不知道问题是否是缺少条目或无效类型。我不知道如何才能将我的验证逻辑保持在一个地方整洁,并且仍然可以访问表单值。

我当然可以在视图中编写一些处理用户输入的逻辑,但这似乎与我应该使用 MVC 执行的操作完全相反。

我是否需要新的验证模式,或者是否有某种方法将原始表单值传递到我的模型类进行处理?


客户控制器代码

    // POST: /Customers/Edit/[id]
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Edit(int id, FormCollection formValues)
    {
        Customer customer = customerRepository.GetCustomer(id);

        try
        {
            UpdateModel(customer);

            customerRepository.Save();

            return RedirectToAction("Details", new { id = customer.AccountID });
        }
        catch
        {
            foreach (var issue in customer.GetRuleViolations())
                ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
        }
        return View(customer);
    }

Serious n00b warning here; please take mercy!

So I finished the Nerd Dinner MVC Tutorial and I'm now in the process of converting a VB.NET application to ASP.NET MVC using the Nerd Dinner program as a sort of rough template.

I am using the "IsValid / GetRuleViolations()" pattern to identify invalid user input or values that violate business rules. I am using LINQ to SQL and am taking advantage of the "OnValidate()" hook that allows me to run the validation and throw an application exception upon trying to save changes to the database via the CustomerRepository class.

Anyway, everything works well, except that by the time the form values reach my validation method invalid types have already been converted to a default or existing value. (I have a "StreetNumber" property that is an integer, though I imagine this would be a problem for DateTime or any other non-strings as well.)

Now, I am guessing that the UpdateModel() method throws an exception and then alters the value because the Html.ValidationMessage is displayed next to the StreetNumber field but my validation method never sees the original input. There are two problems with this:

  1. While the Html.ValidationMessage does signal that something is wrong, there is no corresponding entry in the Html.ValidationSummary. If I could even get the exception message to show up there indicating an invalid cast or something that would be better than nothing.

  2. My validation method which resides in my Customer partial class never sees the original user input so I do not know if the problem is a missing entry or an invalid type. I can't figure out how I can keep my validation logic nice and neat in one place and still get access to the form values.

I could of course write some logic in the View that processes the user input, however that seems like the exact opposite of what I should be doing with MVC.

Do I need a new validation pattern or is there some way to pass the original form values to my model class for processing?


CustomerController Code

    // POST: /Customers/Edit/[id]
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Edit(int id, FormCollection formValues)
    {
        Customer customer = customerRepository.GetCustomer(id);

        try
        {
            UpdateModel(customer);

            customerRepository.Save();

            return RedirectToAction("Details", new { id = customer.AccountID });
        }
        catch
        {
            foreach (var issue in customer.GetRuleViolations())
                ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
        }
        return View(customer);
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

与君绝 2024-09-06 03:47:18

这将是一个很好的起点: Scott Gu - ASP.NET MVC 2:模型验证

但我建议将数据绑定到域对象是不好的做法。

我个人的方法是使用 POCO 演示模型,上面撒有 DataAnnotations 验证属性 进行验证(这使得以后可以轻松连接客户端验证在)。您还可以创建自己的 ValidationAttributes 来连接数据绑定验证。

如果它在数据绑定到 POCO 对象后有效,请向其传递一个服务来执行更复杂的业务验证。验证通过后,将其传递给另一个服务(或同一服务),该服务将值传输到域对象,然后保存它。

我不熟悉 Linq to SQL GetRuleViolations 模式,因此请随意将我的步骤之一替换为适合的模式。

我会尽力在这里解释一下。

POCO 表示模型:

public class EditCustomerForm
{
    [DisplayName("First name")]
    [Required(ErrorMessage = "First name is required")]
    [StringLength(60, ErrorMessage = "First name cannot exceed 60 characters.")]
    public string FirstName { get; set; }

    [DisplayName("Last name")]
    [Required(ErrorMessage = "Last name is required")]
    [StringLength(60, ErrorMessage = "Last name cannot exceed 60 characters.")]
    public string LastName { get; set; }

    [Required(ErrorMessage = "Email is required")]
    [RegularExpression(@"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}" +
                       @"\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\" +
                       @".)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$",
                       ErrorMessage = "Email appears to be invalid.")]
    public string Email { get; set; }
}

控制器逻辑

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, EditCustomerForm editCustomerForm)
{
    var editCustomerForm = CustomerService.GetEditCustomerForm(id);

    return View(editCustomerForm);
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, EditCustomerForm editCustomerForm)
{
    try
    {
        if (Page.IsValid)
        {
            //Complex business validation that validation attributes can't handle
            //If there is an error, I get this method to throw an exception that has
            //the errors in it in the form of a IDictionary<string, string>
            CustomerService.ValidateEditCustomerForm(editCustomerForm, id);

            //If the above method hasn't thrown an exception, we can save it
            //In this method you should map the editCustomerForm back to your Cusomter domain model
            CustomerService.SaveCustomer(editCustomerForm, id)

            //Now we can redirect
            return RedirectToAction("Details", new { id = customer.AccountID });
    }
    //ServiceLayerException is a custom exception thrown by
    //CustomerService.ValidateEditCusotmerForm or possibly .SaveCustomer
    catch (ServiceLayerException ex)
    {
        foreach (var kvp in ex.Errors)
            ModelState.AddModelError(kvp.Key, kvp.Value);
    }
    catch (Exception ex) //General catch
    {
        ModelState.AddModelError("*", "There was an error trying to save the customer, please try again.");
    }

    return View(editCustomerForm);
}

清晰如泥?如果您需要更多说明,请告诉我:-)

再说一次,这只是我的方法。您可以先关注我首先链接到的 Scott Gu 的文章,然后从那里开始。

HTH,
查尔斯

This would be a good starting point: Scott Gu - ASP.NET MVC 2: Model Validation

But I would suggest that it's bad practice to databind stuff to your domain objects.

My personal approach would be to use POCO presentation models sprinkled with DataAnnotations validation attributes to do validation (this makes it easy to hook up client side validation later on). You can also create you own ValidationAttributes to hook into the databinding validation.

If it's valid after databinding to your POCO object, pass it a service that does your more complex business validation. After that validation passes, pass it to another service (or the same service) that transfers the values to your domain object and then saves it.

I'm not familiar with the Linq to SQL GetRuleViolations pattern, so feel free to replace one of my steps with that pattern where it fits.

I'll try my best to explain it here.

POCO presentation model:

public class EditCustomerForm
{
    [DisplayName("First name")]
    [Required(ErrorMessage = "First name is required")]
    [StringLength(60, ErrorMessage = "First name cannot exceed 60 characters.")]
    public string FirstName { get; set; }

    [DisplayName("Last name")]
    [Required(ErrorMessage = "Last name is required")]
    [StringLength(60, ErrorMessage = "Last name cannot exceed 60 characters.")]
    public string LastName { get; set; }

    [Required(ErrorMessage = "Email is required")]
    [RegularExpression(@"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}" +
                       @"\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\" +
                       @".)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$",
                       ErrorMessage = "Email appears to be invalid.")]
    public string Email { get; set; }
}

Controller logic

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, EditCustomerForm editCustomerForm)
{
    var editCustomerForm = CustomerService.GetEditCustomerForm(id);

    return View(editCustomerForm);
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, EditCustomerForm editCustomerForm)
{
    try
    {
        if (Page.IsValid)
        {
            //Complex business validation that validation attributes can't handle
            //If there is an error, I get this method to throw an exception that has
            //the errors in it in the form of a IDictionary<string, string>
            CustomerService.ValidateEditCustomerForm(editCustomerForm, id);

            //If the above method hasn't thrown an exception, we can save it
            //In this method you should map the editCustomerForm back to your Cusomter domain model
            CustomerService.SaveCustomer(editCustomerForm, id)

            //Now we can redirect
            return RedirectToAction("Details", new { id = customer.AccountID });
    }
    //ServiceLayerException is a custom exception thrown by
    //CustomerService.ValidateEditCusotmerForm or possibly .SaveCustomer
    catch (ServiceLayerException ex)
    {
        foreach (var kvp in ex.Errors)
            ModelState.AddModelError(kvp.Key, kvp.Value);
    }
    catch (Exception ex) //General catch
    {
        ModelState.AddModelError("*", "There was an error trying to save the customer, please try again.");
    }

    return View(editCustomerForm);
}

Clear as mud? If you need more clarification, just let me know :-)

Again, this is just my approach. You could possibly just follow the article by Scott Gu I linked to first and then go from there.

HTHs,
Charles

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文