Advertisement
  1. Code
  2. ASP.NET

Desarrollando una aplicación MVC4 en ASP.NET con EF y WebAPI

Scroll to top
Read Time: 21 min

() translation by (you can also view the original English article)

El MVC en ASP.NET ha recorrido un largo camino desde que "The Gu" anotó algunas ideas durante un viaje en avión a una conferencia en 2007. En poco menos de cuatro años, ASP.NET MVC ha visto su cuarta versión, y brinda a los desarrolladores un ambiente que facilita el desarrollo, agiliza los procesos y promueve los patrones modernos.


Navegando

Lanzándose es una de las mejores maneras de manejar una nueva tecnología. ¡Sigamos adelante y sumergámonos en el codec!

Configuración

Usaré Visual Studio 2012 Release Candidate, que está disponible aquí. También recomiendo descargar SQL Server 2012 porque el nuevo Management Studio es una mejora muy necesaria respecto a las versiones anteriores.

Una vez que VS 2012 esté en funcionamiento, continúa y crea un nuevo proyecto. Ve a Archivo -> Nuevo proyecto y elige una aplicación de Internet. No es una plantilla perfecta, pero hará el trabajo.

Project OptionsProject OptionsProject Options

Nota: el código para esta aplicación de demostración se encuentra en un repositorio de Github. No iré a través de cada pieza de código en esta aplicación, pero al final de este tutorial comprenderás bien una aplicación MVC4.

Entity Framework

Voy a usar el código Entity Framework (EF) primero para el modelo de datos. El Código de EF Primero nos permite generar tablas de base de datos con nada más que unos pocos Objetos CLR Antiguos (POCO). Además, EF nos permite usar LINQ para Entidades y expresiones Lambda, lo que facilita la consulta y la emisión de comandos. ¡Una victoria ganada!

Nuestra aplicación será un sitio para revisar... cosas. Por lo tanto, el modelo de datos debe incorporar todos los bits y piezas necesarios para una única revisión. Comenzaremos con una clase llamada Review. Escribe la siguiente clase en tu propio archivo en el directorio Models:

1
2
// Review.cs

3
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Models/Review.cs

4
public class Review
5
{
6
    public int Id { get; set; }
7
    
8
    [Required]
9
    public string Content { get; set; }
10
    
11
    [Required]
12
    [StringLength(128)]
13
    public string Topic { get; set; }
14
    
15
    [Required]
16
    public string Email { get; set; }
17
    
18
    [Required]
19
    public bool IsAnonymous { get; set; }
20
    
21
    public int CategoryId { get; set; }
22
    public virtual Category Category { get; set; }
23
    
24
    public virtual IEnumerable<Comment> Comments { get; set; }
25
}

La clase Review tiene su ID (la clave principal), la propiedad Content para almacenar la revisión, un Topic como el nombre de un restaurante (o cualquier nombre de una organización), una propiedad Email o y una bandera IsAnonymous para indicar si el crítico es anónimo. Las propiedades CategoryId y Category crean una relación de clave externa para vincular una revisión a una categoría (p. Ej., Médicos, dentistas, etc.). Y el último es una colección de objetos Comment.

Ahora escribe la clase Comment. Una vez más, agrega la nueva clase al directorio Models:

1
2
// Comment.cs

3
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Models/Comment.cs

4
public class Comment
5
{
6
    public int Id { get; set; }
7
    
8
    [Required]
9
    public string Content { get; set; }
10
    
11
    [Required]
12
    public string Email { get; set; }
13
    
14
    [Required]
15
    public bool IsAnonymous { get; set; }
16
17
    public int ReviewId { get; set; }
18
    public Review Review { get; set; }
19
}

La clase de comentarios tiene una propiedad Id para la clave principal, el contenido 'Content' del comentario, una propiedad email y una marca de IsAnonymous para los usuarios. Luego están las propiedades ReviewId y Review para crear una relación de clave externa entre los comentarios y las revisiones.

La última es la clase Category. Aquí está su código:

