MVC 3 - Как это будет работать? - PullRequest
17 голосов
/ 27 апреля 2011

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

Я либо что-то упустил, либо Microsoft действительно испортила MVC. Я работал над проектами Java MVC, и они были чистыми и простыми. Это, однако, полный беспорядок ИМО. Примеры онлайн, такие как NerdDinner и проекты, обсуждаемые на ASP.Net, слишком просты, поэтому они «просто» работают. Извините, если это звучит отрицательно, но это мой опыт до сих пор.

У меня есть хранилище и служба, которая общается с хранилищем. Диспетчеры звонят в сервис.

Мой уровень данных НЕ является постоянным, так как классы были сгенерированы SQL metal. Из-за этого у меня много ненужного функционала. В идеале я хотел бы иметь POCO, но я еще не нашел хорошего способа добиться этого.

* Обновление: Конечно, Microsoft ничего не испортила - я сделал. Я не полностью понимал инструменты, которые были в моем распоряжении. Основным недостатком того, что я сделал, было то, что я выбрал неправильную технологию для сохранения своих сущностей. LINQ to SQL хорошо работает в приложениях с отслеживанием состояния, поскольку контекст данных можно легко отслеживать. Тем не менее, это не относится к ситуации без гражданства. Какой будет правильный выбор? Сначала код Entity Framework или только код работают довольно хорошо, но что более важно, это то, что это не должно иметь значения. MVC или приложения переднего плана не должны знать, как хранятся данные. *

При создании объектов я могу использовать привязку объектов:

[HttpPost]
public ActionResult Create(Customer c)
{
    // Persistance logic and return view
}    

Это прекрасно работает, MVC делает некоторую привязку за сценой, и все "очень хорошо".

Это не было "Jolly Good". Заказчиком была модель предметной области, и, что еще хуже, она зависела от среды персистентности, потому что она была сгенерирована SQL metal. Что бы я сделал сейчас, это разработал модель моей предметной области, которая была бы независимой от хранилищ данных или уровней представления. Затем я создал бы модель представления из моей доменной модели и использовал бы ее вместо этого.

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

    [HttpPost]
    public ActionResult Create(Order o)
    {
        // Persistance logic and return view
    }

Чтобы сохранить заказ, мне нужен Customer или хотя бы CustomerId. CustomerId присутствовал в представлении, но к тому времени, когда он получил метод Create, он потерял CustomerId. Мне не нравится сидеть без дела при отладке кода MVC, так как я не смогу изменить его в среде хостинга в любом случае.

Хорошо, немного стону здесь, извините. Что бы я сделал сейчас, это создаю модель представления под названием NewOrder, или SaveOrder, или EditOrder, в зависимости от того, чего я пытаюсь достичь. Эта модель представления будет содержать все интересующие меня свойства. Автоматическое связывание из коробки, как следует из названия, будет связывать отправленные значения и ничего не будет потеряно. Если мне нужно нестандартное поведение, я могу реализовать свое собственное «связывание», и оно выполнит эту работу.

Альтернативой является использование FormCollection:

[HttpPost]
public ActionResult Create(FormCollection collection)
{
   // Here I use the "magic" UpdateModel method which sometimes works and sometimes doesn't, at least for LINQ Entities.               
}

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

Автосвязывание с моделями вида будет работать большую часть времени. Если это не так, то вы можете переопределить это. Как вы знаете, это всегда будет работать? Вы тестируете его и хорошо спите.

Другой подход, который я попробовал, - это использование ViewModel - объектов-обёрток с правилами валидации. Это звучит как хорошая идея, за исключением того, что я не хочу добавлять аннотации к классам сущностей. Этот подход отлично подходит для отображения данных, но что вы делаете, когда дело доходит до записи данных?

[HttpPost]
public ActionResult Create(CustomViewWrapper submittedObject)
{
    // Here I'd have to manually iterate through fields in submittedObject, map it to my Entities, and then, eventually, submit it to the service/repository.
}    

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

Я что-то упустил или Microsoft MVC3 должен работать таким образом? Я не понимаю, как это упрощает вещи, особенно в сравнении с Java MVC.

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

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

