проверьте форму перед отправкой asp.net core 2.0 - PullRequest
0 голосов
/ 08 мая 2018

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

Я уверен, что моя проблема довольно проста для опытного разработчика, у меня просто нет опыта и знаний, чтобы ее исправить.

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

Форма теста выглядит следующим образом: Форма теста

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

Текущее представление, которое содержит функцию javascript, которая работает некоторым образом ... она вообще запрещает мне отправлять сообщения ... поэтому я смотрю на что-то, что не совсем правильно, но в большинстве случаев верно. Так что, думаю, мне нужна небольшая помощь, чтобы исправить эту часть или изменить способ, которым она делается, на что-то лучшее?

@using RefereeOnline.Models.FormsViewModels
@model RenderFormViewModel

@{
    ViewData["Title"] = "Evaluate";
}

<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script>
    function ValidateForm() {
        var isFormValid = true;
        $("#evaluateform input,select").each(function () {
            var FieldId = "span_" + $(this).attr("id");
            if ($.trim($(this).val()).length == 0 || $.trim($(this).val()) == 0) {
                $(this).addClass("highlight");

                //Show required message along with the field
                if ($("#" + FieldId).length == 0) {
                    $("<span class='error' id='" + FieldId + "'>Required</span>").insertAfter(this);
                }
                //If you fill and again make the field empty in that case again show the message
                if ($("#" + FieldId).css('display') == 'none') {
                    $("#" + FieldId).fadeIn(500);
                }
                //$(this).focus();
                isFormValid = false;

            }
            else {
                $(this).removeClass("highlight");
                if ($("#" + FieldId).length > 0) {
                    // Hide the message with the fade out effect
                    $("#" + FieldId).fadeOut(1000);
                }
            }
        });
        return isFormValid;
    }
</script>
<h2>Evaluate (@Model.FormTitle @Model.FormVersion)</h2>

@using (Html.BeginForm("Evaluate", "Forms", FormMethod.Post, new { id = "evaluateform" }))
{
    <div>
        <hr />
        <dl class="dl-horizontal">
            <dt>Name</dt>
            <dd>@Model.PersonName</dd>
            <dt>Match</dt>
            <dd>@Model.ActivityInfo</dd>
            <dt>Level</dt>
            <dd>@Model.LevelName</dd>
        </dl>
        <hr />
        @for (int g = 0; g < Model.Groups.Count; g++)
        {
            <table class="table">
                @Html.Hidden("Model.Groups[" + @g + "].GroupId", Model.Groups[g].GroupId)
                @if (Model.Groups[g].Answers.Any())
                {
                    <tr>
                        <td></td>
                        @foreach (var answer in Model.Groups[g].Answers)
                        {
                            <td>@answer.Text</td>
                        }
                        @if (Model.Groups[g].Questions.Any(x => x.AllowComment))
                        {
                            <td>Comment</td>
                        }
                    </tr>
                }
                @for (int i = 0; i < Model.Groups[g].Questions.Count; i++)
                {
                    <tr>
                        <td>@Model.Groups[g].Questions[i].Text</td>
                        @if (Model.Groups[g].Answers.Any() && !Model.Groups[g].Questions[i].Answers.Any())
                        {
                            foreach (var answer in Model.Groups[g].Answers)
                            {
                                <td>
                                    @Html.RadioButton("Model.Groups[" + g + "].Questions[" + i + "].SelectedAnswer", answer.Id)
                                    @Html.Label(answer.Value.ToString(), answer.Value.ToString())
                                    @Html.Hidden("Model.Groups[" + g + "].Questions[" + i + "].FieldId", Model.Groups[g].Questions[i].FieldId)
                                </td>
                            }
                        }
                        else if (Model.Groups[g].Questions[i].Answers.Any()) //single question with answers
                        {
                            foreach (RenderAnswer answer in Model.Groups[g].Questions[i].Answers)
                            {
                                <td>
                                    @Html.RadioButton("Model.Groups[" + g + "].Questions[" + i + "].SelectedAnswer", answer.Id)
                                    @Html.Label(answer.Value.ToString(), answer.Text)
                                    @Html.Hidden("Model.Groups[" + g + "].Questions[" + i + "].FieldId", Model.Groups[g].Questions[i].FieldId)
                                </td>
                            }
                        }
                        else //single question with textbox
                        {
                            <td>@Html.TextBox("Model.Groups[" + g + "].Questions[" + i + "].AnswerValue", Model.Groups[g].Questions[i].AnswerValue)</td>
                        }
                        @if (Model.Groups[g].Questions.Any(x => x.AllowComment))
                        {
                            <td>
                                @if (Model.Groups[g].Questions[i].AllowComment)
                                {
                                    @Html.TextArea("Model.Groups[" + g + "].Questions[" + i + "].Comment", Model.Groups[g].Questions[i].Comment, 2, 40, null)
                                }
                            </td>
                        }
                    </tr>
                }
            </table>
        }
    </div>
    @Html.Hidden("Model.CustomerId", Model.CustomerId)
    @Html.Hidden("Model.FormId", Model.FormId)
    @Html.Hidden("Model.UserId", Model.UserId)
    @Html.Hidden("Model.ActivityId", Model.ActivityId)

    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Save" class="btn btn-default" onclick="return ValidateForm();" /> |
            @Html.ActionLink("Back", "PlannedEvaluations", "Person", new { userid = Model.UserId })
        </div>
    </div>
}

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

