MVC3主详细验证不显示 [英] MVC3 Master-Details Validation not Displaying

查看:226
本文介绍了MVC3主详细验证不显示的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个MVC3页面与对象(头),有数据,我想在一个页面上更新对象(详细信息)的列表。在细节上反对我有自定义的验证(IValidatableObject)也需要运行。

这似乎一般都可以按预期工作,验证正在运行和返回ValidationResults,如果我把一个@ Html.ValidationSummary(假);在页面上会显示这些验证。不过,我不希望在顶部验证的列表,而是旁边的项目被验证,即Html.ValidationMessageFor这是在页面上,但不显示相关的提示信息。有我丢失的东西?这是在其他网页上(即没有这个主详细情况),所以我想这是一些关于我如何去有关设置进行更新的项目列表,或编辑模板的项目?

Edit.cshtml(页眉详细编辑视图)

  @foreach(在Model.Details.OrderBy(D变种D => d.DetailId))
{
   @ Html.EditorFor(项目=> D,详细信息)
}

Detail.ascx(详细信息编辑模板)

 <%@控制语言=C#继承=System.Web.Mvc.ViewUserControl<详情>中%GT;&所述; TR>
    &所述; TD>
        &所述;%= Model.Name%GT;
        <%= Html.HiddenFor(型号=> model.DetailId)%GT;
    < / TD>
    < TD类=colDescription>
        <%= Html.EditorFor(型号=> model.Description)%GT;
        <%= Html.ValidationMessageFor(型号=> model.Description)%GT;
    < / TD>
    < TD类=colAmount>
        <%= Html.EditorFor(型号=> model.Amount)%GT;
        <%= Html.ValidationMessageFor(型号=> model.Amount)%GT;
    < / TD>
< / TR>

模型实体框架与具有名称和HeaderId与标题细节都DetailId,HeaderId,说明和金额

控制器code:

 公众的ActionResult编辑(头球冲顶,的FormCollection的FormCollection)
{
   如果(的FormCollection [saveButton]!= NULL)
   {
      标题= this.ProcessFormCollectionHeader(头,的FormCollection);
      如果(ModelState.IsValid)
      {
         返回新RedirectResult(〜/ saveNotification);
      }
      其他
      {
         返回视图(编辑,标题);
      }
   }
   其他
   {
      返回视图(编辑,标题);
   }
}

[我知道控制器code可以作为尝试确定这里存在的一个结果被清理了一下,就在这种状态]

IValidatableObject实现:

 公开的IEnumerable<&为ValidationResult GT;验证(ValidationContext validationContext)
{
   如果(this.Name.Length小于5)及与放大器; (this.Amount→10))
   {
      产量返回新的ValidationResult(项目必须有合理的名字有金额比10大,新的[] {金额});
   }
}


解决方案

我会建议你使用真正的编辑模板。与code的问题是,你写你的视图内的foreach循环来呈现它产生相应的输入字段错误名称的模板。我想这就是为什么你在做你的控制器动作一些解决方法来填充模型的原因(头= this.ProcessFormCollectionHeader(头,的FormCollection); ),而不是简单地使用模型绑定来完成这项工作。

因此​​,让我来告诉你正确的方式来实现这一点。

型号:

 公共类头
{
    公共IEnumerable的<详情>详细信息{搞定;组; }
}公共类详细信息:IValidatableObject
{
    公众诠释DetailId {搞定;组; }
    公共字符串名称{;组; }
    公共字符串描述{搞定;组; }
    公众诠释金额{搞定;组; }    公共IEnumerable的<&为ValidationResult GT;验证(ValidationContext validationContext)
    {
        如果((this.Name ??的S​​tring.Empty)。长度小于5&放大器;&放大器; this.Amount→10)
        {
            产量返回新的ValidationResult(
                项目必须有合理的名字有金额比10大
                新的[] {金额}
            );
        }
    }
}

控制器:

 公共类HomeController的:控制器
{
    公众的ActionResult指数()
    {
        VAR模型=新头
        {
            详情= Enumerable.Range(1,5)。选择(X =>新建详细
            {
                DetailId = X,
                NAME =N+ X,
                金额= 50
            })的OrderBy(D =方式> d.DetailId)
        };
        返回查看(模型);
    }    [HttpPost]
    公众的ActionResult指数(头型)
    {
        如果(ModelState.IsValid)
        {
            返回重定向(〜/ saveNotification);
        }
        返回查看(模型);
    }
}

