Проверка ASP.NET MVC 2 на динамической странице

У меня есть мастер регистрации в моем приложении asp.net mvc 2 с несколькими страницами. На первой странице у меня должна быть основная форма данных лиц, вовлеченных в процесс. У меня должно быть 3 текстовых поля, помеченных как Имя, Фамилия и Адрес, и 1 флажок с текстом «Добавить другого человека». Когда пользователь нажимает на переключатель, появляются новые текстовые поля с новым переключателем, поэтому мы можем добавить несколько человек в одну и ту же форму. Теоретически мы должны иметь возможность вставить как можно больше людей. Все поля являются обязательными, поэтому в сводке проверки вверху страницы у меня должно быть что-то вроде «Пожалуйста, введите имя второго человека» или что-то в этом роде. У меня есть класс DTO:

public class Person
{
     public string FullName { get; set; }
     public string LastName { get; set; }
     public string Address{ get; set; }
}

и я полагаю, что моя модель для этой страницы должна быть List<Person>, и я бы добавил html для новых людей с помощью javascript / jQuery. Пожалуйста, помогите мне здесь, как мне проверить эту динамическую страницу? Я могу пройти через этот мастер с помощью кнопок «Сохранить» и «Назад», а также мы должны иметь возможность убрать любой переключатель на странице, и этот конкретный человек должен исчезнуть, а валидатор больше не должен его ловить. Весь мой мастер использует проверку на стороне сервера (DataAnnotations), и я не хочу использовать проверку клиента. Заранее спасибо.

ОБНОВИТЬ:

Мне нужна еще помощь. Я хочу расширить класс Person новым свойством:

public int Percent { get; set; } 

и мне нужна проверка сервера при отправке, если сумма всех процентов в каждом из лиц в IEnumerable<Person> равна 100. Могу ли я создать собственный атрибут для этого и как? Моя модель - это общий список, я не могу применить к ней [CustomAttribute], верно?
Кроме того, у меня должна быть сводка проверки вверху страницы, а не сразу после каждого из входных данных. Я поставил: <%:Html.ValidationSummary(false, "Please correct the following and resubmit the page:")%> Есть ли способ установить разные сообщения проверки для каждого из лиц? Спасибо


person Cemsha    schedule 02.01.2013    source источник


Ответы (1)


Прежде чем приступить к реализации этой задачи, я настоятельно рекомендую вам прочитать Редактирование списка переменной длины в стиле ASP.NET MVC 2 от Стивена Сандерсона.

Готовый?

Хорошо, теперь мы можем перейти к реализации.

Первым делом нужно определить нашу модель представления для задачи. Он у вас уже есть, просто определите для него соответствующие правила проверки:

public class Person
{
    [Required]
    public string FullName { get; set; }

    [Required]
    public string LastName { get; set; }

    [Required]
    public string Address{ get; set; }
}

и я полагаю, что моей моделью для этой страницы должен быть список

Да, конечно.

Итак, давайте продолжим и создадим наш PersonsController:

public class PersonsController : Controller
{
    public ActionResult Index()
    {
        var model = new[] 
        {
            new Person()
        };
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(IEnumerable<Person> persons)
    {
        if (!ModelState.IsValid)
        {
            return View(persons);
        }

        // To do: do whatever you want with the data
        // In this example I am simply dumping it to the output
        // but normally here you would update your database or whatever
        // and redirect to the next step of the wizard
        return Content(string.Join(Environment.NewLine, persons.Select(p => string.Format("name: {0} address: {1}", p.FullName, p.Address))));
    }

    public ActionResult BlankEditorRow()
    {
        return PartialView("_PersonEditorRow", new Person());
    }
}

А теперь давайте определим представление (~/Views/Persons/Index.cshtml):

@model IEnumerable<Person>

@using (Html.BeginForm())
{
    <div id="editorRows">
        @foreach (var item in Model)
        {
            Html.RenderPartial("_PersonEditorRow", item);
        }
    </div>    

    @Html.ActionLink(
        "Add another person", 
        "BlankEditorRow", 
        null, 
        new { id = "addItem" }
    )

    <p>
        <button type="submit">Next step</button>
    </p>
}

<script type="text/javascript">
    $('#addItem').click(function () {
        $.ajax({
            url: this.href,
            cache: false,
            success: function (html) { $('#editorRows').append(html); }
        });
        return false;
    });