<!--onclick="return ValidateForm();" -->

Моя модель просмотра:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace RefereeOnline.Models.FormsViewModels
{
    public class RenderFormViewModel
    {
        public Guid CustomerId { get; set; }
        public Guid FormId { get; set; }
        public string UserId { get; set; }

        public string FormTitle { get; set; }
        public string FormVersion { get; set; }
        public Guid ActivityId { get; set; }
        public string PersonName { get; set; }
        public string ActivityInfo { get; set; }
        public string LevelName { get; set; }

        public List<RenderGroup> Groups { get; set; } = new List<RenderGroup>();
    }

    public class RenderGroup
    {
        public string GroupId { get; set; }

        public List<RenderQuestion> Questions { get; set; } = new List<RenderQuestion>();

        /// <summary>
        /// Contains a list of possible answers to limit to
        /// If empty, no limited answers
        /// </summary>
        public List<RenderAnswer> Answers { get; set; } = new List<RenderAnswer>();
    }

    public class RenderQuestion
    {
        public Guid FieldId { get; set; }
        public string Text { get; set; }

        /// <summary>
        /// Specific answers for this field
        /// Used if in a group, but answers not re-used
        /// </summary>
        public List<RenderAnswer> Answers { get; set; } = new List<RenderAnswer>();

        public string AnswerValue { get; set; }

        public Guid SelectedAnswer { get; set; }
        public bool AllowComment { get; set; }

        public string Comment { get; set; }
    }

    public class RenderAnswer
    {
        public Guid Id { get; set; }
        public string Text { get; set; }
        public int Value { get; set; }
    }
}

Мой контроллер (имеет контекст ядра EF), удалил все ненужные методы:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Rewrite.Internal.UrlActions;
using Microsoft.EntityFrameworkCore;
using RefereeDb.Entities;
using RefereeOnline.Data;
using RefereeOnline.Data.Entities;
using RefereeOnline.Models.FormsViewModels;
using Activity = RefereeOnline.Data.Entities.Activity;

namespace RefereeOnline.Controllers
{
    [Authorize]
    [Route("[controller]/[action]")]
    public class FormsController : Controller
    {
        private readonly RefereeContext _context;

        public FormsController(
            RefereeContext context)
        {
            _context = context;
        }

