Пользовательская проверка ASP.NET Core создает новый экземпляр модели - PullRequest
0 голосов
/ 25 октября 2018

Я играю с ASP.NET Core и пытаюсь придумать пользовательский интерфейс для простой игры в слова.Вы получаете случайное сгенерированное длинное слово, и ожидается, что вы отправите более короткие слова из букв, предоставленных длинным словом.

Приложение еще не использует какой-либо репозиторий, а просто хранит модельэкземпляр в качестве статического поля в контроллере на данный момент.

В настоящее время я сталкиваюсь с проблемой, когда каждый раз, когда новое переданное слово проверяется, создается новый экземпляр игры, который, естественно, гарантирует визуализацию ошибки проверки,потому что в каждой игре есть новое длинное слово.

Я, должно быть, неправильно понимаю, как работает валидация модели, но отладка не дает мне более хороших подсказок, чем просто показ контекста валидации, который приходит с новым длинным словомкаждый раз.

Я застрял, пожалуйста, помогите.

Вот контроллер:

public class HomeController : Controller
{
    private static WordGameModel _model;

    public IActionResult Index()
    {
        if (_model == null)
        {
            _model = new WordGameModel();
        }
        return View(_model);
    }

    [HttpPost]
    public IActionResult Index(WordGameModel incomingModel)
    {
        if (ModelState.IsValid)
        {
            _model.Words.Add(incomingModel.ContainedWordCandidate);
            return RedirectToAction(nameof(Index), _model);
        }
        return View(_model);
    }
}

Модель игры:

public class WordGameModel
{
    public WordGameModel()
    {
        if (DictionaryModel.Dictionary == null) DictionaryModel.LoadDictionary();
        LongWord = DictionaryModel.GetRandomLongWord();
        Words = new List<string>();
    }

    public string LongWord { get; set; }
    public List<string> Words { get; set; }

    [Required(ErrorMessage = "Empty word is not allowed")]
    [MinLength(5, ErrorMessage = "A word shouldn't be shorter than 5 characters")]
    [MatchesLettersInLongWord]
    [NotSubmittedPreviously]
    public string ContainedWordCandidate { get; set; }

    public bool WordWasNotSubmittedPreviously() => !Words.Contains(ContainedWordCandidate);
    public bool WordMatchesLettersInLongWord()
    {
        if (string.IsNullOrWhiteSpace(ContainedWordCandidate)) return false;
        return ContainedWordCandidate.All(letter => LongWord.Contains(letter));
    }
}

КастомАтрибут проверки, где проверка не пройдена:

internal class MatchesLettersInLongWord : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        WordGameModel model = (WordGameModel) validationContext.ObjectInstance;

        if (model.WordMatchesLettersInLongWord()) return ValidationResult.Success;

        return new ValidationResult("The submitted word contains characters that the long word doesn't contain");
    }
}

Просмотр:

@model WordGameModel

<div class="row">
    <div class="col-md-12">
        <h2>@Model.LongWord</h2>
    </div>
</div>

<div class="row">
    <div class="col-md-6">
        <form id="wordForm" method="post">
            <div>
                <input id="wordInput" asp-for="ContainedWordCandidate"/>
                <input type="submit" name="Add" value="Add"/>
                <span asp-validation-for="ContainedWordCandidate"></span>
            </div>

        </form>
    </div>
</div>

<div class="row">
    <div class="col-md-6">
        <ul>
            @foreach (var word in @Model.Words)
            {
                <li>@word</li>
            }
        </ul>
    </div>
</div>

Спасибо.

Ответы [ 3 ]

0 голосов
/ 26 октября 2018

Ваше представление должно включать скрытый ввод для LongWord, чтобы в методе POST, чтобы после вызова вашего конструктора из ModelBinder, LongWord устанавливался на основе значения формы (т.е. значения, которое выотправлено в представление)

