Привязка пользовательской модели не работает с вложенным свойством (ASP.NET Core MVC 2) - PullRequest
0 голосов
/ 27 июня 2018

Я пытаюсь написать пользовательский механизм связывания модели даты и времени, который используется для преобразования опубликованной строки в формате «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 выше?

Большое спасибо

...