Ответы [ 7 ]

17 голосов
/ 27 апреля 2011

1) В случае сохранения заказа и отсутствия CustomerId.Если у Order есть свойство CustomerId, а у вас строго типизированное представление, вы можете сохранить это обратно к действию вашего контроллера, добавив

@Html.HiddenFor(model => model.CustomerId)

. При этом механизм связывания модели по умолчанию заполнит объектыдля вас.

2) Что касается использования модели представления, я бы рекомендовал этот подход.Если вы используете что-то вроде AutoMapper , вы можете избавиться от некоторых проблем в сценариях с избыточным отображением.Если вы используете что-то вроде Свободная проверка , тогда вы можете красиво разделить вопросы проверки.

Вот хорошая ссылка на общий подход к реализации ASP.NET MVC.

10 голосов
/ 11 мая 2011

Я не думаю, что ваша проблема связана с asp.net MVC, но со всеми компонентами, которые вы решили использовать вместе.

Вы хотите, чтобы это было сыро и просто?

Используйте POCO повсюду и внедряйте хранилище там, где вам нужно.

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

Давайте проясним некоторые заблуждения или, возможно, недопонимание:

  • Вы можете передавать сложные объекты через сообщение в представление. Но вы хотите сделать это только в том случае, если это имеет смысл, см. Следующий пункт
  • Образец, который вы выбрали там, вызывает некоторые тревоги. Принятие данных Клиента или CustomerID для заказа и не проверка авторизации может быть Большой дырой в безопасности. То же самое можно сказать о заказе в зависимости от того, что вы принимаете / разрешаете. Это огромный случай использования ViewModels независимо от POCO, LINQ, Asp.net MVC или Java MVC.
  • Вы можете передавать простые значения, которые не отображаются в сообщении, для просмотра. Это сделано со скрытыми полями (которые asp.net MVC поддерживает очень просто для использования значения модели), и в некоторых сценариях генерирует скрытые поля для вас.
  • Вы никоим образом не обязаны использовать linq2sql с Asp.net MVC. Если вам не хватает того, как вы собираетесь его использовать, отойдите от него. Заметьте, я люблю linq2sql, но как это связано с вашим взглядом на то, что вы можете делать с asp.net mvc, странно.
  • «Я работал над проектами Java MVC, и они были чистыми и простыми». Работа над проектом - это не то же самое, что разработка проекта самостоятельно. Навыки дизайна действительно влияют на то, что вы получаете от всего. Не сказать, что это ваш случай, но просто хотел указать на это, учитывая отсутствие подробностей о том, что вам не хватает в Java MVC.
  • "Мой уровень данных НЕ является независимым от постоянства, так как классы были сгенерированы SQL metal. Из-за этого у меня много ненужных функций. В идеале я хотел бы иметь POCO, но я не нашел хорошего способа чтобы достичь этого еще ". Вы выбрали не ту технологию, linq2sql не соответствует этим требованиям. Это не было проблемой в проектах, которые я использовал, но все спроектировано таким образом, что менее привязано к его специфике, чем кажется. Тем не менее, просто перейти к чему-то другому. Кстати, вы должны были поделиться тем, что вы использовали с Java MVC.
  • "CustomerId присутствовал в представлении, но к тому времени, когда он получил метод Create, он потерял CustomerId." Если свойство в порядке, вы можете поспорить, что ваш код содержит ошибку. Теперь, это был бы совершенно другой реальный вопрос, почему он не использует CustomerId / такой вопрос придет: ваш класс Customer, View, ответы, которые вы передаете в View ... будут включать, но Не ограничивайтесь: проверьте исходный HTML-код в браузере, чтобы увидеть, какое значение вы действительно публикуете с источником (альтернативно используйте fiddler, чтобы увидеть то же самое), убедитесь, что CustomerId действительно имеет значение, когда вы передаете его в View.
  • Вы сказали: "" волшебный "метод UpdateModel, который иногда работает, а иногда нет". Это не волшебство, вы можете увидеть, что он делает, и, конечно, найти информацию о нем. Что-то не так в информации, которую вы публикуете, моя ставка - необязательные поля или неправильные значения для информации, которая анализируется ... просмотры поддерживают добавление проверок для этого. Без проверок этого может не хватать.
  • Вы сказали в комментарии: «После вызова UpdateModel я не могу явно установить CustomerId, мне придется извлечь объект клиента, а затем назначить его заказу, который выглядит как накладные расходы, как все, что яneed is CustomerId "... вы принимаете CustomerId, который вводится пользователем (даже если это скрытое поле), вы действительно хотите проверить этот ввод.Кроме того, вы противоречите себе, вы заявляете, что вам просто нужен CustomerId, но затем вы говорите, что вам нужен полный объект Customer, связанный с привязкой заказа.Что это, если вы привязываете только CustomerId, вам все равно нужно получить этого Customer и присвоить его свойству.Никакого волшебства нет кроме сцен ...
  • Также в комментарии: «Обновление модели - это то, чего я сейчас полностью избегаю, так как не знаю, как она будет вести себя с сущностями LINQ. В модели представленияКласс Я создал конструктор, который преобразует сущность LINQ в мою модель представления. Это уменьшило количество кода в контроллере, но все еще не выглядит правильным ".Причина использования ViewModel (или EditModel) не в том, что это linq2sql ... а в том, что, среди многих других причин, вы выставляете модель, которая позволяет манипулировать не только тем, что вы на самом деле хотите позволить пользователю изменять.Раскрытие необработанной модели, если в ней есть поля, которые пользователь не должен изменять, является реальной проблемой.