1
2
// Category.cs

3
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Models/Category.cs

4
public class Category
5
{
6
    public int Id { get; set; }
7
    
8
    [Required]
9
    [StringLength(32)]
10
    public string Name { get; set; }
11
}

Esta clase se explica por sí misma.

Probablemente notaste un uso extenso de la anotación de datos [Required] en las clases anteriores. Estos designan un campo no anulable en la base de datos y proporcionan validación más adelante en el futuro. También puedes crear tus propios atributos de validación personalizados si así lo deseas. Además de [Required], también usamos la palabra clave virtual para algunas propiedades; esas propiedades significan relaciones de clave externa con otras tablas.

Para crear las tablas correspondientes para estas clases, deberás crear una clase DbContext. El siguiente código crea una clase de contexto llamada ReviewedContext:

1
2
    // https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Models/ReviewedContext.cs

3
    public class ReviewedContext : DbContext
4
    {
5
        public DbSet<Review> Reviews { get; set; }
6
        public DbSet<Category> Categories { get; set; }
7
        public DbSet<Comment> Comments { get; set; }
8
9
        public ReviewedContext()
10
        {
11
            Configuration.ProxyCreationEnabled = false;
12
        }
13
    }

El Código de EF Primero nos permite generar tablas de base de datos con nada más que unos pocos Objetos CLR Antiguos (POCO).

Cada propiedad en esta clase corresponde a una tabla cuando se genera la base de datos. El Configuration.ProxyCreationEnabled = false; se asegura de que las entidades se recuperen como objetos de sus respectivas clases en lugar de proxies, lo que hace que la depuración sea mucho más fácil.

A continuación, configuramos un inicializador de base de datos. Un inicializador garantiza que la base de datos se crea correctamente cuando el modelo de datos sufre algún cambio. Sin un inicializador, tendrás que eliminar manualmente la base de datos si realizas un cambio en uno de tus POCO's. Hay algunos tipos diferentes de inicializadores para elegir: DropCreateDatabaseAlways y DropCreateDatabaseIfModelChanges. Los nombres se explican por sí mismos. El que vamos a utilizar es DropCreateDatabaseIfModelChanges.

Los inicializadores DropCreateDatabaseAlways y DropCreateDatabaseIfModelChanges tienen un efecto secundario: eliminan las tablas (y, por lo tanto, los datos) en la base de datos cuando cambia la estructura del modelo. Pero EF Code First proporciona una tercera forma de generar bases de datos: las migraciones. Esta nueva característica realiza un seguimiento de los cambios en la base de datos y no pierde datos a medida que cambian las clases de POCO.

Aquí está el código para nuestro inicializador:

1
2
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Models/ReviewedContextInitializer.cs

3
// http://slipsum.com/

4
public class ReviewedContextInitializer : DropCreateDatabaseIfModelChanges<ReviewedContext>
5
{
6
    protected override void Seed(ReviewedContext context)
7
    {
8
        // Use the context to seed the db.

9
    }
10
}

La clase ReviewedContextInitializer anula el método Seed(). Esto nos da la capacidad de llenar nuestra base de datos con algunos datos de prueba. Ahora, necesitamos visitar el archivo Global.asax y agregar la siguiente línea al método Application_Start():

1
2
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Global.asax.cs

3
Database.SetInitializer(new ReviewedContextInitializer());

Vamos a crear algunos repositorios para recuperar datos de la base de datos, y luego procederemos a configurar la inyección de dependencias (DI) con Ninject. Si no sabes exactamente qué es DI o Inversión de control (IoC), tómate un momento para leer este artículo.

Básicamente, la idea de inyección de dependencia es inject una dependencia concreta en una clase, a diferencia de codificar la clase para que dependa de la dependencia concreta. En otras palabras, es un desacoplamiento de una clase concreta de otra. Si eso sigue siendo claro como lodo, veamos un breve ejemplo:

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