        public IActionResult Evaluate(Guid eventid, Guid activityid)
        {
            var eventData = _context.Events.Find(eventid);
            var customer = _context.Customers.Find(eventData.CustomerId);
            var activityData = _context.Activities.Include(m => m.MetaData).ThenInclude(f => f.Field)
                .ThenInclude(mf => mf.MetaField).Include(l => l.Level).ThenInclude(t => t.Type)
                .ThenInclude(r => r.CustomerReferences).First(x => x.Id == activityid);

            EvaluationForm form = null;
            try
            {
                form = _context.EvaluationForms.Include(f => f.Fields).ThenInclude(a => a.Answers)
                    .First(x => x.Id == activityData.Level.Type.CustomerReferences
                                    .First(c => c.CustomerId == customer.Id &&
                                                c.LicenseTypeId == activityData.Level.LicenseTypeId).EvaluationFormId);
            }
            catch (Exception ex)
            {
                return RedirectToAction("ShowMessage", "Site",
                    new
                    {
                        message = $"Evaluation forms not configured correctly for {activityData.Level.Name}",
                        returnurl = HttpUtility.HtmlEncode(Url.Action("Index", "Home"))
                    });
            }

            var model = BuildViewModel(form);
            model.ActivityId = activityData.Id;
            var user = _context.Users.First(x => x.Id == activityData.PersonId);
            model.PersonName = user.FullName;
            model.LevelName = activityData.Level.Name;
            model.ActivityInfo =
                $"{activityData.Date.ToShortDateString()} {activityData.Date.ToShortTimeString()} {activityData.Place}";
            foreach (CustomerMetaFieldData data in activityData.MetaData.OrderBy(o => o.Field.MetaField.Order))
                model.ActivityInfo += $" {data.FieldValue}";

            return View(model);
        }

        [HttpPost]
        public IActionResult Evaluate(RenderFormViewModel model)
        {
            var activity = _context.Activities.Include(l => l.Level).ThenInclude(r => r.Rules).Include(p => p.Person)
                .ThenInclude(l => l.Licenses).ThenInclude(t => t.Type).First(x => x.Id == model.ActivityId);
            _context.Entry(activity.Person).Collection(x => x.Batches).Load();

            //batch id is assigned in post processing
            Evaluation evaluation = new Evaluation { ActivityId = activity.Id, EvaluationFormId = model.FormId};
            activity.EvaluationData = evaluation;

            var customer = _context.Customers.Include(t => t.AssociatedTypes).ThenInclude(s => s.EvaluationSetup)
                .First(x => x.Id == model.CustomerId);

            var setups = customer.AssociatedTypes.First(t => t.LicenseTypeId == activity.Level.LicenseTypeId)
                .EvaluationSetup.Where(x => x.LicenseLevelId == activity.LicenseLevelId);

            _context.SaveChanges();

            try
            {
                //load the form
                _context.Entry(activity.EvaluationData).Reference(f => f.EvaluationForm).Load();
                _context.Entry(activity.EvaluationData.EvaluationForm).Collection(f => f.Fields).Load();
                foreach (EvaluationFormField field in activity.EvaluationData.EvaluationForm.Fields)
                    _context.Entry(field).Collection(a => a.Answers).Load();

                Dictionary<string, int> points = new Dictionary<string, int>();

                foreach (RenderGroup renderGroup in model.Groups.Where(x => !string.IsNullOrEmpty(x.GroupId)))
                {
                    var groupSetup = setups.FirstOrDefault(x => x.Group == renderGroup.GroupId);

                    if (renderGroup.GroupId != null)
                        points.Add(renderGroup.GroupId, 0);

                    foreach (RenderQuestion question in renderGroup.Questions)
                    {
                        activity.EvaluationData.Data.Add(new EvaluationData
                        {
                            FieldId = question.FieldId,
                            AnswerId = question.SelectedAnswer,
                            EvaluationId = activity.EvaluationData.Id,
                            Comment = question.Comment
                        });

                        if (renderGroup.GroupId != null)
                        {
                            var currentField =
                                activity.EvaluationData.EvaluationForm.Fields.First(f => f.Id == question.FieldId);

                            FieldAnswer currentAnswer = null;
                            if (currentField.SameAnswersForAll)
                            {
                                var field = activity.EvaluationData.EvaluationForm.Fields.First(x =>
                                    x.Answers.Any() && x.Group == renderGroup.GroupId);
                                var answers = field.Answers;
                                currentAnswer = answers.FirstOrDefault(a => a.Id == question.SelectedAnswer);
                            }
                            else
                            {
                                currentAnswer = currentField.Answers.First(a => a.Id == question.SelectedAnswer);
                            }

                            points[renderGroup.GroupId] += currentAnswer.Points;
                        }
                    }

                    if (renderGroup.GroupId != null)
                    {
                        var fields =
                            activity.EvaluationData.EvaluationForm.Fields.Where(x => x.Group == renderGroup.GroupId);
                        int max = 0;
                        if (fields.Any(x => x.SameAnswersForAll))
                        {
                            max = fields.First(x => x.Answers.Any()).Answers.Max(m => m.Points) * fields.Count();
                        }
                        else
                        {
                            max = fields.Sum(x => x.Answers.Max(a => a.Points));
                        }

                        EvaluationPointSums newPoints =
                            new EvaluationPointSums
                            {
                                GroupId = renderGroup.GroupId,
                                EvaluationId = evaluation.Id,
                                Points = points[renderGroup.GroupId],
                                Threshold = groupSetup?.PassThreshold ?? 0,
                                Maximum = max
                            };
                        evaluation.Points.Add(newPoints);
                    }
                }

                _context.Audit.Add(new Audit(User.Identity.Name, evaluation.Id, "Evaluation added"));

                _context.SaveChanges();
            }
            catch (Exception)
            {
                //reverting the evaluation
                _context.Evaluations.Remove(evaluation);

                _context.SaveChanges();

                //todo: go to message
            }

            //post processing the evaluation... should new license be created? or expired..
            PostProcessEvaluation(activity, evaluation);

            return RedirectToAction("EvaluationResult", new { evaluationid = evaluation.Id });
        }

