Должен ли я использовать viewmodel здесь? - PullRequest
5 голосов
/ 04 февраля 2011

Допустим, у меня есть две модели: Thingy и Status.Thingy имеет Status, а в статусе много вещей.Это типичное «отношение объекта и типа объекта».

У меня есть представление, где я просто хочу количество штук в каждом статусе.Или в основном список Status.Name и Status.Thingies.Count.Я мог бы сделать именно это, но это " right ", что нужно сделать, чтобы создать модель представления в виде:

ThingiesByStatusViewModel
-StatusName
-StatusThingiesCount

и подключить его к чему-то вроде AutoMapper.

Для такого тривиального примера это, вероятно, не имеет большого значения, но это поможет мне лучше понять правильное «разделение интересов».

Ответы [ 4 ]

11 голосов
/ 04 февраля 2011

Должен ли я использовать модель представления здесь?

Это риторический вопрос?

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

public class ThingiesByStatusViewModel
{
    public string StatusName { get; set; }
    public int StatusThingiesCount { get; set; }
}

и тогда ваш контроллер вернет IEnumerable<ThingiesByStatusViewModel>. Тогда, на ваш взгляд, вы можете просто использовать шаблон отображения:

@Html.DisplayForModel()

и соответствующий шаблон отображения (~/Views/Shared/DisplayTemplates/ThingiesByStatusViewModel.cshtml):

@model AppName.Models.ThingiesByStatusViewModel
<div>
    <span>StatusName: @Model.StatusName</span>
    <span>Number of thingies: @Model.StatusThingiesCount</span>
</div>

Теперь давайте посмотрим на слой отображения. Предположим, что у нас есть следующий домен:

public class Thingy
{ }

public class Status
{
    public string StatusName { get; set; }
    public IEnumerable<Thingy> Thingies { get; set; }
}

и у нас есть экземпляр IEnumerable<Status>.

Определение отображения может выглядеть так:

Mapper
    .CreateMap<Status, ThingiesByStatusViewModel>()
    .ForMember(
        dest => dest.StatusThingiesCount,
        opt => opt.MapFrom(src => src.Thingies.Count())
    );

и, наконец, действие контроллера будет просто:

public ActionResult Foo()
{
    IEnumerable<Status> statuses = _repository.GetStatuses();
    IEnumerable<ThingiesByStatusViewModel> statusesVM = Mapper.Map<IEnumerable<Status>, IEnumerable<ThingiesByStatusViewModel>>(statuses);
    return View(statusesVM);
}
3 голосов
/ 04 февраля 2011

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

1 голос
/ 04 февраля 2011

Ответ - да.Конечно.Почему бы и нет?Это займет всего около 1% вашего кода!

Подумайте об этом:

  1. Цель ViewModel - это контейнер для отправки данных в представление.

  2. Таким образом, он может «формировать» данные, отправляемые в представление, которые могут не соответствовать вашей доменной модели.Например, если Thingies имеет 50 свойств (или столбцов в таблице ...), вам может понадобиться только 3 из этих свойств.

  3. Для предоставления данных формы я использую класс «Сервис».Например, StatusService (связанный интерфейсом для разрешения DI, например, IStatusService).Таким образом, класс Service получает экземпляры ваших репозиториев, предоставляет методы для использования в ваших контроллерах и специальные свойства только для чтения, которые создают ваши ViewModels для упаковки данных для представлений.

Используя этот способДелая вещи, вы можете легко увидеть, что усилия, которые идут на написание ViewModel, бесполезны.С точки зрения строк кода, вероятно, 1 процент.

Хотите доказательства?

Посмотрите на следующее:

Типичным контроллером будет:

Контроллер:

// ПРИМЕЧАНИЕ. ИСПОЛЬЗОВАНИЕ: service.ViewModel

namespace ES.eLearningFE.Areas.Admin.Controllers
{
    public partial class StepEditorController : Controller
    {
        IStepEditorService service;
        public StepEditorController(IStepEditorService service)
        {
            this.service = service;
        }

        [HttpGet]
        public virtual ActionResult List(int IdCourse)
        {
            service.CourseId = IdCourse;
            return View(service.Steps());
        }

        [HttpGet]
        public virtual ActionResult Edit(int IdCourse, int IdStep)
        {
            service.CourseId = IdCourse;
            service.CurrentStepId = IdStep;
            return View(service.ViewModel);
        }

        [HttpPost]
        public virtual ActionResult Edit(CourseStep step)
        {
            service.CourseId = step.CourseId;
            service.CurrentStepId = step.CourseStepId; 
            service.CourseId = step.CourseId;
            try
            {
                UpdateModel(service.CurrentStep);
                service.Save();
                return RedirectToAction(Actions.Edit(step.CourseId, step.CourseStepId));
            }
            catch
            {
                // Refactor notice : empty catch block : Return errors!
            }
            return View(service.ViewModel);
        }

        [HttpGet]
        public virtual ActionResult New(int IdCourse)
        {
            service.CourseId = IdCourse;
            return View(service.ViewModel);
        }
        [HttpPost]
        public virtual ActionResult New(CourseStep step)
        {
            service.CourseId = step.CourseId;            
            if (ModelState.IsValid)
            {
                service.AddStep(step);
                try
                {
                    service.Save();
                    service.CurrentStepId = step.CourseStepId;
                    return View(Views.Edit, service.ViewModel);
                }
                catch
                {
                    // Refactor notice : empty catch block : Return errors!
                }
            }
            return View(service.ViewModel);
        }
    }
}