Este código crea una clase llamada Foo. Depende de la funcionalidad de un objeto de tipo Bar, y el objeto Bar se crea dentro de la clase Foo. Esto puede ser difícil de mantener y una prueba unitaria porque:

  • Foo y Bar están fuertemente acoplados. Como resultado, el mantenimiento es menos que ideal.
  • Foo depende de una implementación específica de Bar, lo que dificulta las pruebas unitarias.

Este código se puede mejorar con solo unas pocas modificaciones. Echa un vistazo a la clase Foo revisada:

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

En poco menos de cuatro años, MVC en ASP.NET ha visto su cuarta versión...

Ahora, la clase Foo no depende de una implementación específica de Bar. En cambio, un objeto de una clase que implementa la interfaz IBar se suministra a Foo a través del constructor de este último. Este enfoque mejora en gran medida la capacidad de mantenimiento, al tiempo que nos permite inyectar cualquier objeto IBar, lo que facilita la prueba unitaria de nuestro código.

Con esa breve descripción fuera del camino, pongamos a Ninject en funcionamiento. Inicia la Consola del administrador de paquetes y ejecuta Install-Package Ninject.MVC3. Esto agregará Ninject a nuestro proyecto.

El primer repositorio que crearemos es ReviewsRepository, e implementará la interfaz IReviewRepository. Aquí está la interfaz:

1
2
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Models/Abstract/IReviewRepository.cs

3
public interface IReviewRepository
4
{
5
    Review Get(int id);
6
    IQueryable<Review> GetAll();
7
    Review Add(Review review);
8
    Review Update(Review review);
9
    void Delete(int reviewId);
10
    IEnumerable<Review> GetByCategory(Category category);
11
    IEnumerable<Comment> GetReviewComments(int id);
12
}

Esta interfaz garantiza que nuestros repositorios de revisión proporcionen las operaciones básicas de CRUD. También obtenemos la utilidad de recuperar revisiones por una categoría específica, así como de recuperar los comentarios para una revisión determinada. Ahora escribamos una clase concreta implementando esta interfaz:

1
2
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Models/Repos/ReviewRepository.cs

3
public class ReviewRepository : IReviewRepository
4
{
5
    private ReviewedContext _db { get; set; }
6
7
    public ReviewRepository()
8
        :this (new ReviewedContext())
9
    {
10
        
11
    }
12
13
    public ReviewRepository(ReviewedContext db)
14
    {
15
        _db = db;
16
    }
17
18
    public Review Get(int id)
19
    {
20
        return _db.Reviews.SingleOrDefault(r => r.Id == id);
21
    }
22
23
    public IQueryable<Review> GetAll()
24
    {
25
        return _db.Reviews;
26
    }
27
28
    public Review Add(Review review)
29
    {
30
        _db.Reviews.Add(review);
31
        _db.SaveChanges();
32
        return review;
33
    }
34
35
    public Review Update(Review review)
36
    {
37
        _db.Entry(review).State = EntityState.Modified;
38
        _db.SaveChanges();
39
        return review;
40
    }
41
42
    public void Delete(int reviewId)
43
    {
44
        var review = Get(reviewId);
45
        _db.Reviews.Remove(review);
46
    }
47
48
    public IEnumerable<Review> GetByCategory(Category category)
49
    {
50
        return _db.Reviews.Where(r => r.CategoryId == category.Id);
51
    }
52
53
    public IEnumerable<Comment> GetReviewComments(int id)
54
    {
55
        return _db.Comments.Where(c => c.ReviewId == id);
56
    }
57
}

WebAPI es un framework similar a MVC que podemos usar para crear fácilmente una API RESTful...

Este repositorio se basa en un objeto ReviewedContext que se almacena como una variable de clase. Esto nos permite usar LINQ en cualquiera de los métodos del repositorio, facilitando la interacción de la base de datos.

WebAPI tiene una buena característica que nos permite agregar nuestro propio framework DI. Esta función está fuera del alcance de este tutorial, así que asegúrate de leer este artículo para ayudarte a obtener esa configuración.