        public IActionResult EvaluationDetails(Guid activityid, Guid evaluationid, bool score, bool data, string userid)
        {
            //getting event, activity, metadata, form etc...
            var activity = _context.Activities.Include(e => e.EvaluationData).ThenInclude(f => f.EvaluationForm)
                .ThenInclude(ff => ff.Fields).ThenInclude(fc => fc.Answers).Include(m => m.MetaData)
                .ThenInclude(fd => fd.Field).ThenInclude(d => d.MetaField).Include(e => e.Event).Include(l => l.Level)
                .First(x => x.Id == activityid);

            _context.Entry(activity.EvaluationData).Collection(x => x.Data).Load();
            _context.Entry(activity.EvaluationData).Collection(x => x.Points).Load();
            foreach (var evaluationData in activity.EvaluationData.Data)
                _context.Entry(evaluationData).Reference(x => x.Answer).Load();

            DisplayEvaluationViewModel model = new DisplayEvaluationViewModel { UserId = userid };

            model.Activity = activity;
            model.RenderModel = BuildViewModel(activity.EvaluationData.EvaluationForm);

            return View(model);
        }

        private void PostProcessEvaluation(Activity activity, Evaluation evaluation)
        {
            EvaluationRule rule = activity.Person.HasLicense(activity.LicenseLevelId)
                ? activity.Level.Rules.FirstOrDefault(x => x.Scope == LicenseScope.Licensed)
                : activity.Level.Rules.FirstOrDefault(x => x.Scope == LicenseScope.Trainee);

            if (rule != null) //if no rule, nothing happens
            {
                var batch = activity.Person.Batches.FirstOrDefault(x => x.LevelId == activity.LicenseLevelId);

                if (batch == null)
                {
                    //creating new batch, marking evaluation with it
                    batch = new EvaluationIdentityBatch { CurrentBatch = Guid.NewGuid(), LevelId = activity.LicenseLevelId, PersonId = activity.PersonId };
                    evaluation.BatchId = batch.Id;
                    activity.Person.Batches.Add(batch);

                    _context.SaveChanges();
                }

                //get all evaluations belonging to this batch
                var evals = _context.Evaluations.Where(x => x.BatchId == batch.CurrentBatch);

                if (evals.Count(x => x.IsPassed) == rule.Goal)
                {
                    //target hit, all is good, execute passed action
                    ExecuteAction(rule.SuccessAction, activity);
                }
                else if (evals.Count() == rule.Tries)
                {
                    //execute failed action
                    ExecuteAction(rule.FailAction, activity);
                }
                else
                {
                    //log that nothing happens....
                    Trace.TraceError("Rule found, but not triggered");

                }
            }
        }

