Статьи

Создание приложения ASP.NET MVC4 с EF и WebAPI

ASP.NET MVC прошел долгий путь с тех пор, как «The Gu» изложил некоторые идеи во время поездки на самолете на конференцию в 2007 году. Всего за четыре года ASP.NET MVC выпустил четвертый выпуск и предоставляет разработчикам среду это облегчает развитие, оптимизирует процессы и продвигает современные модели.


Прямой прыжок — один из лучших способов освоить новые технологии. Давайте идти вперед и погрузиться прямо в коде!

Я буду использовать Visual Studio 2012 Release Candidate, который доступен здесь . Я также рекомендую загрузить SQL Server 2012, потому что новая Management Studio является крайне необходимым улучшением по сравнению с предыдущими версиями.

Как только VS 2012 будет запущен, создайте новый проект. Перейдите в Файл -> Новый проект и выберите Интернет-приложение . Это не идеальный шаблон, но он сделает работу.

Варианты проекта

Примечание: код для этого демонстрационного приложения находится в репозитории Github . Я не буду разбираться с каждым фрагментом кода в этом приложении, но в конце этого урока вы хорошо разберетесь в приложении MVC4.

Я собираюсь использовать Entity Framework (EF) Code First для модели данных. EF Code First позволяет нам генерировать таблицы базы данных с помощью нескольких простых простых объектов CLR (POCO). Кроме того, EF позволяет нам использовать LINQ to Entities и лямбда-выражения, упрощая поиск и выдачу команд. Победа победа!