    $(document).delegate('a.deleteRow', 'click', function () {
        $(this).parents('div.editorRow:first').remove();
        return false;
    });
</script>

и соответствующий частичный вид (~/Views/Persons/_PersonEditorRow.cshtml):

@model Person

<div class="editorRow">
    @using(Html.BeginCollectionItem("persons")) 
    {
        <div>
            @Html.LabelFor(x => x.FullName)
            @Html.EditorFor(x => x.FullName)
            @Html.ValidationMessageFor(x => x.FullName)
        </div>
        <div>
            @Html.LabelFor(x => x.LastName)
            @Html.EditorFor(x => x.LastName)
            @Html.ValidationMessageFor(x => x.LastName)
        </div>
        <div>
            @Html.LabelFor(x => x.Address)
            @Html.EditorFor(x => x.Address)
            @Html.ValidationMessageFor(x => x.Address)
        </div>

        <a href="#" class="deleteRow">delete</a>
    }
</div>

Примечание. Используемый здесь помощник Html.BeginCollectionItem взят из сообщения в блоге Стивена Сандерсона, на которое я ранее ссылался в своем ответе и который вы уже прочитали и с которым вы знакомы. Вот исходный код для полноты:

public static class HtmlPrefixScopeExtensions
{
    private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";

    public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
    {
        var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
        string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();

        // autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
        html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));

        return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
    }

    public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
    {
        return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
    }

    private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
    {
        // We need to use the same sequence of IDs following a server-side validation failure,  
        // otherwise the framework won't render the validation error messages next to each item.
        string key = idsToReuseKey + collectionName;
        var queue = (Queue<string>)httpContext.Items[key];
        if (queue == null)
        {
            httpContext.Items[key] = queue = new Queue<string>();
            var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
            if (!string.IsNullOrEmpty(previouslyUsedIds))
                foreach (string previouslyUsedId in previouslyUsedIds.Split(','))
                    queue.Enqueue(previouslyUsedId);
        }
        return queue;
    }

    private class HtmlFieldPrefixScope : IDisposable
    {
        private readonly TemplateInfo templateInfo;
        private readonly string previousHtmlFieldPrefix;

        public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
        {
            this.templateInfo = templateInfo;

            previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
            templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
        }

        public void Dispose()
        {
            templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
        }
    }
}

ОБНОВИТЬ:

Плохо, я только что заметил, что ваш вопрос помечен тегом asp.net-mvc-2. Поэтому я думаю, что мои взгляды на Razor не применимы к вашему случаю. Тем не менее, все остальное должно работать так же. Все, что вам нужно сделать, это обновить представления, чтобы они использовали механизм представления WebForms:

Вот ~/Views/Persons/Index.aspx:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Person>>" %>
<% using (Html.BeginForm()) { %>
    <div id="editorRows">
        <% foreach (var item in Model) { %>
            <% Html.RenderPartial("_PersonEditorRow", item); %>
        <% } %>
    </div>    

    <%= Html.ActionLink(
        "Add another person", 
        "BlankEditorRow", 
        null, 
        new { id = "addItem" }
    ) %>

    <p>
        <button type="submit">Next step</button>
    </p>
<% } %>

<script type="text/javascript">
    $('#addItem').click(function () {
        $.ajax({
            url: this.href,
            cache: false,
            success: function (html) { $('#editorRows').append(html); }
        });
        return false;
    });

    $(document).delegate('a.deleteRow', 'click', function () {
        $(this).parents('div.editorRow:first').remove();
        return false;
    });
</script>

и, наконец, часть (~/Views/Persons/_PersonEditorRow.ascx):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Person>" %>
<div class="editorRow">
    <% using(Html.BeginCollectionItem("persons")) { %>
        <div>
            <%= Html.LabelFor(x => x.FullName) %>
            <%= Html.EditorFor(x => x.FullName) %>
            <%= Html.ValidationMessageFor(x => x.FullName) %>
        </div>
        <div>
            <%= Html.LabelFor(x => x.LastName) %>
            <%= Html.EditorFor(x => x.LastName) %>
            <%= Html.ValidationMessageFor(x => x.LastName) %>
        </div>
        <div>
            <%= Html.LabelFor(x => x.Address) %>
            <%= Html.EditorFor(x => x.Address) %>
            <%= Html.ValidationMessageFor(x => x.Address) %>
        </div>

        <a href="#" class="deleteRow">delete</a>
    <% } %>
</div>
person Darin Dimitrov    schedule 02.01.2013
comment
Спасибо, Дарин, все работает нормально. Мне нужна дополнительная помощь. Я хочу расширить класс Person новым свойством: public int Percent { get; set; }, и мне нужна проверка сервера при отправке, если сумма всех процентов в каждом из лиц в IEnumerable ‹Person› равна 100. Могу ли я создать настраиваемый атрибут для это и как? Моя модель - это общий список, я не могу применить к ней [CustomAttribute], верно? - person Cemsha; 06.01.2013
comment
Кроме того, у меня должна быть сводка проверки вверху страницы, а не сразу после каждого ввода. Я поставил: <%:Html.ValidationSummary(false, "Please correct the following and resubmit the page:")%> Есть ли способ установить разные сообщения проверки для каждого человека? Спасибо. - person Cemsha; 06.01.2013