Una de las ubicaciones más importantes de nuestro código es la carpeta App_Start, que contiene un archivo llamado NinjectCommonWeb.cs (la instalación de Ninject agrega automáticamente este archivo a App_Start). Este archivo contiene una clase estática llamada NinjectWebCommon, y tiene un método llamado RegisterServices(). En este método, agrega el siguiente código:

1
2
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/App_Start/NinjectWebCommon.cs

3
kernel.Bind<IReviewRepository>().To<ReviewRepository>();
4
kernel.Bind<ICategoriesRepository>().To<CategoriesRepository>();
5
kernel.Bind<ICommentsRepository>().To<CommentsRepository>();
6
7
GlobalConfiguration.Configuration.DependencyResolver = new NinjectResolver(kernel);

Las primeras tres declaraciones unen una interfaz a una implementación concreta de la interfaz, y la cuarta línea configura el DI para WebAPI (la característica que se trata en el artículo mencionado anteriormente).

WebAPI

Ahora vamos a crear los controladores para la API. WebAPI es un framework similar a MVC que podemos usar para crear fácilmente un servicio REST, y puede ejecutarse dentro de una aplicación MVC4, en su propio proyecto, o puede ser auto hospedado fuera de IIS. Pero eso no es todo; tiene muchas otras características, tales como: negociación de contenido (para serializar automáticamente los datos en cualquier formato que se solicite), vinculación de modelos, validación y muchas más.

Primero necesitamos crear un punto final con WebAPI, y lo hacemos creando una clase que hereda ApiController. Comenzar con esto es bastante fácil. Visual Studio 2012 tiene una nueva característica que crea un nuevo controlador parcialmente con scaffolded o Scaffolding.

Create ApiControllerCreate ApiControllerCreate ApiController

Esto creará una clase de controlador con algunos métodos ya definidos para ti. Aquí hay un ejemplo:

1
2
// GET api/default1

3
public IEnumerable<string> Get()
4
{
5
    return new string[] { "value1", "value2" };
6
}
7
8
// GET api/default1/5

9
public string Get(int id)
10
{
11
    return "value";
12
}
13
14
// POST api/default1

15
public void Post(string value)
16
{
17
}
18
19
// PUT api/default1/5

20
public void Put(int id, string value)
21
{
22
}
23
24
// DELETE api/default1/5

25
public void Delete(int id)
26
{
27
}

Los nombres de los métodos corresponden al verbo HTTP que representan. Ahora vamos a crear la clase ReviewsController. El código es un poco largo, pero bastante sencillo.

1
2
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Controllers/ReviewsController.cs

3
public class ReviewsController : ApiController
4
{
5
    private ICategoriesRepository _categoriesRepository { get; set; }
6
    private IReviewRepository _reviewRepository { get; set; }
7
8
    public ReviewsController(IReviewRepository reviewRepository, ICategoriesRepository categoriesRepository)
9
    {
10
        _reviewRepository = reviewRepository;
11
        _categoriesRepository = categoriesRepository;
12
    }
13
14
    // GET api/review

15
    public IEnumerable<Review> Get()
16
    {
17
        var reviews = _reviewRepository.GetAll();
18
        return reviews;
19
    }
20
21
    // GET api/review/5

22
    public HttpResponseMessage Get(int id)
23
    {
24
        var category = _reviewRepository.Get(id);
25
        if (category == null)
26
        {
27
            return Request.CreateResponse(HttpStatusCode.NotFound);
28
        }
29
        return Request.CreateResponse(HttpStatusCode.OK, category);
30
    }
31
32
    // POST api/review

33
    public HttpResponseMessage Post(Review review)
34
    {
35
        var response = Request.CreateResponse(HttpStatusCode.Created, review);
36
        // Get the url to retrieve the newly created review.

37
        response.Headers.Location = new Uri(Request.RequestUri, string.Format("reviews/{0}", review.Id));
38
        _reviewRepository.Add(review);
39
        return response;
40
    }
41
42
    // PUT api/review/5

43
    public void Put(Review review)
44
    {
45
        _reviewRepository.Update(review);
46
    }
47
48
    // DELETE api/review/5

49
    public HttpResponseMessage Delete(int id)
50
    {
51
        _reviewRepository.Delete(id);
52
        return Request.CreateResponse(HttpStatusCode.NoContent);
53
    }
54
55
    // GET api/reviews/categories/{category}

56
    public HttpResponseMessage GetByCategory(string category)
57
    {
58
59
        var findCategory = _categoriesRepository.GetByName(category);
60
        if (findCategory == null)
61
        {
62
            return Request.CreateResponse(HttpStatusCode.NotFound);
63
        }
64
        return Request.CreateResponse(HttpStatusCode.OK,_reviewRepository.GetByCategory(findCategory));
65
    }
66
67
    // GET api/reviews/comments/{id}

68
    public HttpResponseMessage GetReviewComments(int id)
69
    {
70
71
        var reviewComments = _reviewRepository.GetReviewComments(id);
72
        if (reviewComments == null)
73
        {
74
            return Request.CreateResponse(HttpStatusCode.NotFound);
75
        }
76
        return Request.CreateResponse(HttpStatusCode.OK, reviewComments);
77
    }
78
}