        private void ExecuteAction(EvalAction action, Activity activity)
        {
            switch (action)
            {
                case EvalAction.Issue:
                    License newLicense = new License
                    {
                        Assigned = DateTime.Now,
                        CustomerId = activity.Person.CustomerId,
                        LicenseLevelId = activity.LicenseLevelId,
                        LicenseTypeId = activity.Level.LicenseTypeId,
                        PersonId = activity.Person.Id,
                        Recorded = DateTime.Now
                    };
                    activity.Person.Licenses.Add(newLicense);

                    _context.Audit.Add(new Audit(User.Identity.Name, newLicense.Id, "Created by rule"));
                    break;
                case EvalAction.Expire:
                    var license =
                        activity.Person.CurrentLicenses.First(x => x.LicenseLevelId == activity.LicenseLevelId);
                    license.LastActivity = DateTime.Now;
                    license.ForceExpiry = true;

                    _context.Audit.Add(new Audit(User.Identity.Name, license.Id, "Expired by rule"));

                    break;
            }

            var batch = activity.Person.Batches.First(x => x.LevelId == activity.LicenseLevelId);
            activity.Person.Batches.Remove(batch);

            _context.SaveChanges();
        }

        public IActionResult EvaluationResult(Guid evaluationid)
        {
            EvaluationResultViewModel model = new EvaluationResultViewModel();

            var evaluation = _context.Evaluations.Include(p => p.Points).Include(a => a.Activity)
                .ThenInclude(m => m.MetaData).First(x => x.Id == evaluationid);
            _context.Entry(evaluation.Activity).Reference(p => p.Person).Load();
            _context.Entry(evaluation.Activity).Reference(l => l.Level).Load();
            _context.Entry(evaluation.Activity).Collection(r => r.Relations).Load();

            model.Evaluation = evaluation;

            return View(model);
        }

        private RenderFormViewModel BuildViewModel(EvaluationForm form)
        {
            ApplicationUser user = _context.Users.Include(m => m.AssociationMembers).First(x => x.UserName == User.Identity.Name);

            RenderFormViewModel model = new RenderFormViewModel { CustomerId = form.CustomerId, FormId = form.Id, FormVersion = form.FormVersion, FormTitle = form.Name, UserId = user.Id };

            foreach (EvaluationFormField field in form.Fields.OrderBy(x => x.SortOrder))
            {
                if (string.IsNullOrEmpty(field.Group))
                {
                    //normal field
                    RenderGroup group = new RenderGroup();

                    RenderQuestion newQuestion = new RenderQuestion { Text = field.Text, FieldId = field.Id };
                    newQuestion.Answers.AddRange(field.Answers.OrderBy(o => o.SortOrder).Select(x => new RenderAnswer { Id = x.Id, Text = x.DisplayText, Value = x.Points }));

                    group.Questions.Add(newQuestion);

                    model.Groups.Add(group);
                }
                else
                {
                    //grouped field
                    RenderGroup group = model.Groups.FirstOrDefault(x => x.GroupId == field.Group);
                    if (group == null)
                    {
                        //group does not exist... create + add answers
                        group = new RenderGroup { GroupId = field.Group };

                        if (field.SameAnswersForAll)
                        {
                            var answerfield = form.Fields.Where(x => x.Group == field.Group && x.Answers.Any())
                                .OrderBy(o => o.SortOrder).FirstOrDefault();

                            if (answerfield != null)
                            {
                                //adding general answers
                                group.Answers.AddRange(answerfield.Answers.OrderBy(o => o.SortOrder).Select(x => new RenderAnswer { Id = x.Id, Text = x.DisplayText, Value = x.Points }));
                            }
                        }

                        model.Groups.Add(group);
                    }

                    //creating the question
                    RenderQuestion newQuestion = new RenderQuestion { FieldId = field.Id, Text = field.Text, AllowComment = field.AddComment };

                    //adding specific answers
                    if (!field.SameAnswersForAll && field.Answers.Any())
                        newQuestion.Answers.AddRange(field.Answers.OrderBy(o => o.SortOrder).Select(x => new RenderAnswer { Id = x.Id, Text = x.DisplayText }));

                    group.Questions.Add(newQuestion);
                }
            }

            return model;
        }

