Модель ASP.NET MVC 2 с массивом / списком - PullRequest
6 голосов
/ 15 августа 2010

Я нахожусь в процессе создания моего первого сайта в ASP.NET MVC, это своего рода обучение по ходу дела.Но я столкнулся с проблемой, для которой просто не могу найти решение.

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

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

public class AlbumCreateModel
{
    [Required]
    [DisplayName("Title")]
    public string Title { get; set; }

    [DisplayName("Description")]
    public string Description { get; set; }

    [DisplayName("Publish")]
    public bool Public { get; set; }

    [DisplayName("Tags")]
    // Min 2 tags no max
    public List<AlbumTagModel> Tags { get; set; }

    [DisplayName("Songs")]
    // Min 5 songs no max
    public List<AlbumSongModel> Songs { get; set; }
}

public class AlbumTagModel
{
    [Required]
    [DisplayName("Tag")]
    // Regex to test no spaces
    // min 2 characters
    // maximum 15 characters
    public string Tag { get; set; }
}

public class AlbumSongModel
{
    [Required]
    [DisplayName("Title")]
    public string Title { get; set; }

    [Required]
    [DisplayName("Artist")]
    public string Artist { get; set; }

    [DisplayName("Description")]
    public string Description { get; set; }

    [DisplayName("Song Length")]
    public double Length { get; set; }

    [DisplayName("Year")]
    public int Description { get; set; }
}

Вид:

<%@ Page Title="" Language="C#" MasterPageFile="~/App/Views/Shared/MasterPage.Master" Inherits="System.Web.Mvc.ViewPage<album.App.Models.AlbumCreateModel>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Create
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <% using (Html.BeginForm()) { %>
        <%: Html.ValidationSummary(true, "Committing the album was unsuccessful. Please correct the errors and try again.")%>
        <div>
            <fieldset>
                <legend>Album Information</legend>

                <div class="editor-label">
                    <%: Html.LabelFor(m => m.Title) %>
                </div>
                <div class="editor-field">
                    <%: Html.TextBoxFor(m => m.Title)%>
                    <%: Html.ValidationMessageFor(m => m.Title)%>
                </div>

                <div class="editor-label">
                    <%: Html.LabelFor(m => m.Description) %>
                </div>
                <div class="editor-field">
                    <%: Html.TextAreaFor(m => m.Description)%>
                    <%: Html.ValidationMessageFor(m => m.Description)%>
                </div>

                <!-- Tags here -->

                <!-- Songs here -->

                <p>
                    <input type="submit" value="Commit" />
                </p>
            </fieldset>
        </div>
    <% } %>
</asp:Content>

<asp:Content ID="Content3" ContentPlaceHolderID="MetaData" runat="server">
</asp:Content>

Возможное решение:

Модель:

public class PlaylistModel
    {
        [Required]
        [DisplayName("Title")]
        public string Title { get; set; }

        [DisplayName("Description")]
        public string Description { get; set; }

        [DisplayName("Publish")]
        public bool Public { get; set; }

        [DisplayName("Tags")]
        [ListCount(Min = 2)]
        // Min 2 tags no max
        public List<PlaylistTagModel> Tags { get; set; }

        [DisplayName("Songs")]
        [ListCount(Min = 5)]
        public List<PlaylistSongModel> Songs { get; set; }
    }

    public class PlaylistTagModel
    {
        [Required]
        [DisplayName("Tag")]
        // Regex to test no spaces
        // min 2 characters
        // maximum 15 characters
        public string Tag { get; set; }
    }

    public class PlaylistSongModel
    {
        [Required]
        [DisplayName("Title")]
        public string Title { get; set; }

        [Required]
        [DisplayName("Artist")]
        public string Artist { get; set; }

        [DisplayName("Description")]
        public string Description { get; set; }

        [DisplayName("Song Length")]
        public int Length { get; set; }

        [DisplayName("Year")]
        public int Year { get; set; }
    }

Вид:

<%@ Page Title="" Language="C#" MasterPageFile="~/App/Views/Shared/MasterPage.Master" Inherits="System.Web.Mvc.ViewPage<playlist.App.Models.PlaylistModel>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Create
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <% using (Html.BeginForm()) { %>
        <%: Html.ValidationSummary(true, "Committing the playlist was unsuccessful. Please correct the errors and try again.")%>
        <div>
            <fieldset>
                <legend>Playlist Information</legend>

                <div class="editor-label">
                    <%: Html.LabelFor(m => m.Title) %>
                </div>
                <div class="editor-field">
                    <%: Html.TextBoxFor(m => m.Title)%>
                    <%: Html.ValidationMessageFor(m => m.Title)%>
                </div>

                <div class="editor-label">
                    <%: Html.LabelFor(m => m.Description) %>
                </div>
                <div class="editor-field">
                    <%: Html.TextAreaFor(m => m.Description)%>
                    <%: Html.ValidationMessageFor(m => m.Description)%>
                </div>

                <br />
                <%: Html.ValidationMessageFor(m => m.Tags)%>
                <div class="editor-label">
                    <%: Html.LabelFor(m => m.Tags)%>
                </div>
                <div class="editor-field">
                    <%: Html.EditorFor(m => m.Tags) %>
                    <%: Html.Editor("Tags[" + (Model == null ? 0 : Model.Tags.Count) + "]", "PlaylistTagModel")%>
                </div>

                <br />
                <%: Html.ValidationMessageFor(m => m.Songs)%>
                <div class="editor-label">
                    <%: Html.LabelFor(m => m.Songs)%>
                </div>
                <div class="editor-field">
                    <%: Html.EditorFor(m => m.Songs)%>
                    <%: Html.Editor("Songs[" + (Model == null ? 0 : Model.Songs.Count) + "]", "PlaylistSongModel")%>
                </div>

                <p>
                    <input type="submit" value="Commit" />
                </p>
            </fieldset>
        </div>
    <% } %>
</asp:Content>

<asp:Content ID="Content3" ContentPlaceHolderID="MetaData" runat="server">
</asp:Content>

Два шаблона:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<playlist.App.Models.PlaylistSongModel>" %>

<fieldset>
    <legend>Song Information</legend>
    <%: Html.ValidationSummary(true, "Committing this song was unsuccessful. Please correct the errors and try again.")%>

    <div class="editor-label">
        <%: Html.LabelFor(m => m.Title) %>
    </div>
    <div class="editor-field">
        <%: Html.TextBoxFor(m => m.Title)%>
        <%: Html.ValidationMessageFor(m => m.Title)%>
    </div>

    <div class="editor-label">
        <%: Html.LabelFor(m => m.Artist)%>
    </div>
    <div class="editor-field">
        <%: Html.TextBoxFor(m => m.Artist)%>
        <%: Html.ValidationMessageFor(m => m.Artist)%>
    </div>

    <div class="editor-label">
        <%: Html.LabelFor(m => m.Description)%>
    </div>
    <div class="editor-field">
        <%: Html.TextAreaFor(m => m.Description)%>
        <%: Html.ValidationMessageFor(m => m.Description)%>
    </div>

    <div class="editor-label">
        <%: Html.LabelFor(m => m.Length)%>
    </div>
    <div class="editor-field">
        <%: Html.TextBoxFor(m => m.Length)%>
        <%: Html.ValidationMessageFor(m => m.Length)%>
    </div>

    <div class="editor-label">
        <%: Html.LabelFor(m => m.Year)%>
    </div>
    <div class="editor-field">
        <%: Html.TextBoxFor(m => m.Year)%>
        <%: Html.ValidationMessageFor(m => m.Year)%>
    </div>
</fieldset>

Tag:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<playlist.App.Models.PlaylistTagModel>" %>

<span class="tag"><%: Html.TextBoxFor(m => m.Tag)%></span> <%: Html.ValidationMessageFor(m => m.Tag)%>

И, наконец, мой пользовательский валидатор для списков:

public class ListCountAttribute : ValidationAttribute
    {
        public int Min { get; set; }
        public int Max { get; set; }

        public override bool IsValid(object value)
        {
            if (Min == 0 && Max == 0)
                return true;

            if (value == null)
                return false;

            if (!(value is ICollection))
                throw new InvalidOperationException("ListCountAttribute requires underlying property to implement ICollection");

            ICollection countable = value as ICollection;
            if (Min == 0 && Max != 0)
                return countable.Count <= Max;
            else if (Max == 0 && Min != 0)
                return countable.Count >= Min;
            return (countable.Count >= Min) && (countable.Count <= Max);
        }

        public override string FormatErrorMessage(string name)
        {
            if (Min == 0 && Max != 0)
                return "The field set " + name + " can not be larger then " + Max;
            else if (Max == 0 && Min != 0)
                return "The field set " + name + " need to have atleast a count of " + Min;
            return "The field set " + name + " need to between or equal to " + Min + " and " + Max;
        }
    }

Ответы [ 3 ]

7 голосов
/ 15 августа 2010

Создайте папку в / views / shared с именем "EditorTemplates". Это должно быть точно названо.

В этой папке создайте файлы с именами AlbumTagModel.ascx и AlbumSongModel.ascx, строго типизированные для соответствующих моделей.

Добавьте поля ввода в эти файлы, но не оборачивайте их в теги формы.

Вернитесь на страницу просмотра:

<%: Html.EditorFor(m => m.Tags)%>

и

<%: Html.EditorFor(m => m.Songs)%>

Теперь вуаля!

При рендеринге теги ввода будут подписаны в соответствии с вашим списком. EditorFor будет зацикливаться и визуализировать все самостоятельно. Когда вы публикуете строго типизированную модель AlbumViewModel, ваши списки будут правильно привязаны к своим исходным позициям.

Чтобы добавить новые песни / теги, добавьте в вашу AlbumViewModel следующее:

public AlbumTagModel NewTagModel {get;set;}

и добавьте для него дополнительный EditorFor ().

Когда ваша модель отправляет сообщение, если NewTagModel действителен, добавьте его в список и перезапустите представление.

1 голос
/ 15 августа 2010

Вы должны написать собственный атрибут проверки, чтобы поддержать это требование.

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

public class AtLeastOneRequiredAttribute : ValidationAttribute
{
  public override bool IsValid(object value)
  {
    if (value == null)
      return false;

    if (!(value is ICountable))
      throw new InvalidOperationException("AtLeastOneRequiredAttribute requires underlying property to implement ICountable");

    ICountable countable = value as ICountable;
    return countable.Count >= 1;
  }
}

Мы используем наши собственные "дочерние модели" вместо общих списков и заставляем их реализовывать ICountable, который является утилитарным интерфейсом в нашей среде.,Вы просто проверите, чтобы убедиться, что в вашем значении реализован IList, затем вызовите (value as IList).Count.

. Для общего минимума вместо «По крайней мере, один» определите свойство Min.

Hopeесли вас направят в правильном направлении, пишите, если у вас есть другие вопросы.

0 голосов
/ 20 октября 2010

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

public class Candidate
    {
        public Candidate()
        {
            this.References = new List<Reference>();
        }
        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime? DateOfBirth { get; set; }
        [UIHint("Reference")]  
        public List<Reference> References { get; set; }

    }

public class Reference
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Institution { get; set; }
        public string Email { get; set; }
        public string Phone { get; set; }
        public string Position { get; set; }
    }

Я создал шаблон в Views / Shared / TemplateEditors и создал строго типизированный частичный просмотр Register.ascx.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Models.Reference>" %>
<%:Html.TextBoxFor(model=>model.Name) %>
<%:Html.TextBoxFor(model=>model.Email) %>
<%:Html.TextBoxFor(model=>model.Institution) %>
<%:Html.TextBoxFor(model=>model.Position) %>
<%:Html.TextBoxFor(model=>model.Phone) %>

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

 <%: Html.EditorFor(m => m.References)%>

Когда я запускаю проект, я получаю

 InvalidOperationException: The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[Models.Reference]', but this dictionary requires a model item of type 'Models.Reference'.

Ребята, вы видите что-то не так с реализацией?

Спасибо.

...