Este código utiliza los objetos IReviewRepository e ICategoriesRepository para realizar la acción apropiada (por ejemplo: recuperar datos para solicitudes GET, agregar datos con solicitudes POST, etc.). Estos respositorios se inyectan con Ninject a través del Constructor Injection.

Si aún no tienes Fiddler, obténlo ahora, incluso si no eres un desarrollador de .NET.

Ten en cuenta que algunos de los métodos devuelven diferentes tipos de datos. WebAPI nos da la capacidad de devolver un tipo de datos no de cadena (como IEnumerable<Review>), y serializará el objeto para enviar la respuesta del servidor. También puedes usar la nueva clase HttpResonseMessage para devolver un código de estado HTTP específico junto con los datos devueltos. Una forma de crear un objeto HttpResponseMessage es llamando a Request.CreateResponse(responseCode, data).

Podemos probar adecuadamente nuestro proyecto WebAPI con una herramienta como Fiddler2. Si aún no tienes Fiddler, obténlo ahora, incluso si no eres un desarrollador de .NET. Fiddler es una fantástica herramienta de depuración HTTP. Una vez que estés ejecutando Fiddler, haz clic en RequestBuilder e ingresa la URL de la API que deseas probar. Luego elige el tipo de solicitud apropiado. Si realizas una solicitud POST, asegúrate de especificar un encabezado Content-Type: application/json y luego coloca una estructura JSON válida en el cuerpo de la solicitud. La siguiente imagen muestra una solicitud JSON POST sin procesar a la URL api/reviews:

Cuando envíes la solicitud, verás algo como la siguiente imagen:

Ten en cuenta que el código de estado de las solicitudes POST es un 201. WebAPI hace un gran trabajo al devolver el código de estado correcto para un servicio web RESTful. Diviértete con Fiddler2, ¡es una herramienta fantástica!

Con WebAPI, puedes especificar el enrutamiento para los controladores (como MVC). En MVC4, se agrega un archivo RouteConfig.cs a la carpeta App_Start. Las rutas para un proyecto WebAPI son como las rutas MVC.

1
2
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/App_Start/RouteConfig.cs

3
routes.MapHttpRoute(
4
    name: "GetReviewComments",
5
    routeTemplate: "api/reviews/comments/{id}",
6
    defaults: new { id = RouteParameter.Optional, controller = "Reviews", action = "GetReviewComments" }
7
);
8
9
routes.MapHttpRoute(
10
    name: "GetByCategories",
11
    routeTemplate: "api/reviews/categories/{category}",
12
    defaults: new { category = RouteParameter.Optional, controller = "Reviews", action = "GetByCategory" }
13
);
14
15
routes.MapHttpRoute(
16
    name: "DefaultApi",
17
    routeTemplate: "api/{controller}/{id}",
18
    defaults: new { id = RouteParameter.Optional }
19
);