Служба:

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

// ЗАМЕЧАНИЕ СЛЕДУЮЩЕЙ СОБСТВЕННОСТИ: public StepEditorVM ViewModel

namespace ES.eLearning.Domain.Services.Admin
{
    public class SqlStepEditorService : IStepEditorService
    {
        DataContext db;

        public SqlStepEditorService(DbDataContextFactory contextFactory)
        {
            db = contextFactory.Make();
            CoursesRepository = new SqlRepository<Course>(db);
            StepsRepository = new SqlRepository<CourseStep>(db);
        }

        #region IStepEditorService Members

        public StepEditorVM ViewModel
        {
            get
            {
                if (CurrentStep != null)
                {
                    return new StepEditorVM
                    {
                        CurrentStep = this.CurrentStep,
                        Steps = this.Steps()
                    };
                }
                else // New Step
                {
                    return new StepEditorVM
                    {
                        CurrentStep = new CourseStep(),
                        Steps = this.Steps()
                    };
                }
            }
        }

        public CourseStep CurrentStep
        {
            get
            {
                return FindStep(CurrentStepId, CourseId);
            }
        }

        // Refactor notice : Expose Steps with a CourseId parameter, instead of reading from the CourseId property?
        public List<CourseStep> Steps()
        {
            if (CourseId == null) throw new ApplicationException("Cannot get Steps [CourseId == null]");
            return (from cs in StepsRepository.Query where cs.CourseId == CourseId select cs).ToList();
        }

        // Refactor notice :  Pattern for dealing with null input parameters
        public int ? CourseId { get; set; }
        public int ? CurrentStepId { get; set; }

        public CourseStep FindStep(int ? StepId, int ? CourseId)
        {
            // Refactor notice :  Pattern for dealing with null input parameters
            if (CourseId == null) throw new ApplicationException("Cannot Find Step [CourseId == null]");
            if (CurrentStepId == null) throw new ApplicationException("Cannot Find Step [StepId == null]");
            try
            {
                return (from cs in StepsRepository.Query where ((cs.CourseStepId == StepId) && (cs.CourseId == CourseId)) select cs).First();
            }
            catch
            {
                return null;
            }
        }

        public void AddStep(CourseStep step)
        {
            StepsRepository.Add(step);
        }

        public void DeleteStep(CourseStep step)
        {
            StepsRepository.Delete(step);
        }

        public void Clear()
        {
            CurrentStepId = null;
            CourseId = null;
        }

        public void Save()
        {
            db.SubmitChanges();
        }

        #endregion

        #region Repositories

        private IRepository<Course> CoursesRepository
        {
            get;
            set;
        }

        private IRepository<CourseStep> StepsRepository
        {
            get;
            set;
        }

        #endregion
    }
}

Интерфейс:

И интерфейс будет выглядеть так:

namespace ES.eLearning.Domain.Services.Interfaces
{
    public interface IStepEditorService
    {
        StepEditorVM ViewModel { get; }

        CourseStep CurrentStep { get; }
        List<CourseStep> Steps();
        int ? CourseId { get; set; }
        int ? CurrentStepId { get; set; }
        CourseStep FindStep(int ? StepId, int ? CourseId);
        void AddStep(CourseStep step);
        void DeleteStep(CourseStep step);
        void Clear();
        void Save();
    }
}

Класс ViewModel:

И, наконец, сам класс ViewModel:

namespace ES.eLearning.Domain.ViewModels
{
    public class StepEditorVM
    {
        public CourseStep CurrentStep { get; set; }
        public List<CourseStep> Steps { get; set; }
    }
}

По сравнению со всеми остальными, это ничто.

Так почему бы не сделать это?

Другие биты:

Общий репозиторий:

namespace ES.eLearning.Domain
{
    public class SqlRepository<T> : IRepository<T> where T : class
    {
        DataContext db;
        public SqlRepository(DataContext db)
        {
            this.db = db;
        }

        #region IRepository<T> Members

        public IQueryable<T> Query
        {
            get { return db.GetTable<T>(); }
        }

        public List<T> FetchAll()
        {
            return Query.ToList();
        }

        public void Add(T entity)
        {
            db.GetTable<T>().InsertOnSubmit(entity);
        }

        public void Delete(T entity)
        {
            db.GetTable<T>().DeleteOnSubmit(entity);
        }

        public void Save()
        {
            db.SubmitChanges();
        }

        #endregion
    }
}

IRepository:

namespace Wingspan.Web.Mvc
{
    public interface IRepository<TEntity> where TEntity : class
    {
        List<TEntity> FetchAll();
        IQueryable<TEntity> Query {get;}
        void Add(TEntity entity);
        void Delete(TEntity entity);
        void Save();
    }
}

ПРИМЕЧАНИЕ. Это то, над чем я сейчас работаю, так что это незавершенная работа, которая намного проще, чем будет заключительная вещь, но это, безусловно, первая и вторая итерация, и она дает представлениенасколько структурирована ваша работа.

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

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

Т.е. все происходит очень быстро, и вы точно знаете, что пытаетесь сделать.

Как только вы это сделаете, вы создадите свои Представления и увидите, что произойдет ...

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

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

Весь смысл:

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

1 голос
/ 04 февраля 2011

Да, вам следует использовать ВМ.

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

Если текущий пример тривиален - тогда вам будет легче получить практическое занятие.

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

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