<form id="wordForm" method="post">
    <div>
        <input type="hidden" asp-for="LongWord" /> // add hidden input
        <input id="wordInput" asp-for="ContainedWordCandidate"/>
        <input type="submit" name="Add" value="Add"/>
        <span asp-validation-for="ContainedWordCandidate"></span>
    </div>
</form>

В качестве примечания, в вашем методе записи он должен быть просто return RedirectToAction(nameof(Index)); - метод GET не имеет (и не должен) иметь параметр для модели, поэтомуне имеет смысла передавать его (и это все равно просто создаст некрасивую строку запроса)

0 голосов
/ 26 октября 2018

Не используйте статическое поле в контроллере для хранения ваших слов.Не рекомендуется сохранять состояние в контроллере, поскольку, как указано в другом ответе, контроллер равен transient, и для каждого запроса создается новый.Таким образом, даже если ваша статическая переменная все еще должна быть доступна, хранить ее с контроллером не очень хорошо.Также вы хотите сохранить свою модель в чистоте, то есть не вкладывать в нее какую-либо бизнес / игровую логику.Используйте другой класс для этого.Используйте модель только для проверки правильности значений, т. Е. Минимальной длины, требуемой и т. Д.

Лучшим решением для вашей проблемы будет создание службы singleton для хранения данных.Как одиночный, только один сервис будет создан за время жизни вашего приложения.Вы можете использовать Dependency Injection, чтобы внедрить его в свой контроллер и использовать его для каждого запроса, зная, что это будет один и тот же экземпляр службы для каждого запроса.

Например:

public interface IWordService
{
    IEnumerable<String> Words { get; }

    bool WordWasNotSubmittedPreviously(string word);

    bool WordMatchesLettersInLongWord(string longWord, string containedWordCandidate);

    void AddWordToList(string word);
}

public class WordService : IWordService
{
    private List<string> _words;

    public IEnumerable<string> Words => _words;

    public WordService()
    {
        _words = new List<string>();
    }

    public bool WordWasNotSubmittedPreviously(string containedWordCandidate) => !_words.Contains(containedWordCandidate);

    public bool WordMatchesLettersInLongWord(string longWord, string containedWordCandidate)
    {
        if (string.IsNullOrWhiteSpace(containedWordCandidate)) return false;
        return containedWordCandidate.All(letter => longWord.Contains(letter));
    }

    public void AddWordToList(string word)
    {
        _words.Add(word);
    }
}

Этот сервис выполняет всю работу, которую выполнял ваш ValidationAttribute, но мы можем использовать Dependency Injection, чтобы убедиться, что мы создаем только один для всего приложения.

В вас Startup.cs добавьте это к методу ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IWordService, WordService>();

    ....
}

Теперь мы можем внедрить это в наш контроллер и, поскольку мы зарегистрировали его как singleton, мы будем получать один и тот же экземпляр каждый раз, даже если мы получим другой экземпляр контроллера:

public class HomeController : Controller
{
    private readonly IWordService _wordService;

    public HomeController(IWordService wordService)
    {
        _wordService = wordService;
    }

    [HttpPost]
    public IActionResult Index(WordGameModel incomingModel)
    {
        if (ModelState.IsValid)
        {
            // Use the `_wordService instance to perform your checks and validation
            ...
        }

        ...
    }
}

Я оставил фактическое использование _wordService для реализации :-), но это должно быть довольно просто.

Вы можете прочитать больше о Dependency Injection (DI) здесь

А также метод ConfigureServices здесь

0 голосов
/ 26 октября 2018

С каждым запросом к вашему действию в HomeController инфраструктура mvc создает для этого новый экземпляр контроллера.После возврата ответа он утилизирует контроллер.

Поля и объекты контроллера не могут быть общими для запросов.В вашем случае с каждым вызванным действием ваш экземпляр WordGameModel будет снова создан, а его конструктор создаст новое длинное слово.
Вы можете сохранить свой объект в некоторой базе данных для каждого пользователя, чтобы обеспечить функциональность.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...