La ruta DefaultApi es generada automáticamente por Visual Studio. Las otras dos rutas son personalizadas y se asignan a métodos específicos en el controlador Reviews. Hay muchos artículos y tutoriales que proporcionan buena información sobre el enrutamiento. Asegúrate de echarle un vistazo.

MVC4

Eso cubre mucho de lo que WebAPI tiene para ofrecer. A continuación, vamos a escribir algunos métodos para mostrar los datos. Consumiremos la API en un momento, pero por ahora usaremos los repositorios en nuestro HomeController. Un HomeController fue creado por Visual Studio; Solo modifiquemos sus métodos para mostrar los datos. Primero, obtengamos una lista de las categorías en el método Index.

1
2
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Controllers/HomeController.cs

3
private ICategoriesRepository _categoriesRepository { get; set; }
4
private IReviewRepository _reviewRepository { get; set; }
5
6
public HomeController(ICategoriesRepository categoriesRepository, IReviewRepository reviewRepository)
7
{
8
    _categoriesRepository = categoriesRepository;
9
    _reviewRepository = reviewRepository;
10
}
11
12
public ActionResult Index()
13
{
14
    var categories = _categoriesRepository.GetAll();
15
    return View(categories);
16
}

Aquí, continuamos usando DI aceptando los repositorios como parámetros para el constructor HomeController. Ninject inyecta automáticamente las clases concretas adecuadas para nosotros. A continuación, agreguemos algo de código a la vista Index para mostrar las categorías:

1
2
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Views/Home/Index.cshtml

3
@model IEnumerable<Reviewed.Models.Category>
4
5
<h3>Pick a Category:</h3>
6
<ul class="round">

7
    @foreach(var category in Model)
8
    {
9
        <li>
10
        <a href="@Url.Action("Reviews", new { id = @category.Name} )">@category.Name</a>
11
        </li>
12
    }
13
</ul>

Esto genera una lista de categorías en las que los usuarios pueden hacer clic. Ahora agrega un nuevo método a HomeController que recupera un Review. Llamaremos a este método Reviews, tal como se muestra aquí:

1
2
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Controllers/HomeController.cs

3
public ActionResult Reviews(string id)
4
{
5
    List<Review> reviews = new List<Review>();
6
    if (!string.IsNullOrWhiteSpace(id))
7
    {
8
        reviews = _reviewRepository.GetByCategory(_categoriesRepository.GetByName(id)).ToList();
9
    }
10
    else
11
    {
12
        reviews = _reviewRepository.GetAll().ToList();
13
    }
14
15
    foreach (var review in reviews)
16
    {
17
        var comments = _reviewRepository.GetReviewComments(review.Id);
18
        review.Comments = comments.ToList();
19
    }
20
    return View(reviews);
21
}

Debido a que ya existe una ruta para /{controller}/{action}/{id}, puedes usar una URL como Home/Reviews/Doctors. El motor de enrutamiento pasará "Doctors" como el parámetro id al método Reviews. Usamos el ID como la categoría y recuperamos todas las revisiones asociadas con esa categoría. Sin embargo, si no se proporciona una categoría, simplemente recuperamos todas las revisiones en la base de datos. Una vez que tenemos todas las revisiones, pasamos la lista de revisión a la vista. Veamos ahora la vista:

1
2
// https://github.com/jcreamer898/NetTutsMvcEf/blob/master/Reviewed/Views/Home/Reviews.cshtml

3
<div class="reviews">

4
@foreach(var review in Model)
5
{
6
    <h3>@review.Topic</h3>
7
    <p>
8
        @review.Content
9
    </p>
10
    
11
    var hasComments = review.Comments.Count > 0 ? "is-comments" : null;
12
    
13
    <ul class="@hasComments">

14
        @foreach(var comment in review.Comments)
15
        {
16
            <li>
17
                <strong>@(!comment.IsAnonymous ? string.Format("{0} says,", comment.Email) : "")</strong>
18
                <blockquote>@comment.Content</blockquote>
19
            </li>
20
        }
21
    </ul>
22
}
23
</div>