        #region create
    }
}

1 Ответ

0 голосов
/ 08 мая 2018

ПРИМЕЧАНИЕ Обновлен раздел скрипта

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

Это может быть то, что вы можете изменить, чтобы продолжать двигаться вперед, приведенные ниже фрагменты - это то, что сработало для меня в прошлом, но существует множество мнений, поэтому посмотрите, работает или помогает:

Контроллер

[Route("VerifyForm")]
public IActionResult VerifyForm()
{
    var viewModel = new VerifyModel()
    {
        Store = "",
        DateFrom = DateTime.Now,
    };

    return View(viewModel);
}


/// <summary>
///     Called by a form post
/// </summary>
[HttpPost("VerifyForm")]
public IActionResult VerifyForm(VerifyModel model)
{
    if (!ModelState.IsValid)
    {
        // finding modelstate validation errors
        var errors = ModelState.Values.SelectMany(v => v.Errors);
        foreach (var err in errors)
        {
            _logger.LogError($"Model State err: {err.ErrorMessage}");
        }
        return View(model);
    }

    // do something with the model data

    // . . .

    // return back to the view - or another view - whatever you wish
    return View(model);
}

View

<form asp-action="VerifyForm" asp-controller="Admin" method="post">

    <!-- form fields here -->

    <!-- this triggers the post, called in .js -->
    <input type="submit" id="inputSave" style="display: none" />
</form>

<!-- my visible button bound to in .js -->
<button id="buttonSaveUser" class="btn btn-sm btn-primary" title="Click to save">
    Save
</button>

1020 * Javascript *

Сначала просмотрите мои комментарии о представлении и использовании вами функции отправки, измените свое представление и затем, учитывая, что вы можете просто изменить свой сценарий

Во-вторых, переместите ваш скрипт ниже к точке, которую я видел, по вашему мнению, скрипт не должен запускаться сверху:

<script>

    // initialize the buttons or other ui events
    // for those who complain:  Yes, there are other ways to do things but this is simple

    $(function(){ 

        // I'll assume this is the name of your new button
        // bind to the button this way, get rid of onClick in your view

        $('#buttonSaveUser').on('click', function ()
        {   
            // this is my call to do some other validation, if not valid just exit
            if (ValidateForm() === false) return;

            // triggers the submit button which triggers the post
            $('#inputSave').click();
        });
    });


    function ValidateForm() {
        var isFormValid = true;

        $("#evaluateform input,select").each(function () {
            var FieldId = "span_" + $(this).attr("id");
            if ($.trim($(this).val()).length == 0 || $.trim($(this).val()) == 0) {
                $(this).addClass("highlight");

                //Show required message along with the field
                if ($("#" + FieldId).length == 0) {
                    $("<span class='error' id='" + FieldId + "'>Required</span>").insertAfter(this);
                }
                //If you fill and again make the field empty in that case again show the message
                if ($("#" + FieldId).css('display') == 'none') {
                    $("#" + FieldId).fadeIn(500);
                }
                //$(this).focus();
                isFormValid = false;

            }
            else {
                $(this).removeClass("highlight");
                if ($("#" + FieldId).length > 0) {
                    // Hide the message with the fade out effect
                    $("#" + FieldId).fadeOut(1000);
                }
            }
        });
        return isFormValid;
    }

</script>
  • не используйте submit для запуска скрипта на стороне клиента - он предназначен для запуска формы POST - я бы создал базовую кнопку, которая связана со стороной клиента
  • избавиться от onClick = '' и связать в javascript

Надеюсь, это поможет.

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