4 голосов
/ 27 апреля 2011

Если ваш взгляд правильно определен, вы можете легко сделать это>

    [HttpPost]
    public ActionResult Create(Order o, int CustomerId)
    {
        //you got the id, life back to jolly good (hopefully)
        // Persistance logic and return view
    }

EDIT:

как упомянуто Attadieni, под правильным представлением я имел в виду, что у вас есть что-то подобное внутри тега формы>

@Html.HiddenFor(model => model.CustomerId)

ASP.NET MVC автоматически свяжется с соответствующими параметрами.

3 голосов
/ 10 мая 2011

Я, должно быть, упускаю проблему.

У вас есть контроллер Order с действием Create, как вы сказали:

public class OrderController()
{
    [HttpGet]
    public ViewResult Create()
    {
        var vm = new OrderCreateViewModel { 
            Customers = _customersService.All(),
            //An option, not the only solution; for simplicities sake
            CustomerId = *some value which you might already know*; 
            //If you know it set it, if you don't use another scheme.
        }                             
        return View(vm);    
    }

    [HttpPost]
    public ActionResult Create(OrderCreateViewModel model)
    {
        // Persistance logic and return view
    }  
}

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

public class OrderCreateViewModel 
{
    // a whole bunch of order properties....
    public Cart OrderItems { get; set; }
    public int CustomerId { get; set; }

    // Different options
    public List<Customer> Customers { get; set; } // An option
    public string CustomerName { get; set; } // An option to use as a client side search
}

В вашем представлении есть раскрывающийся список клиентов, который вы можете добавить в качестве свойства к модели представления или в текстовое поле, к которому вы подключаетесь к поиску на стороне сервера через JQuery, где вы можете установить скрытое поле CustomerId, когда совпадение сделал, однако вы решили сделать это. И если вы уже знаете, что customerId заблаговременно (что, по-видимому, подразумевается в некоторых других публикациях), просто установите его в viewmodel и обойдите все вышеперечисленное.

У вас есть все данные вашего заказа. У вас есть идентификатор клиента, прикрепленный к этому заказу. Тебе хорошо идти.

"Чтобы сохранить заказ, мне нужен Customer или хотя бы CustomerId. CustomerId присутствовал в представлении, но к тому времени, когда он получил метод Create, он потерял CustomerId."

Что? Зачем? Если CustomerId был в представлении, установленном и отправленном назад, он находится в модели для метода HttpPost Create, который именно там, где вам нужно. Что вы имеете в виду, что он потерян?

ViewModel сопоставляется с объектом Model типа order. Как и предполагалось, использование AutoMapper полезно ...