Este código utiliza una nueva característica de MVC4. El atributo de clase del elemento <ul> no aparecerá en el HTML si hasComments es nulo. Lee más sobre esta característica aquí.

JavaScript

Ninguna aplicación web moderna está completa sin JavaScript, y la usaremos para consumir nuestro servicio WebAPI. Usaremos Backbone.js para esto; así que, adelante y descarga Backbone y su dependencia Underscore. Coloca los archivos de JavaScript en el directorio Scripts.

Aprovecharemos otra nueva característica de MVC4 llamada agrupación de scripts. En la carpeta App_Start, encontrarás el archivo BundleConfig.cs. En este archivo, puedes configurar MVC4 para agrupar archivos JavaScript juntos. Ábrelo y agrega un nuevo paquete, como este:

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

Luego, en el archivo /Views/Shared/_Layout.cshtml, agrega lo siguiente en la parte inferior del body de la página:

1
2
@Scripts.Render("~/bundles/backbone")

Esto incluirá tus scripts si tu aplicación está en modo de depuración, o los dejará solos con la opción desactivada.

El código MVC4 que escribimos para recuperar la lista de revisión es una buena forma de mostrarlos, pero todo lo nuevo está usando Ajax. Así que vamos a refactorizar el código para utilizar Backbone.js. A través de JavaScript, recuperaremos las vistas de forma asíncrona después de que la página se haya cargado. Crea un nuevo archivo en la carpeta Scripts llamada home.js. Agrega el siguiente código a ese archivo:

1
2
var Review = Backbone.Model.extend();
3
var Reviews = Backbone.Collection.extend({
4
    model: Review,
5
    url: '/api/reviews'
6
});
7
var Comment = Backbone.Model.extend({});
8
var Comments = Backbone.Collection.extend({
9
    model: Comment,
10
    url: '/api/reviews/comments/'
11
});

Estos son los modelos de datos de JavaScript, cada uno correspondiente a una URL para recuperar datos del servicio WebAPI. Ahora vamos a escribir la vista:

1
2
var ListReviews = Backbone.View.extend({
3
    el: '.reviews',
4
    initialize: function() {
5
        this.collection.on('reset', this.render, this);
6
        this.collection.fetch();
7
    },
8
    render: function() {
9
        this.collection.each(this.renderItem, this);
10
    },
11
    renderItem: function(model) {
12
        var view = new ReviewItem({
13
            model: model
14
        });
15
        this.$el.append(view.el);
16
    }
17
});

Esta vista es para la lista completa de comentarios. Cuando se llama al método fetch() de la colección, se activa el evento reset y luego se llama a render(). El tercer parámetro pasado al método on() es el alcance, o lo que será this en la devolución de la llamada render(). En el método render(), llama al método each() de la colección, pasando el método renderItem(). Se llamará al método renderItem() en cada elemento de la colección, generando un nuevo ReviewItem para cada revisión.

El código para ReviewItem sigue:

1
2
var ReviewItem = Backbone.View.extend({
3
    events: {
4
        'click a': 'getComments'
5
    },
6
    tagName: 'li',
7
    initialize: function () {
8
        
9
        this.template = _.template($('#reviewsTemplate').html());
10
        this.collection = new Comments();
11
        this.collection.on('reset', this.loadComments, this);
12
        this.render();
13
    },
14
    render: function () {
15
        var html = this.template(this.model.toJSON());
16
        this.$el.append(html);
17
    },
18
    getComments: function () {
19
        this.collection.fetch({
20
            data: {
21
                Id: this.model.get('Id')
22
            }
23
        });
24
    },
25
    loadComments: function () {
26
        var self = this,
27
            item;
28
        this.comments = this.$el.find('ul');
29
        this.collection.each(function (comment) {
30
            item = new CommentItem({
31
                model: comment
32
            });
33
            self.comments.append(item.el);
34
        });
35
        
36
        this.$el.find('a').hide();
37
    }
38
});