Нашим приложением будет обзор сайта для рассмотрения … вещей. Следовательно, модель данных должна включать в себя все необходимые фрагменты для одного обзора. Начнем с класса под названием Review . Запишите следующий класс в свой собственный файл в каталоге Models :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Review.cs
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Models/Review.cs
public class Review
{
    public int Id { get;
     
    [Required]
    public string Content { get;
     
    [Required]
    [StringLength(128)]
    public string Topic { get;
     
    [Required]
    public string Email { get;
     
    [Required]
    public bool IsAnonymous { get;
     
    public int CategoryId { get;
    public virtual Category Category { get;
     
    public virtual IEnumerable<Comment> Comments { get;
}

Класс Review имеет свой Id (первичный ключ), свойство Content для хранения отзыва, Topic такую ​​как название ресторана (или любое название организации), свойство Email и флаг IsAnonymous чтобы IsAnonymous является ли рецензент анонимный. Свойства CategoryId и Category создают отношение внешнего ключа, чтобы связать обзор с Category (например, «Врачи», «Стоматологи» и т. Д.). И, наконец, коллекция объектов Comment .

Теперь напишите класс Comment . Еще раз добавьте новый класс в каталог Models :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
// Comment.cs
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Models/Comment.cs
public class Comment
{
    public int Id { get;
     
    [Required]
    public string Content { get;
     
    [Required]
    public string Email { get;
     
    [Required]
    public bool IsAnonymous { get;
 
    public int ReviewId { get;
    public Review Review { get;
}

Класс комментария имеет свойство Id для первичного ключа, Content комментария, свойство Email и флаг IsAnonymous для пользователей. Затем существуют свойства ReviewId и Review для создания отношения внешнего ключа между комментариями и отзывами.

Последний класс Category . Вот его код:

01
02
03
04
05
06
07
08
09
10
// Category.cs
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Models/Category.cs
public class Category
{
    public int Id { get;
     
    [Required]
    [StringLength(32)]
    public string Name { get;
}

Этот класс говорит само за себя.

Вы, вероятно, заметили широкое использование аннотации данных [Required] в вышеупомянутых классах. Они обозначают необнуляемое поле в базе данных и обеспечивают проверку позже в будущем. Вы также можете создать свои собственные атрибуты проверки, если хотите. В дополнение к [Required] , мы также использовали virtual ключевое слово для некоторых свойств; эти свойства обозначают отношения внешнего ключа с другими таблицами.

Чтобы создать соответствующие таблицы для этих классов, вам нужно создать класс DbContext . Следующий код создает класс контекста с именем ReviewedContext :

01
02
03
04
05
06
07
08
09
10
11
12
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Models/ReviewedContext.cs
public class ReviewedContext : DbContext
{
    public DbSet<Review> Reviews { get;
    public DbSet<Category> Categories { get;
    public DbSet<Comment> Comments { get;
 
    public ReviewedContext()
    {
        Configuration.ProxyCreationEnabled = false;
    }
}

EF Code First позволяет нам генерировать таблицы базы данных с помощью нескольких простых простых объектов CLR (POCO).

Каждое свойство в этом классе соответствует таблице при создании базы данных. Configuration.ProxyCreationEnabled = false; гарантирует, что сущности извлекаются как объекты их соответствующих классов, а не как прокси, что значительно упрощает отладку.

Далее мы настраиваем инициализатор базы данных. Инициализатор гарантирует, что база данных создается правильно, когда модель данных претерпевает какие-либо изменения. Без инициализатора вам придется вручную удалять базу данных, если вы вносите изменения в один из ваших POCO. Есть несколько различных типов инициализаторов на выбор: DropCreateDatabaseAlways и DropCreateDatabaseIfModelChanges . Имена говорят сами за себя. Мы собираемся использовать DropCreateDatabaseIfModelChanges .

DropCreateDatabaseAlways и DropCreateDatabaseIfModelChanges имеют побочный эффект: они DropCreateDatabaseIfModelChanges таблицы (и, следовательно, данные) в базе данных при изменении структуры модели. Но EF Code First предоставляет третий способ создания баз данных: миграция . Эта новая функция отслеживает изменения в базе данных и не теряет данные при изменении классов POCO.

Вот код для нашего инициализатора:

1
2
3
4
5
6
7
8
9
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Models/ReviewedContextInitializer.cs
// http://slipsum.com/
public class ReviewedContextInitializer : DropCreateDatabaseIfModelChanges<ReviewedContext>
{
    protected override void Seed(ReviewedContext context)
    {
        // Use the context to seed the db.
    }
}

Класс ReviewedContextInitializer переопределяет метод Seed() . Это дает нам возможность заполнить нашу базу данных некоторыми тестовыми данными. Теперь нам нужно посетить файл Global.asax и добавить следующую строку в метод Application_Start() :

1
2
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Global.asax.cs
Database.SetInitializer(new ReviewedContextInitializer());

Давайте создадим несколько репозиториев для извлечения данных из базы данных, и мы продолжим настройку внедрения зависимостей (DI) с помощью Ninject . Если вы точно не знаете, что такое DI или Inversion of Control (IoC), найдите время, чтобы прочитать эту статью .

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

1
2
3
4
5
6
7
8
9
public class Foo
{
    private Bar _bar;
     
    public Foo()
    {
        _bar = new Bar();
    }
}

Этот код создает класс с именем Foo . Это зависит от функциональности объекта типа Bar , а объект Bar создается внутри класса Foo . Это может быть трудно поддерживать и модульное тестирование, потому что:

  • Foo и Bar тесно связаны. В результате техническое обслуживание не является идеальным.
  • Foo зависит от конкретной реализации Bar , что затрудняет юнит-тестирование.

Этот код может быть улучшен с помощью нескольких модификаций. Взгляните на пересмотренный класс Foo :

1
2
3
4
5
6
7
8
9
public class Foo
{
    private IBar _bar;
     
    public Foo(IBar bar)
    {
        _bar = bar;
    }
}

Всего за четыре года ASP.NET MVC выпустила четвертый релиз …

Теперь класс Foo не зависит от конкретной реализации Bar . Вместо этого объект класса, реализующий интерфейс IBar передается в Foo через конструктор последнего. Такой подход значительно повышает удобство сопровождения, а также позволяет нам внедрять любой объект IBar упрощая модульное тестирование нашего кода.

С этим кратким описанием, давайте запустим Ninject. Install-Package Ninject.MVC3 консоль диспетчера пакетов и запустите Install-Package Ninject.MVC3 . Это добавит Ninject в наш проект.

Первый репозиторий, который мы создадим, это ReviewsRepository , и он будет реализовывать интерфейс IReviewRepository . Вот интерфейс:

01
02
03
04
05
06
07
08
09
10
11
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Models/Abstract/IReviewRepository.cs
public interface IReviewRepository
{
    Review Get(int id);
    IQueryable<Review> GetAll();
    Review Add(Review review);
    Review Update(Review review);
    void Delete(int reviewId);
    IEnumerable<Review> GetByCategory(Category category);
    IEnumerable<Comment> GetReviewComments(int id);
}

Этот интерфейс гарантирует, что наши репозитории отзывов обеспечивают основные операции CRUD. Мы также получаем утилиту для поиска отзывов по определенной категории, а также для получения комментариев к данному отзыву. Теперь давайте напишем конкретный класс, реализующий этот интерфейс:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Models/Repos/ReviewRepository.cs
public class ReviewRepository : IReviewRepository
{
    private ReviewedContext _db { get;
 
    public ReviewRepository()
        :this (new ReviewedContext())
    {
         
    }
 
    public ReviewRepository(ReviewedContext db)
    {
        _db = db;
    }
 
    public Review Get(int id)
    {
        return _db.Reviews.SingleOrDefault(r => r.Id == id);
    }
 
    public IQueryable<Review> GetAll()
    {
        return _db.Reviews;
    }
 
    public Review Add(Review review)
    {
        _db.Reviews.Add(review);
        _db.SaveChanges();
        return review;
    }
 
    public Review Update(Review review)
    {
        _db.Entry(review).State = EntityState.Modified;
        _db.SaveChanges();
        return review;
    }
 
    public void Delete(int reviewId)
    {
        var review = Get(reviewId);
        _db.Reviews.Remove(review);
    }
 
    public IEnumerable<Review> GetByCategory(Category category)
    {
        return _db.Reviews.Where(r => r.CategoryId == category.Id);
    }
 
    public IEnumerable<Comment> GetReviewComments(int id)
    {
        return _db.Comments.Where(c => c.ReviewId == id);
    }
}

WebAPI — это MVC-подобная инфраструктура, которую мы можем использовать для простого создания RESTful API …

Этот репозиторий использует объект ReviewedContext который хранится как переменная класса. Это позволяет нам использовать LINQ в любом из методов репозитория, упрощая взаимодействие с базой данных.

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

Одним из наиболее важных мест для нашего кода является папка App_Start , которая содержит файл с именем NinjectCommonWeb.cs (установка Ninject автоматически добавляет этот файл в App_Start ). Этот файл содержит статический класс NinjectWebCommon и имеет метод RegisterServices() . В этом методе добавьте следующий код:

1
2
3
4
5
6
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/App_Start/NinjectWebCommon.cs
kernel.Bind<IReviewRepository>().To<ReviewRepository>();
kernel.Bind<ICategoriesRepository>().To<CategoriesRepository>();
kernel.Bind<ICommentsRepository>().To<CommentsRepository>();
 
GlobalConfiguration.Configuration.DependencyResolver = new NinjectResolver(kernel);

Первые три оператора связывают интерфейс с конкретной реализацией интерфейса, а четвертая строка устанавливает DI для WebAPI (функция, описанная в вышеупомянутой статье).

Давайте теперь создадим контроллеры для API. WebAPI — это MVC-подобная инфраструктура, которую мы можем использовать для простого создания службы RESTful, и она может запускаться внутри приложения MVC4, в своем собственном проекте или может размещаться вне IIS. Но это не все; у него есть много других функций, таких как: согласование содержимого (для автоматической сериализации данных в любой запрошенный формат), привязка модели, проверка и многое другое .

Сначала нам нужно создать конечную точку с помощью WebAPI, и мы делаем это путем создания класса, который наследует ApiController . Начать с этим довольно легко. В Visual Studio 2012 появилась новая функция, которая создает новый частично защищенный контроллер.

Создать ApiController

Это создаст класс контроллера с несколькими методами, уже определенными для вас. Вот пример:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// GET api/default1
public IEnumerable<string> Get()
{
    return new string[] { «value1», «value2» };
}
 
// GET api/default1/5
public string Get(int id)
{
    return «value»;
}
 
// POST api/default1
public void Post(string value)
{
}
 
// PUT api/default1/5
public void Put(int id, string value)
{
}
 
// DELETE api/default1/5
public void Delete(int id)
{
}

Имена методов соответствуют глаголу HTTP, который они представляют. Теперь мы создадим класс ReviewsController . Код немного длинный, но довольно простой.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Controllers/ReviewsController.cs
public class ReviewsController : ApiController
{
    private ICategoriesRepository _categoriesRepository { get;
    private IReviewRepository _reviewRepository { get;
 
    public ReviewsController(IReviewRepository reviewRepository, ICategoriesRepository categoriesRepository)
    {
        _reviewRepository = reviewRepository;
        _categoriesRepository = categoriesRepository;
    }
 
    // GET api/review
    public IEnumerable<Review> Get()
    {
        var reviews = _reviewRepository.GetAll();
        return reviews;
    }
 
    // GET api/review/5
    public HttpResponseMessage Get(int id)
    {
        var category = _reviewRepository.Get(id);
        if (category == null)
        {
            return Request.CreateResponse(HttpStatusCode.NotFound);
        }
        return Request.CreateResponse(HttpStatusCode.OK, category);
    }
 
    // POST api/review
    public HttpResponseMessage Post(Review review)
    {
        var response = Request.CreateResponse(HttpStatusCode.Created, review);
        // Get the url to retrieve the newly created review.
        response.Headers.Location = new Uri(Request.RequestUri, string.Format(«reviews/{0}», review.Id));
        _reviewRepository.Add(review);
        return response;
    }
 
    // PUT api/review/5
    public void Put(Review review)
    {
        _reviewRepository.Update(review);
    }
 
    // DELETE api/review/5
    public HttpResponseMessage Delete(int id)
    {
        _reviewRepository.Delete(id);
        return Request.CreateResponse(HttpStatusCode.NoContent);
    }
 
    // GET api/reviews/categories/{category}
    public HttpResponseMessage GetByCategory(string category)
    {
 
        var findCategory = _categoriesRepository.GetByName(category);
        if (findCategory == null)
        {
            return Request.CreateResponse(HttpStatusCode.NotFound);
        }
        return Request.CreateResponse(HttpStatusCode.OK,_reviewRepository.GetByCategory(findCategory));
    }
 
    // GET api/reviews/comments/{id}
    public HttpResponseMessage GetReviewComments(int id)
    {
 
        var reviewComments = _reviewRepository.GetReviewComments(id);
        if (reviewComments == null)
        {
            return Request.CreateResponse(HttpStatusCode.NotFound);
        }
        return Request.CreateResponse(HttpStatusCode.OK, reviewComments);
    }
}

Этот код использует IReviewRepository и ICategoriesRepository для выполнения соответствующих действий (например, получение данных для запросов GET, добавление данных с помощью запросов POST и т. Д.). Эти репозитории вводятся с помощью Ninject через конструктор Injection .

Если у вас еще нет Fiddler, приобретите его сейчас — даже если вы не являетесь разработчиком .NET.

Обратите внимание, что некоторые методы возвращают разные типы данных. WebAPI дает нам возможность возвращать нестроковый тип данных (например, IEnumerable<Review> ), и он будет сериализовывать объект для отправки в ответе сервера. Вы также можете использовать новый класс HttpResonseMessage для возврата определенного кода состояния HTTP вместе с возвращенными данными. Одним из способов создания объекта HttpResponseMessage является вызов Request.CreateResponse(responseCode, data) .

Мы можем правильно протестировать наш проект WebAPI с помощью такого инструмента, как Fiddler2 . Если у вас еще нет Fiddler, приобретите его сейчас — даже если вы не являетесь разработчиком .NET. Fiddler — это фантастический инструмент отладки HTTP. Запустив Fiddler, нажмите RequestBuilder и введите URL-адрес API, который вы хотите протестировать. Затем выберите подходящий тип запроса. Если вы делаете запрос POST, убедитесь, что вы указали заголовок Content-Type: application/json , а затем поместите правильную структуру JSON в тело запроса. На следующем рисунке показан необработанный запрос JSON POST к URL-адресу api/reviews :

Когда вы отправите запрос, вы увидите что-то вроде следующего изображения:

Обратите внимание, что код состояния запроса POST — 201. WebAPI отлично справляется с возвратом правильного кода состояния для веб-службы RESTfull. Веселитесь с Fiddler2, это фантастический инструмент!

С помощью WebAPI вы можете указать маршрутизацию для контроллеров (так же, как MVC). В MVC4 файл RouteConfig.cs добавляется в папку App_Start . Маршруты для проекта WebAPI аналогичны маршрутам MVC.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/App_Start/RouteConfig.cs
routes.MapHttpRoute(
    name: «GetReviewComments»,
    routeTemplate: «api/reviews/comments/{id}»,
    defaults: new { id = RouteParameter.Optional, controller = «Reviews», action = «GetReviewComments» }
);
 
routes.MapHttpRoute(
    name: «GetByCategories»,
    routeTemplate: «api/reviews/categories/{category}»,
    defaults: new { category = RouteParameter.Optional, controller = «Reviews», action = «GetByCategory» }
);
 
routes.MapHttpRoute(
    name: «DefaultApi»,
    routeTemplate: «api/{controller}/{id}»,
    defaults: new { id = RouteParameter.Optional }
);

Маршрут DefaultApi автоматически создается Visual Studio. Два других маршрута являются пользовательскими и соответствуют определенным методам на контроллере Reviews. Есть много статей и учебных пособий, которые предоставляют хорошую информацию о маршрутизации. Не забудьте проверить это .

Это охватывает многое из того, что может предложить WebAPI. Далее мы напишем несколько методов для отображения данных. Мы немного освоим API, но сейчас мы будем использовать репозитории в нашем HomeController . HomeController был создан Visual Studio; давайте просто изменим его методы для отображения данных. Во-первых, давайте получим список категорий в методе Index .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Controllers/HomeController.cs
private ICategoriesRepository _categoriesRepository { get;
private IReviewRepository _reviewRepository { get;
 
public HomeController(ICategoriesRepository categoriesRepository, IReviewRepository reviewRepository)
{
    _categoriesRepository = categoriesRepository;
    _reviewRepository = reviewRepository;
}
 
public ActionResult Index()
{
    var categories = _categoriesRepository.GetAll();
    return View(categories);
}

Здесь мы продолжаем использовать DI, принимая репозитории в качестве параметров для конструктора HomeController . Ninject автоматически вводит соответствующие нам конкретные классы. Далее, давайте добавим некоторый код в представление Index для отображения категорий:

01
02
03
04
05
06
07
08
09
10
11
12
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Views/Home/Index.cshtml
@model IEnumerable<Reviewed.Models.Category>
 
<h3>Pick a Category:</h3>
<ul class=»round»>
    @foreach(var category in Model)
    {
        <li>
        <a href=»@Url.Action(«Reviews», new { id = @category.Name} )»>@category.Name</a>
        </li>
    }
</ul>

Это создает список категорий, по которым пользователи могут щелкнуть. Теперь добавьте новый метод HomeController который получает Review . Мы будем называть этот метод Reviews , показанные здесь:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Controllers/HomeController.cs
public ActionResult Reviews(string id)
{
    List<Review> reviews = new List<Review>();
    if (!string.IsNullOrWhiteSpace(id))
    {
        reviews = _reviewRepository.GetByCategory(_categoriesRepository.GetByName(id)).ToList();
    }
    else
    {
        reviews = _reviewRepository.GetAll().ToList();
    }
 
    foreach (var review in reviews)
    {
        var comments = _reviewRepository.GetReviewComments(review.Id);
        review.Comments = comments.ToList();
    }
    return View(reviews);
}

Поскольку маршрут для /{controller}/{action}/{id} уже существует, вы можете использовать URL-адрес, например Home/Reviews/Doctors . Механизм маршрутизации передаст «Doctors» в качестве параметра id методу Reviews . Мы используем id в качестве категории и получаем все отзывы, связанные с этой категорией. Однако, если категория не указана, мы просто получаем все отзывы в базе данных. Как только у нас есть все отзывы, мы передаем список отзывов на просмотр. Давайте посмотрим на вид прямо сейчас:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Views/Home/Reviews.cshtml
<div class=»reviews»>
@foreach(var review in Model)
{
    <h3>@review.Topic</h3>
    <p>
        @review.Content
    </p>
     
    var hasComments = review.Comments.Count > 0 ?
     
    <ul class=»@hasComments»>
        @foreach(var comment in review.Comments)
        {
            <li>
                <strong>@(!comment.IsAnonymous ? string.Format(«{0} says,», comment.Email) : «»)</strong>
                <blockquote>@comment.Content</blockquote>
            </li>
        }
    </ul>
}
</div>

Этот код использует новую функцию MVC4. Атрибут class элемента <ul/> не будет отображаться в HTML, если hasComments имеет значение null . Подробнее об этой функции читайте здесь .

Ни одно современное веб-приложение не обходится без JavaScript, и мы будем использовать его для использования нашего сервиса WebAPI. Для этого мы будем использовать Backbone.js; Итак, вперед и снова скачайте Backbone и его зависимость Underscore . Поместите файлы JavaScript в каталог сценариев .

Мы воспользуемся еще одной новой функцией MVC4, которая называется комплектация скриптов. В папке App_Start вы найдете файл BundleConfig.cs . В этом файле вы можете настроить MVC4 для объединения файлов JavaScript. Откройте его и добавьте новый пакет, например так:

1
2
3
bundles.Add(new ScriptBundle(«~/bundles/backbone»).Include(
        «~/Scripts/underscore*»,
        «~/Scripts/backbone*»));

Затем в файле /Views/Shared/_Layout.cshtml добавьте следующее внизу тела страницы:

1
@Scripts.Render(«~/bundles/backbone»)

Это объединит ваши скрипты, если ваше приложение находится в режиме отладки, или оставит их наедине с выключенным.

Код MVC4, который мы написали для получения списка обзора, является прекрасным способом их отображения, но все новые возможности используют Ajax. Итак, давайте проведем рефакторинг кода для использования Backbone.js. С помощью JavaScript мы будем извлекать представления асинхронно после загрузки страницы. Создайте новый файл в папке Scripts именем home.js Добавьте следующий код в этот файл:

01
02
03
04
05
06
07
08
09
10
var Review = Backbone.Model.extend();
var Reviews = Backbone.Collection.extend({
    model: Review,
    url: ‘/api/reviews’
});
var Comment = Backbone.Model.extend({});
var Comments = Backbone.Collection.extend({
    model: Comment,
    url: ‘/api/reviews/comments/’
});

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
var ListReviews = Backbone.View.extend({
    el: ‘.reviews’,
    initialize: function() {
        this.collection.on(‘reset’, this.render, this);
        this.collection.fetch();
    },
    render: function() {
        this.collection.each(this.renderItem, this);
    },
    renderItem: function(model) {
        var view = new ReviewItem({
            model: model
        });
        this.$el.append(view.el);
    }
});

Это представление для всего списка отзывов. Когда вызывается метод fetch() коллекции, он вызывает событие reset и затем вызывает render() . Третий параметр, передаваемый методу on() является областью действия или тем, что будет в render() вызове render() . В методе render() вызовите метод each() коллекции, передав метод renderItem() . Метод renderItem() будет вызываться для каждого элемента в коллекции, генерируя новый ReviewItem для каждого отзыва.

Код для ReviewItem следующий:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var ReviewItem = Backbone.View.extend({
    events: {
        ‘click a’: ‘getComments’
    },
    tagName: ‘li’,
    initialize: function () {
         
        this.template = _.template($(‘#reviewsTemplate’).html());
        this.collection = new Comments();
        this.collection.on(‘reset’, this.loadComments, this);
        this.render();
    },
    render: function () {
        var html = this.template(this.model.toJSON());
        this.$el.append(html);
    },
    getComments: function () {
        this.collection.fetch({
            data: {
                Id: this.model.get(‘Id’)
            }
        });
    },
    loadComments: function () {
        var self = this,
            item;
        this.comments = this.$el.find(‘ul’);
        this.collection.each(function (comment) {
            item = new CommentItem({
                model: comment
            });
            self.comments.append(item.el);
        });
         
        this.$el.find(‘a’).hide();
    }
});

WebAPI — фантастическое дополнение к стеку ASP.NET; Многофункциональный API на основе REST никогда не был таким простым.

Представление ReviewItem отвечает за отображение каждого отдельного отзыва. Метод initialize() компилирует шаблон, используемый для отображения каждого отзыва; этот шаблон находится в элементе <script/> . Backbone извлекает шаблон из элемента <script/> и объединяет его с обзором.

Обработчик события click также настроен для загрузки комментариев для каждого отзыва. При щелчке по getComments() метод getComments() , который getComments() комментарии, передавая Id службе WebAPI. Метод fetch() — это просто абстракция к методу $.ajax jQuery, поэтому обычные параметры Ajax, такие как data , можно передавать в вызове fetch() . Наконец, метод loadComments() сработает и создаст новое представление CommentItem для каждого возвращаемого комментария. tagName в этом представлении гарантирует, что представление создается с <li/> качестве его свойства $el .

Далее, давайте посмотрим на представление CommentItem :

01
02
03
04
05
06
07
08
09
10
11
12
var CommentItem = Backbone.View.extend({
    tagName: ‘li’,
    initialize: function () {
        this.template = _.template($(‘#commentsTemplate’).html());
        this.render();
    },
    render: function () {
        var html = this.template(this.model.toJSON());
        this.$el.html(html);
    }
     
});

Это простой вид, который отображает каждый комментарий. Теперь давайте Review.cshtml представление Review.cshtml следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@model IEnumerable<Reviewed.Models.Review>
 
@section scripts {
    <script type=»text/javascript» src=»/Scripts/home.js»></script>
}
 
<div class=»reviews»>
    <ul></ul>
</div>
 
<script type=»text/html» id=»reviewsTemplate»>
 
    <h3><%= Topic %></h3>
    <p>
        <%= Content %>
    </p>
     
    <a href=»#comments»>Load Comments</a>
    <ul></ul>
 
</script>
 
<script type=»text/html» id=»commentsTemplate»>
<li>
    <strong><%= !IsAnonymous ?
    <blockquote><%= Content %></blockquote>
</li>
</script>

Обратите внимание на @section scripts в приведенном выше коде. Это не новая функция для MVC4, но это отличный инструмент для рендеринга определенных частей JavaScript. В файле _layout.cshtml также есть @RenderSection("scripts", required: false) который отображает раздел, определенный в представлении. Элементы <script/> являются шаблонами Underscore для визуализации содержимого. Они следуют синтаксису Ruby-esque, и все, что находится внутри <% %> , оценивается как оператор. Все, что находится внутри <%= %> будет выводиться в HTML. Циклы и условные операторы могут использоваться следующим образом:

01
02
03
04
05
06
07
08
09
10
11
<ul>
<script type=»text/html» id=»template»>
<% for (var i = 0; i < list.length; i++) { %>
    <li><%= list[i] %></li>
<% } %>
</ul>
 
<% if (someCondition) { %>
    <%= output %>
<% } %>
</script>

Это шаблон. Чтобы использовать это, сделайте это:

1
2
3
4
5
6
var template = _.template($(‘#template’).html());
var html = template({
    someCondition: true,
    output: ‘hello world’,
    list: [‘foo’, ‘bar’, ‘bam’]
});

В Интернете доступно множество шаблонизаторов JavaScript: Handlebars.js , mustache.js и Hogan.js довольно популярны. Будьте уверены, проверьте их и выберите тот, который работает для вас.

WebAPI — фантастическое дополнение к стеку ASP.NET; Многофункциональный API на основе REST никогда не был таким простым. В MVC4 есть много замечательных новых функций. Будьте уверены и проверьте их! Как я уже упоминал ранее, код для этого примера доступен на Github . Вилка это!