有没有办法强制在 MVC3 控制器中重新验证模型?
我有一个模型,其中包含两次地址和人员,一次用于“主要”联系人,一次用于“发票”联系人,以及一个名为 InvoiceContactSameAsMain 的布尔值 - 一个笨拙的名称,但具有描述性。该属性的 getter 检查“main”和“invoice”的 Address 和 Contact 对象是否相同,如果相同则返回 true。 setter 检查该值是否为 true,如果是,则将主 Person 复制到发票 Person 上,并将主 Address 复制到发票 Address 上。
在我的视图中,布尔值由复选框表示(如您所期望的)。附加到此的是一个小的 JS 函数,如果选中该复选框,则隐藏发票字段并通过将 data-val HTML 属性设置为 false 并强制重新解析不引人注目的内容来“关闭”客户端验证表单中的验证属性。取消选中该框自然会显示字段并重新打开验证。
所有这些工作正常,直到我到达我的控制器。
尽管模型是“有效的”并且包含正确的字段(感谢我的 InvoiceContactSameAsMain 设置器),但 ModelState.IsValid 仍然是绝对错误的,而且我似乎找不到任何方法来重新验证模型。如果我清除 ModelState,所有错误都会消失。我非常愿意避免按名称挖掘 ModelState 中的字段,因为 Person 和 Address 对象在整个项目中使用,并且可能需要在某些时候进行更改或扩展。
我在这里错过了一些明显的东西,可以让我重新验证 ModelState 吗?我尝试过 TryUpdateModel 和 TryValidateModel,但它们似乎都使用缓存的 ModelState 值。我什至尝试再次递归调用我的操作,传递“固定”模型。我几乎庆幸的是,其中一个没有起作用。
如果有更多细节或示例有帮助,请告诉我。
编辑:显然,如果这是解决问题的完全错误的方法,请告诉我。
编辑 2:根据 Ron Sijm 的建议添加代码示例。
模型如下: 公开课详情 { 公共整数?用户ID { 获取;放; 这是包装在 ViewModel 中,
public Company Company { get; set; }
public Address CompanyAddress { get; set; }
public Person MainPerson { get; set; }
public Address InvoiceAddress { get; set; }
public Person InvoiceContact { get; set; }
[Display(Name = "Promotional code")]
[StringLength(20, ErrorMessage = "Promotional code should not exceed 20 characters")]
public string PromotionalCode { get; set; }
[Display(Name = "Invoice contact same as main")]
public bool InvoiceContactSameasMain
{
get { return InvoiceContact.Equals(MainPerson); }
set
{
if (value)
{
InvoiceContact = MainPerson.Copy();
InvoiceAddress = CompanyAddress.Copy();
}
}
}
[_Common.MustAccept]
[Display(Name = "I agree with the Privacy Policy")]
public bool PrivacyFlag { get; set; }
[Display(Name = "Please subscribe to Sodexo News Letter")]
public bool MarketingOption { get; set; }
[Display(Name = "Contract number")]
public int? ContractNumber { get; set; }
public Details()
{
Company = new Company();
CompanyAddress = new Address();
MainPerson = new Person();
InvoiceAddress = new Address();
InvoiceContact = new Person();
}
}
因为页面中涉及许多 SelectList:
public class DetailsViewModel
{
public Details Details { get; set; }
public SelectList MainContactTitles { get; set; }
public SelectList InvoiceContactTitles { get; set; }
public SelectList SICCodes { get; set; }
public SelectList TypesOfBusiness { get; set; }
public SelectList NumbersOfEmployees { get; set; }
public DetailsViewModel()
{
}
}
控制器的两个相关操作如下:
public class DetailsController : _ClientController
{
[Authorize]
public ActionResult Index()
{
DetailsViewModel viewModel = new DetailsViewModel();
if (Client == null)
{
viewModel.Details = DetailsFunctions.GetClient((int)UserId, null);
}
else
{
viewModel.Details = DetailsFunctions.GetClient((int)UserId, Client.ContractNumber);
}
viewModel.MainContactTitles = DetailsFunctions.GetTitles((int)UserId, viewModel.Details.MainPerson.title);
viewModel.InvoiceContactTitles = DetailsFunctions.GetTitles((int)UserId, viewModel.Details.InvoiceContact.title);
viewModel.SICCodes = DetailsFunctions.GetSICCodes(viewModel.Details.Company.sic_code);
viewModel.NumbersOfEmployees = DetailsFunctions.GetNumbersOfEmployees(viewModel.Details.Company.number_of_employees);
viewModel.TypesOfBusiness = DetailsFunctions.GetTypesOfBusiness(viewModel.Details.Company.public_private);
return View(viewModel);
}
[Authorize]
[HttpPost]
public ActionResult Index(DetailsViewModel ViewModel)
{
if (ModelState.IsValid)
{
//go to main page for now
DetailsFunctions.SetClient((int)UserId, ViewModel.Details);
return RedirectToAction("Index", "Home");
}
else
{
ViewModel.MainContactTitles = DetailsFunctions.GetTitles((int)UserId, ViewModel.Details.MainPerson.title);
ViewModel.InvoiceContactTitles = DetailsFunctions.GetTitles((int)UserId, ViewModel.Details.InvoiceContact.title);
ViewModel.SICCodes = DetailsFunctions.GetSICCodes(ViewModel.Details.Company.sic_code);
ViewModel.NumbersOfEmployees = DetailsFunctions.GetNumbersOfEmployees(ViewModel.Details.Company.number_of_employees);
ViewModel.TypesOfBusiness = DetailsFunctions.GetTypesOfBusiness(ViewModel.Details.Company.public_private);
return View(ViewModel);
}
}
}
如果需要,我可以提供视图和 JS,但由于模型绑定都工作得很好,我不确定这有多大帮助。
I have a Model which contains an Address and Person twice, once for the "main" contact, and once for the "invoice" contact, and a boolean value called InvoiceContactSameAsMain - a clumsy name, but descriptive. The getter of the property checks to see if the Address and Contact objects for "main" and "invoice" are the same, and returns true if they are. The setter checks to see if the value is true, and if so, copies the main Person over the invoice Person , and the main Address over the invoice Address.
In my View, the boolean value is represented by a check box (as you'd expect). Attached to this is a small JS function which, if the check box is checked, hides the invoice fields and "switches off" the client-side validation by setting the data-val HTML attribute to false and forcing a re-parse of the unobtrusive validation attributes in the form. Un-checking the box naturally shows the fields and turns the validation back on.
All of this works fine, until I get to my Controller.
Despite the Model being "valid" and containing the correct fields (thanks to my InvoiceContactSameAsMain setter), ModelState.IsValid remains resolutely false, and I can't seem to find any way to revalidate the model. If I clear the ModelState, any and all errors disappear. I'd very much rather avoid digging through the fields in the ModelState by name, as the Person and Address objects are used throughout the project and may need to change or be extended at some point.
Is there something obvious I've missed here that will allow me to revalidate the ModelState? I've tried TryUpdateModel and TryValidateModel, but they both appear to use the cached ModelState values. I've even tried recursively calling my Action again, passing in the "fixed" model. I'm almost thankful that one didn't work.
Please let me know if any more detail or examples will help.
Edit: Obviously, if this is completely the wrong way to approach the problem, just let me know.
Edit 2: Added code samples as per Ron Sijm's suggestion.
The model is as follows:
public class Details
{
public int? UserID { get; set; }
public Company Company { get; set; }
public Address CompanyAddress { get; set; }
public Person MainPerson { get; set; }
public Address InvoiceAddress { get; set; }
public Person InvoiceContact { get; set; }
[Display(Name = "Promotional code")]
[StringLength(20, ErrorMessage = "Promotional code should not exceed 20 characters")]
public string PromotionalCode { get; set; }
[Display(Name = "Invoice contact same as main")]
public bool InvoiceContactSameasMain
{
get { return InvoiceContact.Equals(MainPerson); }
set
{
if (value)
{
InvoiceContact = MainPerson.Copy();
InvoiceAddress = CompanyAddress.Copy();
}
}
}
[_Common.MustAccept]
[Display(Name = "I agree with the Privacy Policy")]
public bool PrivacyFlag { get; set; }
[Display(Name = "Please subscribe to Sodexo News Letter")]
public bool MarketingOption { get; set; }
[Display(Name = "Contract number")]
public int? ContractNumber { get; set; }
public Details()
{
Company = new Company();
CompanyAddress = new Address();
MainPerson = new Person();
InvoiceAddress = new Address();
InvoiceContact = new Person();
}
}
This is wrapped in a ViewModel as there are a number of SelectLists involved in the page:
public class DetailsViewModel
{
public Details Details { get; set; }
public SelectList MainContactTitles { get; set; }
public SelectList InvoiceContactTitles { get; set; }
public SelectList SICCodes { get; set; }
public SelectList TypesOfBusiness { get; set; }
public SelectList NumbersOfEmployees { get; set; }
public DetailsViewModel()
{
}
}
The Controller's two relevant actions are as follows:
public class DetailsController : _ClientController
{
[Authorize]
public ActionResult Index()
{
DetailsViewModel viewModel = new DetailsViewModel();
if (Client == null)
{
viewModel.Details = DetailsFunctions.GetClient((int)UserId, null);
}
else
{
viewModel.Details = DetailsFunctions.GetClient((int)UserId, Client.ContractNumber);
}
viewModel.MainContactTitles = DetailsFunctions.GetTitles((int)UserId, viewModel.Details.MainPerson.title);
viewModel.InvoiceContactTitles = DetailsFunctions.GetTitles((int)UserId, viewModel.Details.InvoiceContact.title);
viewModel.SICCodes = DetailsFunctions.GetSICCodes(viewModel.Details.Company.sic_code);
viewModel.NumbersOfEmployees = DetailsFunctions.GetNumbersOfEmployees(viewModel.Details.Company.number_of_employees);
viewModel.TypesOfBusiness = DetailsFunctions.GetTypesOfBusiness(viewModel.Details.Company.public_private);
return View(viewModel);
}
[Authorize]
[HttpPost]
public ActionResult Index(DetailsViewModel ViewModel)
{
if (ModelState.IsValid)
{
//go to main page for now
DetailsFunctions.SetClient((int)UserId, ViewModel.Details);
return RedirectToAction("Index", "Home");
}
else
{
ViewModel.MainContactTitles = DetailsFunctions.GetTitles((int)UserId, ViewModel.Details.MainPerson.title);
ViewModel.InvoiceContactTitles = DetailsFunctions.GetTitles((int)UserId, ViewModel.Details.InvoiceContact.title);
ViewModel.SICCodes = DetailsFunctions.GetSICCodes(ViewModel.Details.Company.sic_code);
ViewModel.NumbersOfEmployees = DetailsFunctions.GetNumbersOfEmployees(ViewModel.Details.Company.number_of_employees);
ViewModel.TypesOfBusiness = DetailsFunctions.GetTypesOfBusiness(ViewModel.Details.Company.public_private);
return View(ViewModel);
}
}
}
I can provide the view and JS if needs be, but as the Model binding is all working just fine, I'm not sure how much help that is.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
这是一个相当糟糕的黑客,但我最终只是在检查 ModelState.IsValid 之前清除了控制器中相关字段的 ModelState 错误:
唯一的好处是,如果 Person 或 Address 对象发生更改,则此代码不需要更改被改变。
It's a moderately crap hack, but I've ended up just clearing the ModelState errors for the relevant fields in the controller before checking ModelState.IsValid:
The only upside is, if the Person or Address objects change, this code won't need to be altered.