WebAPI es una fantástica adición a la pila ASP.NET; una API basada en REST rica en características nunca ha sido tan fácil.

La vista ReviewItem es responsable de representar cada revisión individual. El método initialize() compila la plantilla utilizada para mostrar cada revisión; esta plantilla reside en un elemento <script>. Backbone saca la plantilla del elemento <script/> y la combina con la revisión.

También se configura un controlador de eventos clic para cargar los comentarios de cada revisión. Cuando se hace clic en el enlace, se llama al método getComments(), que busca los comentarios pasando un id al servicio WebAPI. El método fetch() es simplemente una abstracción del método $.ajax de jQuery, por lo que los parámetros Ajax normales, como data, se pueden pasar en la llamada fetch(). Por último, el método loadComments() se activará y creará una nueva vista CommentItem para cada comentario devuelto. El tagName en esta vista garantiza que la vista se crea con un <li> como su propiedad $el.

A continuación, veamos la vista CommentItem:

1
2
var CommentItem = Backbone.View.extend({
3
    tagName: 'li',
4
    initialize: function () {
5
        this.template = _.template($('#commentsTemplate').html());
6
        this.render();
7
    },
8
    render: function () {
9
        var html = this.template(this.model.toJSON());
10
        this.$el.html(html);
11
    }
12
    
13
});

Esta es una vista simple que muestra cada comentario. Ahora vamos a modificar la vista Review.cshtml de la siguiente manera:

1
2
@model IEnumerable<Reviewed.Models.Review>
3
4
@section scripts {
5
    <script type="text/javascript" src="/Scripts/home.js"></script>
6
}
7
8
<div class="reviews">
9
    <ul></ul>
10
</div>
11
12
<script type="text/html" id="reviewsTemplate">
13
14
    <h3><%= Topic %></h3>

15
    <p>
16
        <%= Content %>
17
    </p>

18
    
19
    <a href="#comments">Load Comments</a>

20
    <ul></ul>

21
22
</script>
23
24
<script type="text/html" id="commentsTemplate">
25
<li>
26
    <strong><%= !IsAnonymous ? Email + " says," : "" %></strong>

27
    <blockquote><%= Content %></blockquote>

28
</li>       

29
</script>

Observa los scripts de @section en el código anterior. Esta no es una característica nueva de MVC4, pero es una gran herramienta para representar partes específicas de JavaScript. En el archivo _layout.cshtml, también hay un @RenderSection("scripts", required: false) que representa la sección definida en la vista. Los elementos <script> son plantillas Underscore para representar contenido. Siguen una sintaxis de Ruby-esque, y cualquier cosa dentro de <% %> se evalúa como una declaración. Cualquier cosa dentro de <% =%> se enviará al HTML. Los bucles y las declaraciones condicionales se pueden utilizar de esta manera:

1
2
<ul>
3
<script type="text/html" id="template">
4
<% for (var i = 0; i < list.length; i++) { %>
5
    <li><%= list[i] %></li>

6
<% } %>
7
</ul>

8
9
<% if (someCondition) { %>
10
    <%= output %>
11
<% } %>
12
</script>

Esa es la plantilla. Para usarlo, haz esto:

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

Hay muchos frameworks de plantillas JavaScript disponibles en la Web: Handlebars.js, mustache.js y Hogan.js son muy populares. Asegúrate de revisarlos y elige el que funcione para ti.

Conclusión

WebAPI es una fantástica adición a la pila ASP.NET; una API basada en REST rica en características nunca ha sido tan fácil. Hay muchas nuevas características en MVC4. ¡Asegúrate de revisarlos! Como mencioné anteriormente, el código para este ejemplo está disponible en Github. ¡Tenlo!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.