查看(〜/查看/主页/ Index.cshtml

  @model头@using(Html.BeginForm())
{
    <表>
        <&THEAD GT;
            &所述; TR>
                <第i个姓名和LT; /第i
                <第i说明< /第i
                <第i度< /第i
            < / TR>
        < / THEAD>
        <&TBODY GT;
            @ Html.EditorFor(X => x.Details)
        < / TBODY>
    < /表>
    <按钮式=提交>确定< /按钮>
}

对于一些细节编辑器类型的模板(〜/查看/共享/ EditorTemplates / Detail.ascx 〜/查看/共享/ EditorTemplates /详细信息.cshtml 的剃刀):

 <%@控制
    语言=C#
    继承=System.Web.Mvc.ViewUserControl&所述; MvcApplication1.Controllers.Detail>中
%GT;&所述; TR>
    &所述; TD>
        <%= Html.DisplayFor(型号=> model.Name)%GT;
        <%= Html.HiddenFor(型号=> model.DetailId)%GT;
        <%= Html.HiddenFor(型号=> model.Name)%GT;
    < / TD>
    < TD类=colDescription>
        <%= Html.EditorFor(型号=> model.Description)%GT;
        <%= Html.ValidationMessageFor(型号=> model.Description)%GT;
    < / TD>
    < TD类=colAmount>
        <%= Html.EditorFor(型号=> model.Amount)%GT;
        <%= Html.ValidationMessageFor(型号=> model.Amount)%GT;
    < / TD>
< / TR>

下面是两件事情,我没有提高你的code:


  • 我在控制器级别进行的详细信息收集的顺序由DetailId。它是控制器的责任prepare显示视图模型。认为不应该做这种排序。所有这一切的观点应该做的是显示数据

  • 感谢previous改善我的git摆脱在您正在使用呈现模板编辑器视图的foreach循环,并用一个取代它 @ Html.EditorFor(X =&GT ; x.Details)电话。其工作原理是,ASP.NET MVC检测详细信息是类型的集合属性(的IEnumerable<详情> ),它会自动查找里面的〜/查看/ SomeController / EditorTemplates 〜/查看/共享/ EditorTemplates 文件夹名为 Detail.ascx Detail.cshtml (同​​名的集合类型)。然后,它会呈现此模板集合的每一个元素,让你不必担心它

  • 感谢previous改进,里面的 [HttpPost] 操作不再需要任何 ProcessFormCollectionHeader 黑客。在操作参数将正确地从请求数据由模型粘结剂粘结

  • 里面我已经取代&LT的 Detail.ascx 模板;%= Model.Name%GT; <%= Html.DisplayFor(型号=> model.Name)%GT; 您的网站上公开。

  • 在我确保了名称属性是不反对它的长度测试之前,空的验证方法。通过在你的榜样你的方式只有一个输入域模板里面的说明字段,并没有相应的输入字段名称属性,表单所以当提交此属性将始终为空。其结果是我添加了一个对应的隐藏的输入字段吧。

I have an MVC3 page with an object (Header) that has data and a list of objects (Details) that I want to update on a single page. On the details object I have custom validation (IValidatableObject) that also needs to run.

This appears to generally be working as expected, validations are running and returning ValidationResults and if I put an @Html.ValidationSummary(false); on the page it displays those validations. However I don't want a list of validations at the top, but rather next to the item being validated i.e. Html.ValidationMessageFor which is on the page, but not displaying the relevant message. Is there something I'm missing? This is working on other pages (that don't have this Master-Details situation), so i'm thinking it is something about how I'm going about setting up the list of items to be updated or the editor template for the item?

Edit.cshtml (the Header-Details edit view)

@foreach (var d in Model.Details.OrderBy(d => d.DetailId))
{
   @Html.EditorFor(item => d, "Detail")
}

Detail.ascx (the Details Editor Template)

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Detail>" %>

<tr>            
    <td>
        <%= Model.Name %>
        <%= Html.HiddenFor(model => model.DetailId) %>
    </td>
    <td class="colDescription">
        <%= Html.EditorFor(model => model.Description) %>
        <%= Html.ValidationMessageFor(model => model.Description) %>
    </td>
    <td class="colAmount">
        <%= Html.EditorFor(model => model.Amount) %>
        <%= Html.ValidationMessageFor(model => model.Amount) %>
    </td>
</tr>

Model is Entity Framework with Header that has Name and HeaderId and Detail has DetailId, HeaderId, Description and Amount

Controller Code:

public ActionResult Edit(Header header, FormCollection formCollection)
{
   if (formCollection["saveButton"] != null)
   {
      header = this.ProcessFormCollectionHeader(header, formCollection);
      if (ModelState.IsValid)
      {
         return new RedirectResult("~/saveNotification");
      }
      else
      {
         return View("Edit", header);
      }
   }
   else
   {
      return View("Edit", header);
   }
}

[I know controller code can be cleaned up a bit, just at this state as a result of trying to determine what is occuring here]

IValidatableObject implementation:

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
   if (this.Name.Length < 5) && (this.Amount > 10))
   {
      yield return new ValidationResult("Item must have sensible name to have Amount larger than 10.", new[] { "Amount" });
   }
}

