Я спросил о проблеме с цифрами здесь.
Учитывая некоторые ответы, я попытался попытаться реализовать свое собственное связующее устройство следующим образом:
namespace MvcApplication1.Core
{
public class PropertyModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object objectModel = new object();
if (bindingContext.ModelType == typeof(PropertyModel))
{
HttpRequestBase request = controllerContext.HttpContext.Request;
string price = request.Form.Get("Price").Replace(",", string.Empty);
ModelBindingContext newBindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(
() => new PropertyModel()
{
Price = Convert.ToInt32(price)
},
typeof(PropertyModel)
),
ModelState = bindingContext.ModelState,
ValueProvider = bindingContext.ValueProvider
};
// call the default model binder this new binding context
return base.BindModel(controllerContext, newBindingContext);
}
else
{
return base.BindModel(controllerContext, bindingContext);
}
}
//protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
//{
// //return base.CreateModel(controllerContext, bindingContext, modelType);
// PropertyModel model = new PropertyModel();
// if (modelType == typeof(PropertyModel))
// {
// model = (PropertyModel)base.CreateModel(controllerContext, bindingContext, modelType);
// HttpRequestBase request = controllerContext.HttpContext.Request;
// string price = request.Form.Get("Price").Replace(",", string.Empty);
// model.Price = Convert.ToInt32(price);
// }
// return model;
//}
}
}
И обновил мой класс контроллера следующим образом:
namespace MvcApplication1.Controllers
{
public class PropertyController : Controller
{
public ActionResult Edit()
{
PropertyModel model = new PropertyModel
{
AgentName = "John Doe",
BuildingStyle = "Colonial",
BuiltYear = 1978,
Price = 650000,
Id = 1
};
return View(model);
}
[HttpPost]
public ActionResult Edit([ModelBinder(typeof(PropertyModelBinder))] PropertyModel model)
{
if (ModelState.IsValid)
{
//Save property info.
}
return View(model);
}
public ActionResult About()
{
ViewBag.Message = "Your app description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
}
Теперь, если я ввожу цену запятыми, мое пользовательское связующее устройство удалит запятые, что я хочу, но проверка еще не удалась. Итак, вопрос: Как выполнить выборочную проверку в моем настраиваемом соединителе модели, чтобы можно было избежать захваченного значения цены запятыми? Другими словами, я подозреваю, что мне нужно сделать больше в моем настраиваемом связующем устройстве, но не знаю, как и что. Спасибо.
Update:
Итак, я попробовал решение @mare на qaru.site/info/363752/... и обновил мое связующее устройство следующим образом:
namespace MvcApplication1.Core
{
public class PropertyModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object objectModel = new object();
if (bindingContext.ModelType == typeof(PropertyModel))
{
HttpRequestBase request = controllerContext.HttpContext.Request;
string price = request.Form.Get("Price").Replace(",", string.Empty);
ModelBindingContext newBindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(
() => new PropertyModel()
{
Price = Convert.ToInt32(price)
},
typeof(PropertyModel)
),
ModelState = bindingContext.ModelState,
ValueProvider = bindingContext.ValueProvider
};
// call the default model binder this new binding context
object o = base.BindModel(controllerContext, newBindingContext);
newBindingContext.ModelState.Remove("Price");
newBindingContext.ModelState.Add("Price", new ModelState());
newBindingContext.ModelState.SetModelValue("Price", new ValueProviderResult(price, price, null));
return o;
}
else
{
return base.BindModel(controllerContext, bindingContext);
}
}
//protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
//{
// //return base.CreateModel(controllerContext, bindingContext, modelType);
// PropertyModel model = new PropertyModel();
// if (modelType == typeof(PropertyModel))
// {
// model = (PropertyModel)base.CreateModel(controllerContext, bindingContext, modelType);
// HttpRequestBase request = controllerContext.HttpContext.Request;
// string price = request.Form.Get("Price").Replace(",", string.Empty);
// model.Price = Convert.ToInt32(price);
// }
// return model;
//}
}
}
Он сортирует, но если я ввожу 0 для цены, модель возвращается как действительная, что неверно, потому что у меня есть аннотация Range, в которой говорится, что минимальная цена равна 1. На моем конце wit.
Update:
Чтобы протестировать специализированное связующее устройство с составными типами. Я создал следующие классы модели представлений:
using System.ComponentModel.DataAnnotations;
namespace MvcApplication1.Models
{
public class PropertyRegistrationViewModel
{
public PropertyRegistrationViewModel()
{
}
public Property Property { get; set; }
public Agent Agent { get; set; }
}
public class Property
{
public int HouseNumber { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
[Required(ErrorMessage="You must enter the price.")]
[Range(1000, 10000000, ErrorMessage="Bad price.")]
public int Price { get; set; }
}
public class Agent
{
public string FirstName { get; set; }
public string LastName { get; set; }
[Required(ErrorMessage="You must enter your annual sales.")]
[Range(10000, 5000000, ErrorMessage="Bad range.")]
public int AnnualSales { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string Line1 { get; set; }
public string Line2 { get; set; }
}
}
И вот контроллер:
using MvcApplication1.Core;
using MvcApplication1.Models;
using System.Web.Mvc;
namespace MvcApplication1.Controllers {
public class RegistrationController : Controller
{
public ActionResult Index() {
PropertyRegistrationViewModel viewModel = new PropertyRegistrationViewModel();
return View(viewModel);
}
[HttpPost]
public ActionResult Index([ModelBinder(typeof(PropertyRegistrationModelBinder))]PropertyRegistrationViewModel viewModel)
{
if (ModelState.IsValid)
{
//save registration.
}
return View(viewModel);
}
}
}
Вот пример реализации связующего объекта:
using MvcApplication1.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MvcApplication1.Core
{
public class PropertyRegistrationModelBinder : DefaultModelBinder
{
protected override object GetPropertyValue(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
System.ComponentModel.PropertyDescriptor propertyDescriptor,
IModelBinder propertyBinder)
{
if (propertyDescriptor.ComponentType == typeof(PropertyRegistrationViewModel))
{
if (propertyDescriptor.Name == "Property")
{
var price = bindingContext.ValueProvider.GetValue("Property.Price").AttemptedValue.Replace(",", string.Empty);
var property = new Property();
// Question 1: Price is the only property I want to modify. Is there any way
// such that I don't have to manually populate the rest of the properties like so?
property.Price = string.IsNullOrWhiteSpace(price)? 0: Convert.ToInt32(price);
property.HouseNumber = Convert.ToInt32(bindingContext.ValueProvider.GetValue("Property.HouseNumber").AttemptedValue);
property.Street = bindingContext.ValueProvider.GetValue("Property.Street").AttemptedValue;
property.City = bindingContext.ValueProvider.GetValue("Property.City").AttemptedValue;
property.State = bindingContext.ValueProvider.GetValue("Property.State").AttemptedValue;
property.Zip = bindingContext.ValueProvider.GetValue("Property.Zip").AttemptedValue;
// I had thought that when this property object returns, our annotation of the Price property
// will be honored by the model binder, but it doesn't validate it accordingly.
return property;
}
if (propertyDescriptor.Name == "Agent")
{
var sales = bindingContext.ValueProvider.GetValue("Agent.AnnualSales").AttemptedValue.Replace(",", string.Empty);
var agent = new Agent();
// Question 2: AnnualSales is the only property I need to process before validation,
// Is there any way I can avoid tediously populating the rest of the properties?
agent.AnnualSales = string.IsNullOrWhiteSpace(sales)? 0: Convert.ToInt32(sales);
agent.FirstName = bindingContext.ValueProvider.GetValue("Agent.FirstName").AttemptedValue;
agent.LastName = bindingContext.ValueProvider.GetValue("Agent.LastName").AttemptedValue;
var address = new Address();
address.Line1 = bindingContext.ValueProvider.GetValue("Agent.Address.Line1").AttemptedValue + " ROC";
address.Line2 = bindingContext.ValueProvider.GetValue("Agent.Address.Line2").AttemptedValue + " MD";
agent.Address = address;
// I had thought that when this agent object returns, our annotation of the AnnualSales property
// will be honored by the model binder, but it doesn't validate it accordingly.
return agent;
}
}
return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
}
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = bindingContext.Model as PropertyRegistrationViewModel;
//In order to validate our model, it seems that we will have to manually validate it here.
base.OnModelUpdated(controllerContext, bindingContext);
}
}
}
И вот вид Razor:
@model MvcApplication1.Models.PropertyRegistrationViewModel
@{
ViewBag.Title = "Property Registration";
}
<h2>Property Registration</h2>
<p>Enter your property and agent information below.</p>
@using (Html.BeginForm("Index", "Registration"))
{
@Html.ValidationSummary();
<h4>Property Info</h4>
<text>House Number</text> @Html.TextBoxFor(m => m.Property.HouseNumber)<br />
<text>Street</text> @Html.TextBoxFor(m => m.Property.Street)<br />
<text>City</text> @Html.TextBoxFor(m => m.Property.City)<br />
<text>State</text> @Html.TextBoxFor(m => m.Property.State)<br />
<text>Zip</text> @Html.TextBoxFor(m => m.Property.Zip)<br />
<text>Price</text> @Html.TextBoxFor(m => m.Property.Price)<br />
<h4>Agent Info</h4>
<text>First Name</text> @Html.TextBoxFor(m => m.Agent.FirstName)<br />
<text>Last Name</text> @Html.TextBoxFor(m => m.Agent.LastName)<br />
<text>Annual Sales</text> @Html.TextBoxFor(m => m.Agent.AnnualSales)<br />
<text>Agent Address L1</text>@Html.TextBoxFor(m => m.Agent.Address.Line1)<br />
<text>Agent Address L2</text>@Html.TextBoxFor(m => m.Agent.Address.Line2)<br />
<input type="submit" value="Submit" name="submit" />
}
И вот файл global.asax, в котором я подключаю подкатализацию пользовательской модели. Кстати, кажется, что этот шаг не нужен, потому что я заметил, что он все еще работает без этого шага.
using MvcApplication1.Core;
using MvcApplication1.Models;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
namespace MvcApplication1 {
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication {
protected void Application_Start() {
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
ModelBinders.Binders.Add(typeof(PropertyRegistrationViewModel), new PropertyRegistrationModelBinder());
}
}
}
Может быть, я делаю что-то неправильно или недостаточно. Я заметил следующие проблемы:
- Хотя мне нужно только изменить значение цены объекта свойства, кажется, мне нужно утомительно заполнить все другие свойства в связующем устройстве. Я должен сделать то же самое для свойства AnnualSales свойства агента. Так или иначе, этого можно избежать в модельном связующем?
- Я думал, что метод BindModel по умолчанию будет выполнять нашу аннотацию свойств наших объектов и проверять их соответственно после вызова GetPropertyValue, но это не так. Если я ввожу какой-то параметр значения вне диапазона для параметра Цена объекта "Свойство" или "Годовые продажи агента", модель возвращается как действительная. Другими словами, аннотации Range игнорируются. Я знаю, что могу проверить их, переопределив OnModelUpdated в настраиваемом подменю модели, но это слишком много работы, и, кроме того, у меня есть аннотации на месте, почему реализация по умолчанию для имплантата модели не почитает их только потому, что я преобладаю часть из этого?
@dotnetstep: Не могли бы вы рассказать об этом? Спасибо.