Я пытаюсь написать пользовательский механизм связывания модели даты и времени, который используется для преобразования опубликованной строки в формате «dd / MM / yyyy» в действительную дату на сервере (формат даты сервера - «MM / dd / yyyy»). Мой исходный код выглядит следующим образом:
Просмотр модели
TestViewModel.cs
namespace AcaraDataRequestApplication.Models
{
public class TestViewModel
{
public int Id { get; set; }
public FirstNestedViewModel FirstNested { get; set; }
}
public class FirstNestedViewModel
{
public string Name { get; set; }
public SecondNestedViewModel SecondNested { get; set; }
}
public class SecondNestedViewModel
{
public string Code { get; set; }
[Required]
//[CustomDateTimeModelBinder(DateFormat = "dd/MM/yyyy")]
[ModelBinder(typeof(CustomDateTimeModelBinder))]
public DateTime ReleaseDate { get; set; }
}
}
Как вы можете видеть, я хотел бы привязать значение для свойства ReleaseDate из опубликованных данных формы, используя настраиваемое связующее для модели даты и времени (формат даты публикации: дд / мм / гггг и формат даты сервера на основе текущих настроек культуры, в мой случай MM / дд / гггг).
Пользовательская модель Binder
CustomDateTimeModelBinder.cs
public class CustomDateTimeModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if(bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
if(bindingContext.ModelType != typeof(DateTime))
{
return Task.CompletedTask;
}
string modelName = GetModelName(bindingContext);
ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if(valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);
string dateToParse = valueProviderResult.FirstValue;
if(string.IsNullOrEmpty(dateToParse))
{
return Task.CompletedTask;
}
DateTime dateTimeResult = ParseDate(bindingContext, dateToParse);
if(dateTimeResult == DateTime.MinValue)
{
bindingContext.ModelState.TryAddModelError(modelName, $"The value '{dateToParse}' is not valid.");
return Task.CompletedTask;
}
bindingContext.Result = ModelBindingResult.Success(dateTimeResult);
return Task.CompletedTask;
}
}
По сути, логика привязки пользовательской модели заключается в преобразовании размещенной строки в формате дд / мм / гггг в действительную дату на сервере (формат даты сервера: ММ / дд / гггг). Это означает, что опубликованное строковое значение: «27/06/2018» будет правильно преобразовано на 27 июня 2018 года на сервере.
CustomDateTimeModelBinderAttribute.cs
namespace AcaraDataRequestApplication.ModelBinders
{
public class CustomDateTimeModelBinderAttribute: ModelBinderAttribute
{
public string DateFormat { get; set; }
public CustomDateTimeModelBinderAttribute() :
base(typeof(CustomDateTimeModelBinder))
{
}
}
}
Этот атрибут наследует ModelBinderAttribute, он позволяет мне непосредственно декорировать атрибут на уровне свойства вместо использования атрибута ModelBinder.
CustomDateTimeModelBinderProvider.cs
namespace AcaraDataRequestApplication.ModelBinders
{
public class CustomDateTimeModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if(context.Metadata.ModelType == typeof(DateTime))
{
return new CustomDateTimeModelBinder();
}
return null;
}
}
}
Этот провайдер помогает мне при необходимости зарегистрировать пользовательскую привязку модели даты и времени на уровне приложения
View
DateOnNestedModel.cshtml
@model TestViewModel
@{
ViewData["Title"] = "Date On Nested Model";
}
<form asp-controller="Home" asp-action="DateOnNestedModel" method="post">
<div class="form-group">
<label>Choose a date</label>
<div class="input-group date" data-provide="datepicker" data-date-format="dd/mm/yyyy" data-date-autoclose="true" data-date-today-highlight="true">
<input type="text" class="form-control" asp-for="FirstNested.SecondNested.ReleaseDate">
<div class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</div>
</div>
<span asp-validation-for="FirstNested.SecondNested.ReleaseDate" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<div class="row">
<p>@Model?.FirstNested?.SecondNested?.ReleaseDate.ToString()</p>
</div>
@section Scripts
{
@Html.Partial("_DatepickerScriptsPartial");
}
В Razor View у меня есть только одно поле даты, использующее загрузчик даты начальной загрузки, который отображает дату в формате: дд / мм / гггг. (Обратите внимание, что если представление Razor содержит все поля, соответствующие TestViewModel, то моя пользовательская привязка модели даты и времени может работать нормально. Но если представление Razor содержит только одно поле даты, тогда моя настраиваемая привязка модели даты и времени не запускается).
Контроллер
HomeController.cs
namespace AcaraDataRequestApplication.Controllers
{
public class HomeController : Controller
{
public IActionResult DateOnNestedModel()
{
return View();
}
[HttpPost]
public IActionResult DateOnNestedModel(TestViewModel viewModel)
{
if(ModelState.IsValid)
{
}
return View(viewModel);
}
}
}
Демо
Случай 1: пользовательское связующее для модели даты и времени не запускается
На экране я выбираю «27/06/2018» для поля даты и нажимаю кнопку «Отправить», но на сервере я понял, что код привязки пользовательской модели даты и времени не запускается при отладке.
Экран
viewModel.FirstNested.SecondNested.ReleaseDate не привязывается при отладке
Случай 2: пользовательское связующее для модели даты и времени работает нормально, когда я регистрирую связующее для модели на уровне приложения
Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new CustomDateTimeModelBinderProvider());
});
}
}
TestViewModel.cs
public class SecondNestedViewModel
{
public string Code { get; set; }
[Required]
//[CustomDateTimeModelBinder(DateFormat = "dd/MM/yyyy")]
//[ModelBinder(typeof(CustomDateTimeModelBinder))]
public DateTime ReleaseDate { get; set; }
}
Я сделал шаги, аналогичные случаю 1, и на этот раз я понял, что код связывателя пользовательской модели даты и времени выполнялся при отладке.
viewModel.FirstNested.SecondNested.ReleaseDate привязано при отладке
Случай 3: пользовательское связывание модели даты и времени работает нормально, когда я связываю дополнительное поле с именем TestViewModel.FirstNested.SecondNested.Code
Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
//options.ModelBinderProviders.Insert(0, new CustomDateTimeModelBinderProvider());
});
}
}
TestViewModel.cs
public class SecondNestedViewModel
{
public string Code { get; set; }
[Required]
//[CustomDateTimeModelBinder(DateFormat = "dd/MM/yyyy")]
[ModelBinder(typeof(CustomDateTimeModelBinder))]
public DateTime ReleaseDate { get; set; }
}
DateOnNestedModel.cshtml
@model TestViewModel
@{
ViewData["Title"] = "Date On Nested Model";
}
<form asp-controller="Home" asp-action="DateOnNestedModel" method="post">
<div class="form-group">
<label asp-for="FirstNested.SecondNested.Code">Code</label>
<input type="text" class="form-control" asp-for="FirstNested.SecondNested.Code" />
</div>
<div class="form-group">
<label>Choose a date</label>
<div class="input-group date" data-provide="datepicker" data-date-format="dd/mm/yyyy" data-date-autoclose="true" data-date-today-highlight="true">
<input type="text" class="form-control" asp-for="FirstNested.SecondNested.ReleaseDate">
<div class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</div>
</div>
<span asp-validation-for="FirstNested.SecondNested.ReleaseDate" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<div class="row">
<p>@Model?.FirstNested?.SecondNested?.ReleaseDate.ToString()</p>
</div>
@section Scripts
{
@Html.Partial("_DatepickerScriptsPartial");
}
На экране я ввожу «Abc» для поля кода и выбираю «27/06/2018» для поля даты и нажимаю кнопку «Отправить», но на сервере я понял, что код привязки пользовательской модели даты и времени работает нормально при отладке.
Экран
viewModel.FirstNested.SecondNested.ReleaseDate привязано при отладке
Вопросы
Не могли бы вы помочь мне выяснить причину, по которой связыватель пользовательской модели даты не работает в случае 1 выше?
Большое спасибо