解决方案

I would recommend you to use real editor templates. The problem with your code is that you are writing a foreach loop inside your view to render the template which generates wrong names for the corresponding input fields. I guess that's the reason why you are doing some workarounds in your controller action to populate the model (header = this.ProcessFormCollectionHeader(header, formCollection);) instead of simply using the model binder to do the job.

So let me show you the correct way to achieve that.

Model:

public class Header
{
    public IEnumerable<Detail> Details { get; set; }
}

public class Detail : IValidatableObject
{
    public int DetailId { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public int Amount { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if ((this.Name ?? string.Empty).Length < 5 && this.Amount > 10)
        {
            yield return new ValidationResult(
                "Item must have sensible name to have Amount larger than 10.", 
                new[] { "Amount" }
            );
        }
    }
}

Controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new Header
        {
            Details = Enumerable.Range(1, 5).Select(x => new Detail
            {
                DetailId = x,
                Name = "n" + x,
                Amount = 50
            }).OrderBy(d => d.DetailId)
        };
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(Header model)
    {
        if (ModelState.IsValid)
        {
            return Redirect("~/saveNotification");
        }
        return View(model);
    }
}

View (~/Views/Home/Index.cshtml):

@model Header

@using (Html.BeginForm())
{
    <table>
        <thead>
            <tr>
                <th>Name</th>
                <th>Description</th>
                <th>Amount</th>
            </tr>
        </thead>
        <tbody>
            @Html.EditorFor(x => x.Details)
        </tbody>
    </table>
    <button type="submit">OK</button>
}

Editor template for the Detail type (~/Views/Shared/EditorTemplates/Detail.ascx or ~/Views/Shared/EditorTemplates/Detail.cshtml for Razor):

<%@ Control 
    Language="C#" 
    Inherits="System.Web.Mvc.ViewUserControl<MvcApplication1.Controllers.Detail>" 
%>

<tr>            
    <td>
        <%= Html.DisplayFor(model => model.Name) %>
        <%= Html.HiddenFor(model => model.DetailId) %>
        <%= Html.HiddenFor(model => model.Name) %>
    </td>
    <td class="colDescription">
        <%= Html.EditorFor(model => model.Description) %>
        <%= Html.ValidationMessageFor(model => model.Description) %>
    </td>
    <td class="colAmount">
        <%= Html.EditorFor(model => model.Amount) %>
        <%= Html.ValidationMessageFor(model => model.Amount) %>
    </td>
</tr>

Here are a couple of things that I did to improve your code:

  • I performed the ordering of the Details collection by DetailId at the controller level. It's the controller's responsibility to prepare the view model for display. The view should not be doing this ordering. All that the view should do is display the data
  • Thanks to the previous improvement I git rid of the foreach loop in the view that you were using to render the editor template and replaced it with a single @Html.EditorFor(x => x.Details) call. The way this works is that ASP.NET MVC detects that Details is a collection property (of type IEnumerable<Detail>) and it will automatically look for a custom editor templated inside the ~/Views/SomeController/EditorTemplates or ~/Views/Shared/EditorTemplates folders called Detail.ascx or Detail.cshtml (same name as the type of the collection). It will then render this template for each element of the collection so that you don't need to worry about it
  • Thanks to the previous improvement, inside the [HttpPost] action you no longer need any ProcessFormCollectionHeader hacks. The header action argument will be correctly bound from the request data by the model binder
  • Inside the Detail.ascx template I have replaced <%= Model.Name %> with <%= Html.DisplayFor(model => model.Name) %> in order to properly HTML encode the output and fill the XSS hole that was open on your site.
  • Inside the Validate method I ensured that the Name property is not null before testing against its length. By the way in your example you only had an input field for the Description field inside the template and didn't have a corresponding input field for the Name property, so when the form is submitted this property will always be null. As a consequence I have added a corresponding hidden input field for it.

这篇关于MVC3主详细验证不显示的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