[HttpPost]
public ActionResult Create(OrderCreateViewModel model)
{
    if(!ModelState.IsValid)
    {
      return View(model);
    }   

    // Persistance logic and return view

    var orderToCreate = new Order();

    //Build an AutoMapper map
    Mapper.CreateMap<OrderCreateViewModel, Order>();

    //Map View Model to object(s)
    Mapper.Map(model, orderToCreate);       

    //Other specialized mapping and logic

    _orderService.Create(orderToCreate);

    //Handle outcome. return view, spit out error, etc.
}  

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

И все готово. Если вы не хотите использовать аннотации данных для проверки, хорошо, сделайте это на уровне обслуживания, используйте упомянутую библиотеку проверки данных, что бы вы ни выбрали. Как только вы вызываете метод Create () вашего сервисного уровня со всеми данными, все готово. Где разъединение? Чего нам не хватает?

Ответ Атаддеини верный, я просто пытаюсь показать немного больше кода. Upvote ataddeini

2 голосов
/ 09 мая 2011

Если идентификатор клиента уже присутствует в модели заказа (в этом примере), он должен быть доступен без расширения сигнатуры метода. Если вы просматриваете источник в представлении, правильно ли указан идентификатор клиента в скрытом поле формы? Используете ли вы атрибут [Bind] в классе модели Order и случайно не заполняете идентификатор клиента?

1 голос
/ 10 мая 2011

если вы используете сервисы (иначе, сервисный уровень, бизнес-фасад), для обработки, скажем, OrderModel, вы можете извлечь Interface и получить ваш ViewModel / DTO для его реализации, чтобы вы могли передать ViewModel /DTO к сервису.

Если вы используете репозитории для прямого управления данными (без уровня обслуживания) в контроллере, то вы можете сделать это как старый добрый способ загрузки объекта из репозитория и затем выполнитьUpdateModel на нем.

[HttpPost]
public ActionResult Create(string customerCode, int customerId, Order order)
{
    var cust = _customerRepository.Get(customerId);
    cust.AddOrder(order);//this should carry the customerId to the order.CustomerId
}

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

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

1 голос
/ 09 мая 2011

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

Попробуйте следовать этому примеру.

1) GET действие перед отправкой в ​​View, допустим, вы назначаете CustomerID на этом этапе.

public ActionResult Create()
        {
            var o = new Order();
            o.CustomerID = User.Identity.Name; // or any other wher you store the customerID
            return View(o);
        }

2) В представлении, если вы не используете какой-либо элемент управления для CustomerID, например текстовое поле, выпадающий список и т. Д., Вы должны использовать скрытое поле, чтобы сохранить значение.

@using (Html.BeginForm())
    {
        @Html.HiddenFor(m => m.CustomerID)

        <label>Requested Date:</label>
        @Html.TextBoxFor(m => m.DateRequested)

        ...
    }

3) Наконец, действие POST для получения и сохранения заказа. Здесь, поскольку CustomerID был сохранен в скрытом значении, связыватель моделей автоматически поместит все значения формы в объект Order o, тогда вам просто нужно использовать методы CRUD и сохранить его.

[HttpPost]
public ActionResult Create(Order o)
        {
            return View();
        }

Для этого может быть два подхода: один подразумевает сохранение всех значений модели, даже если они не используются в представлении, а другой - сохранение только тех значений, которые используются. Я думаю, что MVC делает правильную вещь, чтобы следовать последним, избегая ненужного хранения большого количества мусора для больших моделей, когда единственная мысль - назвать одно, CustomerName, так или иначе, это может дать вам контроль над тем, какие данные хранить через Весь цикл action-view-action и сохранение памяти.

Для более сложных сценариев, когда не все поля находятся в одной модели, вам нужно использовать ViewModels. Например, для подробных сценариев вы должны создать OrderViewModel, который имеет два свойства: Order o и IEnumerable od, но, опять же, вам потребуется явно использовать значения в представлении или использовать скрытые поля.

В последних выпусках теперь вы можете использовать классы POCO и Code-First, которые делают все чище и проще, вы можете попробовать EF4 + CTP5.

...